summaryrefslogtreecommitdiffstats
path: root/editeng/source/editeng
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--editeng/source/editeng/editattr.cxx459
-rw-r--r--editeng/source/editeng/editdata.cxx16
-rw-r--r--editeng/source/editeng/editdbg.cxx531
-rw-r--r--editeng/source/editeng/editdoc.cxx3006
-rw-r--r--editeng/source/editeng/editeng.cxx2938
-rw-r--r--editeng/source/editeng/editobj.cxx759
-rw-r--r--editeng/source/editeng/editobj2.hxx282
-rw-r--r--editeng/source/editeng/editsel.cxx94
-rw-r--r--editeng/source/editeng/editsel.hxx58
-rw-r--r--editeng/source/editeng/editstt2.hxx99
-rw-r--r--editeng/source/editeng/editundo.cxx661
-rw-r--r--editeng/source/editeng/editundo.hxx291
-rw-r--r--editeng/source/editeng/editview.cxx1800
-rw-r--r--editeng/source/editeng/edtspell.cxx708
-rw-r--r--editeng/source/editeng/eehtml.cxx813
-rw-r--r--editeng/source/editeng/eehtml.hxx84
-rw-r--r--editeng/source/editeng/eeobj.cxx92
-rw-r--r--editeng/source/editeng/eeobj.hxx50
-rw-r--r--editeng/source/editeng/eerdll.cxx228
-rw-r--r--editeng/source/editeng/eertfpar.cxx633
-rw-r--r--editeng/source/editeng/eertfpar.hxx66
-rw-r--r--editeng/source/editeng/fieldupdater.cxx70
-rw-r--r--editeng/source/editeng/impedit.cxx2787
-rw-r--r--editeng/source/editeng/impedit.hxx1362
-rw-r--r--editeng/source/editeng/impedit2.cxx4509
-rw-r--r--editeng/source/editeng/impedit3.cxx4910
-rw-r--r--editeng/source/editeng/impedit4.cxx3141
-rw-r--r--editeng/source/editeng/impedit5.cxx845
-rw-r--r--editeng/source/editeng/misspellrange.cxx21
-rw-r--r--editeng/source/editeng/section.cxx19
-rw-r--r--editeng/source/editeng/textconv.cxx543
-rw-r--r--editeng/source/editeng/textconv.hxx107
32 files changed, 31982 insertions, 0 deletions
diff --git a/editeng/source/editeng/editattr.cxx b/editeng/source/editeng/editattr.cxx
new file mode 100644
index 0000000000..75bbcabc5a
--- /dev/null
+++ b/editeng/source/editeng/editattr.cxx
@@ -0,0 +1,459 @@
+/* -*- 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 <vcl/outdev.hxx>
+
+#include <svl/grabbagitem.hxx>
+#include <svl/voiditem.hxx>
+#include <libxml/xmlwriter.h>
+#include <editeng/svxfont.hxx>
+#include <editeng/flditem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/postitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <editeng/udlnitem.hxx>
+#include <editeng/contouritem.hxx>
+#include <editeng/shdditem.hxx>
+#include <editeng/escapementitem.hxx>
+#include <editeng/colritem.hxx>
+#include <editeng/wrlmitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/crossedoutitem.hxx>
+#include <editeng/kernitem.hxx>
+#include <editeng/autokernitem.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/emphasismarkitem.hxx>
+#include <editeng/charscaleitem.hxx>
+#include <editeng/charreliefitem.hxx>
+#include <editeng/cmapitem.hxx>
+
+#include <editattr.hxx>
+
+
+EditCharAttrib::EditCharAttrib(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 nS, sal_Int32 nE )
+: maItemHolder(rPool, &rItem)
+, nStart(nS)
+, nEnd(nE)
+, bFeature(false)
+, bEdge(false)
+{
+ assert((rItem.Which() >= EE_ITEMS_START) && (rItem.Which() <= EE_ITEMS_END));
+ assert((rItem.Which() < EE_FEATURE_START) || (rItem.Which() > EE_FEATURE_END) || (nE == (nS+1)));
+}
+
+EditCharAttrib::~EditCharAttrib()
+{
+}
+
+void EditCharAttrib::SetFont( SvxFont&, OutputDevice* )
+{
+}
+
+void EditCharAttrib::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("EditCharAttrib"));
+ (void)xmlTextWriterWriteFormatAttribute(
+ pWriter, BAD_CAST("nStart"), "%" SAL_PRIdINT32, nStart);
+ (void)xmlTextWriterWriteFormatAttribute(
+ pWriter, BAD_CAST("nEnd"), "%" SAL_PRIdINT32, nEnd);
+ GetItem()->dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+
+
+EditCharAttribFont::EditCharAttribFont(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_FONTINFO || rItem.Which() == EE_CHAR_FONTINFO_CJK || rItem.Which() == EE_CHAR_FONTINFO_CTL);
+}
+
+void EditCharAttribFont::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ const SvxFontItem& rAttr = static_cast<const SvxFontItem&>(*GetItem());
+
+ rFont.SetFamilyName( rAttr.GetFamilyName() );
+ rFont.SetFamily( rAttr.GetFamily() );
+ rFont.SetPitch( rAttr.GetPitch() );
+ rFont.SetCharSet( rAttr.GetCharSet() );
+}
+
+
+
+EditCharAttribItalic::EditCharAttribItalic(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_ITALIC || rItem.Which() == EE_CHAR_ITALIC_CJK || rItem.Which() == EE_CHAR_ITALIC_CTL);
+}
+
+void EditCharAttribItalic::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ rFont.SetItalic( static_cast<const SvxPostureItem*>(GetItem())->GetPosture() );
+}
+
+
+
+EditCharAttribWeight::EditCharAttribWeight(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_WEIGHT || rItem.Which() == EE_CHAR_WEIGHT_CJK || rItem.Which() == EE_CHAR_WEIGHT_CTL);
+}
+
+void EditCharAttribWeight::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ rFont.SetWeight( static_cast<const SvxWeightItem*>(GetItem())->GetValue() );
+}
+
+
+
+EditCharAttribUnderline::EditCharAttribUnderline(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_UNDERLINE);
+}
+
+void EditCharAttribUnderline::SetFont( SvxFont& rFont, OutputDevice* pOutDev )
+{
+ rFont.SetUnderline( static_cast<const SvxUnderlineItem*>(GetItem())->GetValue() );
+
+ if ( pOutDev )
+ pOutDev->SetTextLineColor( static_cast<const SvxUnderlineItem*>(GetItem())->GetColor() );
+
+}
+
+
+
+EditCharAttribOverline::EditCharAttribOverline(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_OVERLINE);
+}
+
+void EditCharAttribOverline::SetFont( SvxFont& rFont, OutputDevice* pOutDev )
+{
+ rFont.SetOverline( static_cast<const SvxOverlineItem*>(GetItem())->GetValue() );
+ if ( pOutDev )
+ pOutDev->SetOverlineColor( static_cast<const SvxOverlineItem*>(GetItem())->GetColor() );
+}
+
+
+
+EditCharAttribFontHeight::EditCharAttribFontHeight(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_FONTHEIGHT || rItem.Which() == EE_CHAR_FONTHEIGHT_CJK || rItem.Which() == EE_CHAR_FONTHEIGHT_CTL);
+}
+
+void EditCharAttribFontHeight::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ // Property is ignored
+ rFont.SetFontSize( Size( rFont.GetFontSize().Width(), static_cast<const SvxFontHeightItem*>(GetItem())->GetHeight() ) );
+}
+
+
+
+EditCharAttribFontWidth::EditCharAttribFontWidth(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_FONTWIDTH);
+}
+
+void EditCharAttribFontWidth::SetFont( SvxFont& /*rFont*/, OutputDevice* )
+{
+ // must be calculated outside, because f(device)...
+}
+
+
+
+EditCharAttribStrikeout::EditCharAttribStrikeout(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_STRIKEOUT);
+}
+
+void EditCharAttribStrikeout::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ rFont.SetStrikeout( static_cast<const SvxCrossedOutItem*>(GetItem())->GetValue() );
+}
+
+
+
+EditCharAttribCaseMap::EditCharAttribCaseMap(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_CASEMAP);
+}
+
+void EditCharAttribCaseMap::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ rFont.SetCaseMap( static_cast<const SvxCaseMapItem*>(GetItem())->GetCaseMap() );
+}
+
+
+
+EditCharAttribColor::EditCharAttribColor(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_COLOR);
+}
+
+void EditCharAttribColor::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ Color aColor = static_cast<const SvxColorItem*>(GetItem())->GetValue();
+ rFont.SetColor( aColor);
+}
+
+
+EditCharAttribBackgroundColor::EditCharAttribBackgroundColor(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_BKGCOLOR);
+}
+
+void EditCharAttribBackgroundColor::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ Color aColor = static_cast<const SvxColorItem*>(GetItem())->GetValue();
+ rFont.SetTransparent(aColor.IsTransparent());
+ rFont.SetFillColor(aColor);
+}
+
+EditCharAttribLanguage::EditCharAttribLanguage(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert((rItem.Which() == EE_CHAR_LANGUAGE) || (rItem.Which() == EE_CHAR_LANGUAGE_CJK) || (rItem.Which() == EE_CHAR_LANGUAGE_CTL));
+}
+
+void EditCharAttribLanguage::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ rFont.SetLanguage( static_cast<const SvxLanguageItem*>(GetItem())->GetLanguage() );
+}
+
+
+
+EditCharAttribShadow::EditCharAttribShadow(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_SHADOW);
+}
+
+void EditCharAttribShadow::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ rFont.SetShadow( static_cast<const SvxShadowedItem*>(GetItem())->GetValue() );
+}
+
+
+
+EditCharAttribEscapement::EditCharAttribEscapement(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_ESCAPEMENT);
+}
+
+void EditCharAttribEscapement::SetFont( SvxFont& rFont, OutputDevice* pOutDev )
+{
+ sal_uInt16 const nProp = static_cast<const SvxEscapementItem*>(GetItem())->GetProportionalHeight();
+ rFont.SetPropr( static_cast<sal_uInt8>(nProp) );
+
+ short nEsc = static_cast<const SvxEscapementItem*>(GetItem())->GetEsc();
+ rFont.SetNonAutoEscapement( nEsc, pOutDev );
+}
+
+
+
+EditCharAttribOutline::EditCharAttribOutline(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_OUTLINE);
+}
+
+void EditCharAttribOutline::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ rFont.SetOutline( static_cast<const SvxContourItem*>(GetItem())->GetValue() );
+}
+
+
+
+EditCharAttribTab::EditCharAttribTab(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 nPos)
+: EditCharAttrib(rPool, rItem, nPos, nPos+1)
+{
+ SetFeature( true );
+}
+
+void EditCharAttribTab::SetFont( SvxFont&, OutputDevice* )
+{
+}
+
+
+
+EditCharAttribLineBreak::EditCharAttribLineBreak(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 nPos)
+: EditCharAttrib(rPool, rItem, nPos, nPos+1)
+{
+ SetFeature( true );
+}
+
+void EditCharAttribLineBreak::SetFont( SvxFont&, OutputDevice* )
+{
+}
+
+
+
+EditCharAttribField::EditCharAttribField(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 nPos)
+: EditCharAttrib(rPool, rItem, nPos, nPos+1)
+{
+ SetFeature( true ); // !!!
+}
+
+void EditCharAttribField::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ if ( mxFldColor )
+ {
+ rFont.SetFillColor( *mxFldColor );
+ rFont.SetTransparent( false );
+ }
+ if ( mxTxtColor )
+ rFont.SetColor( *mxTxtColor );
+ if ( mxFldLineStyle )
+ rFont.SetUnderline( *mxFldLineStyle );
+}
+
+
+void EditCharAttribField::SetFieldValue(const OUString& rVal)
+{
+ aFieldValue = rVal;
+}
+
+void EditCharAttribField::Reset()
+{
+ aFieldValue.clear();
+ mxTxtColor.reset();
+ mxFldColor.reset();
+ mxFldLineStyle.reset();
+}
+
+EditCharAttribField::EditCharAttribField(const EditCharAttribField& rAttr)
+: EditCharAttrib(rAttr.GetHolder().getPool(), *rAttr.GetHolder().getItem(), rAttr.GetStart(), rAttr.GetEnd())
+, aFieldValue( rAttr.aFieldValue )
+{
+ // Use this constructor only for temporary Objects, Item is not pooled.
+ mxTxtColor = rAttr.mxTxtColor;
+ mxFldColor = rAttr.mxFldColor;
+ mxFldLineStyle = rAttr.mxFldLineStyle;
+}
+
+EditCharAttribField::~EditCharAttribField()
+{
+ Reset();
+}
+
+bool EditCharAttribField::operator == ( const EditCharAttribField& rAttr ) const
+{
+ if ( aFieldValue != rAttr.aFieldValue )
+ return false;
+
+ if ( ( mxTxtColor && !rAttr.mxTxtColor ) || ( !mxTxtColor && rAttr.mxTxtColor ) )
+ return false;
+ if ( ( mxTxtColor && rAttr.mxTxtColor ) && ( *mxTxtColor != *rAttr.mxTxtColor ) )
+ return false;
+
+ if ( ( mxFldColor && !rAttr.mxFldColor ) || ( !mxFldColor && rAttr.mxFldColor ) )
+ return false;
+ if ( ( mxFldColor && rAttr.mxFldColor ) && ( *mxFldColor != *rAttr.mxFldColor ) )
+ return false;
+
+ if ( ( mxFldLineStyle && !rAttr.mxFldLineStyle ) || ( !mxFldLineStyle && rAttr.mxFldLineStyle ) )
+ return false;
+ if ( ( mxFldLineStyle && rAttr.mxFldLineStyle ) && ( *mxFldLineStyle != *rAttr.mxFldLineStyle ) )
+ return false;
+
+ return true;
+}
+
+
+
+EditCharAttribPairKerning::EditCharAttribPairKerning(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_PAIRKERNING);
+}
+
+void EditCharAttribPairKerning::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ rFont.SetKerning( static_cast<const SvxAutoKernItem*>(GetItem())->GetValue() ? FontKerning::FontSpecific : FontKerning::NONE );
+}
+
+
+
+EditCharAttribKerning::EditCharAttribKerning(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_KERNING);
+}
+
+void EditCharAttribKerning::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ rFont.SetFixKerning( static_cast<const SvxKerningItem*>(GetItem())->GetValue() );
+}
+
+
+
+EditCharAttribWordLineMode::EditCharAttribWordLineMode(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_WLM);
+}
+
+void EditCharAttribWordLineMode::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ rFont.SetWordLineMode( static_cast<const SvxWordLineModeItem*>(GetItem())->GetValue() );
+}
+
+
+
+EditCharAttribEmphasisMark::EditCharAttribEmphasisMark(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_EMPHASISMARK);
+}
+
+void EditCharAttribEmphasisMark::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ rFont.SetEmphasisMark( static_cast<const SvxEmphasisMarkItem*>(GetItem())->GetEmphasisMark() );
+}
+
+
+
+EditCharAttribRelief::EditCharAttribRelief(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_RELIEF);
+}
+
+void EditCharAttribRelief::SetFont( SvxFont& rFont, OutputDevice* )
+{
+ rFont.SetRelief( static_cast<const SvxCharReliefItem*>(GetItem())->GetValue() );
+}
+
+
+EditCharAttribGrabBag::EditCharAttribGrabBag(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 _nStart, sal_Int32 _nEnd)
+: EditCharAttrib(rPool, rItem, _nStart, _nEnd)
+{
+ assert(rItem.Which() == EE_CHAR_GRABBAG);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/editdata.cxx b/editeng/source/editeng/editdata.cxx
new file mode 100644
index 0000000000..f272a9afd5
--- /dev/null
+++ b/editeng/source/editeng/editdata.cxx
@@ -0,0 +1,16 @@
+/* -*- 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/.
+ */
+
+#include <editeng/editdata.hxx>
+
+#include <limits>
+
+const size_t EE_APPEND = std::numeric_limits<size_t>::max();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/editdbg.cxx b/editeng/source/editeng/editdbg.cxx
new file mode 100644
index 0000000000..31ec9d0930
--- /dev/null
+++ b/editeng/source/editeng/editdbg.cxx
@@ -0,0 +1,531 @@
+/* -*- 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 <memory>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/window.hxx>
+
+#include <editeng/lspcitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <editeng/ulspitem.hxx>
+#include <editeng/contouritem.hxx>
+#include <editeng/colritem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/adjustitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <editeng/postitem.hxx>
+#include <editeng/udlnitem.hxx>
+#include <editeng/crossedoutitem.hxx>
+#include <editeng/shdditem.hxx>
+#include <editeng/escapementitem.hxx>
+#include <editeng/kernitem.hxx>
+#include <editeng/wrlmitem.hxx>
+#include <editeng/autokernitem.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/emphasismarkitem.hxx>
+#include <editeng/numitem.hxx>
+#include <editeng/tstpitem.hxx>
+#include <editeng/charscaleitem.hxx>
+#include <editeng/charreliefitem.hxx>
+#include <editeng/frmdiritem.hxx>
+
+#include "impedit.hxx"
+#include <editeng/editeng.hxx>
+#include <editeng/editview.hxx>
+#include <editdoc.hxx>
+
+#include <rtl/strbuf.hxx>
+#include <osl/diagnose.h>
+
+#if defined( DBG_UTIL ) || ( OSL_DEBUG_LEVEL > 1 )
+
+namespace
+{
+struct DebOutBuffer
+{
+ OStringBuffer str;
+ void append(std::string_view descr, const SfxEnumItemInterface& rItem)
+ {
+ str.append(descr + OString::number(rItem.GetEnumValue()));
+ }
+ void append(std::string_view descr, const SvxLRSpaceItem& rItem)
+ {
+ str.append(OString::Concat(descr) + "FI=" + OString::number(rItem.GetTextFirstLineOffset())
+ + ", LI=" + OString::number(rItem.GetTextLeft())
+ + ", RI=" + OString::number(rItem.GetRight()));
+ }
+ void append(std::string_view descr, const SvxNumBulletItem& rItem)
+ {
+ str.append(descr);
+ for (sal_uInt16 nLevel = 0; nLevel < 3; nLevel++)
+ {
+ str.append("Level" + OString::number(nLevel) + "=");
+ const SvxNumberFormat* pFmt = rItem.GetNumRule().Get(nLevel);
+ if (pFmt)
+ {
+ str.append("(" + OString::number(pFmt->GetFirstLineOffset()) + ","
+ + OString::number(pFmt->GetAbsLSpace()) + ",");
+ if (pFmt->GetNumberingType() == SVX_NUM_BITMAP)
+ str.append("Bitmap");
+ else if (pFmt->GetNumberingType() != SVX_NUM_CHAR_SPECIAL)
+ str.append("Number");
+ else
+ {
+ str.append("Char=[" + OString::number(pFmt->GetBulletChar()) + "]");
+ }
+ str.append(") ");
+ }
+ }
+ }
+ void append(std::string_view descr, const SfxBoolItem& rItem)
+ {
+ str.append(descr + OString::number(static_cast<int>(rItem.GetValue())));
+ }
+ void append(std::string_view descr, const SfxInt16Item& rItem)
+ {
+ str.append(descr + OString::number(rItem.GetValue()));
+ }
+ void append(std::string_view descr, const SfxUInt16Item& rItem)
+ {
+ str.append(descr + OString::number(rItem.GetValue()));
+ }
+ void append(const SvxULSpaceItem& rItem)
+ {
+ str.append("SB=" + OString::number(rItem.GetUpper())
+ + ", SA=" + OString::number(rItem.GetLower()));
+ }
+ void append(std::string_view descr, const SvxLineSpacingItem& rItem)
+ {
+ str.append(descr);
+ if (rItem.GetLineSpaceRule() == SvxLineSpaceRule::Min)
+ {
+ str.append("Min: " + OString::number(rItem.GetInterLineSpace()));
+ }
+ else if (rItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop)
+ {
+ str.append("Prop: " + OString::number(rItem.GetPropLineSpace()));
+ }
+ else
+ str.append("Unsupported Type!");
+ }
+ void append(const SvxTabStopItem& rTabs)
+ {
+ str.append("Tabs: " + OString::number(rTabs.Count()));
+ if (rTabs.Count())
+ {
+ str.append("( ");
+ for (sal_uInt16 i = 0; i < rTabs.Count(); ++i)
+ {
+ const SvxTabStop& rTab = rTabs[i];
+ str.append(OString::number(rTab.GetTabPos()) + " ");
+ }
+ str.append(')');
+ }
+ }
+ void append(std::string_view descr, const SvxColorItem& rItem)
+ {
+ Color aColor(rItem.GetValue());
+ str.append(descr + OString::number(aColor.GetRed()) + ", "
+ + OString::number(aColor.GetGreen()) + ", " + OString::number(aColor.GetBlue()));
+ }
+ void append(std::string_view descr, const SvxFontItem& rItem)
+ {
+ str.append(descr + OUStringToOString(rItem.GetFamilyName(), RTL_TEXTENCODING_ASCII_US)
+ + " (CharSet: " + OString::number(rItem.GetCharSet()) + ")");
+ }
+ void append(std::string_view descr, const SvxEscapementItem& rItem)
+ {
+ str.append(descr + OString::number(rItem.GetEsc()) + ", "
+ + OString::number(rItem.GetProportionalHeight()));
+ }
+ void appendHeightAndPts(std::string_view descr, tools::Long h, MapUnit eUnit)
+ {
+ MapMode aItemMapMode(eUnit);
+ MapMode aPntMap(MapUnit::MapPoint);
+ Size aSz = OutputDevice::LogicToLogic(Size(0, h), aItemMapMode, aPntMap);
+ str.append(descr + OString::number(h) + " Points=" + OString::number(aSz.Height()));
+ }
+ void append(std::string_view descr, const SvxFontHeightItem& rItem, const SfxItemPool& rPool)
+ {
+ appendHeightAndPts(descr, rItem.GetHeight(), rPool.GetMetric(rItem.Which()));
+ }
+ void append(std::string_view descr, const SvxKerningItem& rItem, const SfxItemPool& rPool)
+ {
+ appendHeightAndPts(descr, rItem.GetValue(), rPool.GetMetric(rItem.Which()));
+ }
+};
+}
+
+static OString DbgOutItem(const SfxItemPool& rPool, const SfxPoolItem& rItem)
+{
+ DebOutBuffer buffer;
+ switch ( rItem.Which() )
+ {
+ case EE_PARA_WRITINGDIR:
+ buffer.append("WritingDir=", rItem.StaticWhichCast(EE_PARA_WRITINGDIR));
+ break;
+ case EE_PARA_OUTLLRSPACE:
+ buffer.append("Outline ", rItem.StaticWhichCast(EE_PARA_OUTLLRSPACE));
+ break;
+ case EE_PARA_LRSPACE:
+ buffer.append("", rItem.StaticWhichCast(EE_PARA_LRSPACE));
+ break;
+ case EE_PARA_NUMBULLET:
+ buffer.append("NumItem ", rItem.StaticWhichCast(EE_PARA_NUMBULLET));
+ break;
+ case EE_PARA_BULLETSTATE:
+ buffer.append("ShowBullet=", rItem.StaticWhichCast(EE_PARA_BULLETSTATE));
+ break;
+ case EE_PARA_HYPHENATE:
+ buffer.append("Hyphenate=", rItem.StaticWhichCast(EE_PARA_HYPHENATE));
+ break;
+ case EE_PARA_OUTLLEVEL:
+ buffer.append("Level=", rItem.StaticWhichCast(EE_PARA_OUTLLEVEL));
+ break;
+ case EE_PARA_ULSPACE:
+ buffer.append(rItem.StaticWhichCast(EE_PARA_ULSPACE));
+ break;
+ case EE_PARA_SBL:
+ buffer.append("SBL=", rItem.StaticWhichCast(EE_PARA_SBL));
+ break;
+ case EE_PARA_JUST:
+ buffer.append("SvxAdust=", rItem.StaticWhichCast(EE_PARA_JUST));
+ break;
+ case EE_PARA_TABS:
+ buffer.append(rItem.StaticWhichCast(EE_PARA_TABS));
+ break;
+ case EE_CHAR_LANGUAGE:
+ buffer.append("Language=", rItem.StaticWhichCast(EE_CHAR_LANGUAGE));
+ break;
+ case EE_CHAR_LANGUAGE_CJK:
+ buffer.append("LanguageCJK=", rItem.StaticWhichCast(EE_CHAR_LANGUAGE_CJK));
+ break;
+ case EE_CHAR_LANGUAGE_CTL:
+ buffer.append("LanguageCTL=", rItem.StaticWhichCast(EE_CHAR_LANGUAGE_CTL));
+ break;
+ case EE_CHAR_COLOR:
+ buffer.append("Color= ", rItem.StaticWhichCast(EE_CHAR_COLOR));
+ break;
+ case EE_CHAR_BKGCOLOR:
+ buffer.append("FillColor= ", rItem.StaticWhichCast(EE_CHAR_BKGCOLOR));
+ break;
+ case EE_CHAR_FONTINFO:
+ buffer.append("Font=", rItem.StaticWhichCast(EE_CHAR_FONTINFO));
+ break;
+ case EE_CHAR_FONTINFO_CJK:
+ buffer.append("FontCJK=", rItem.StaticWhichCast(EE_CHAR_FONTINFO_CJK));
+ break;
+ case EE_CHAR_FONTINFO_CTL:
+ buffer.append("FontCTL=", rItem.StaticWhichCast(EE_CHAR_FONTINFO_CTL));
+ break;
+ case EE_CHAR_FONTHEIGHT:
+ buffer.append("Size=", rItem.StaticWhichCast(EE_CHAR_FONTHEIGHT), rPool);
+ break;
+ case EE_CHAR_FONTHEIGHT_CJK:
+ buffer.append("SizeCJK=", rItem.StaticWhichCast(EE_CHAR_FONTHEIGHT_CJK), rPool);
+ break;
+ case EE_CHAR_FONTHEIGHT_CTL:
+ buffer.append("SizeCTL=", rItem.StaticWhichCast(EE_CHAR_FONTHEIGHT_CTL), rPool);
+ break;
+ case EE_CHAR_FONTWIDTH:
+ buffer.append("Width=", rItem.StaticWhichCast(EE_CHAR_FONTWIDTH));
+ break;
+ case EE_CHAR_WEIGHT:
+ buffer.append("FontWeight=", rItem.StaticWhichCast(EE_CHAR_WEIGHT));
+ break;
+ case EE_CHAR_WEIGHT_CJK:
+ buffer.append("FontWeightCJK=", rItem.StaticWhichCast(EE_CHAR_WEIGHT_CJK));
+ break;
+ case EE_CHAR_WEIGHT_CTL:
+ buffer.append("FontWeightCTL=", rItem.StaticWhichCast(EE_CHAR_WEIGHT_CTL));
+ break;
+ case EE_CHAR_UNDERLINE:
+ buffer.append("FontUnderline=", rItem.StaticWhichCast(EE_CHAR_UNDERLINE));
+ break;
+ case EE_CHAR_OVERLINE:
+ buffer.append("FontOverline=", rItem.StaticWhichCast(EE_CHAR_OVERLINE));
+ break;
+ case EE_CHAR_EMPHASISMARK:
+ buffer.append("FontEmphasisMark=", rItem.StaticWhichCast(EE_CHAR_EMPHASISMARK));
+ break;
+ case EE_CHAR_RELIEF:
+ buffer.append("FontRelief=", rItem.StaticWhichCast(EE_CHAR_RELIEF));
+ break;
+ case EE_CHAR_STRIKEOUT:
+ buffer.append("FontStrikeout=", rItem.StaticWhichCast(EE_CHAR_STRIKEOUT));
+ break;
+ case EE_CHAR_ITALIC:
+ buffer.append("FontPosture=", rItem.StaticWhichCast(EE_CHAR_ITALIC));
+ break;
+ case EE_CHAR_ITALIC_CJK:
+ buffer.append("FontPostureCJK=", rItem.StaticWhichCast(EE_CHAR_ITALIC_CJK));
+ break;
+ case EE_CHAR_ITALIC_CTL:
+ buffer.append("FontPostureCTL=", rItem.StaticWhichCast(EE_CHAR_ITALIC_CTL));
+ break;
+ case EE_CHAR_OUTLINE:
+ buffer.append("FontOutline=", rItem.StaticWhichCast(EE_CHAR_OUTLINE));
+ break;
+ case EE_CHAR_SHADOW:
+ buffer.append("FontShadowed=", rItem.StaticWhichCast(EE_CHAR_SHADOW));
+ break;
+ case EE_CHAR_ESCAPEMENT:
+ buffer.append("Escape=", rItem.StaticWhichCast(EE_CHAR_ESCAPEMENT));
+ break;
+ case EE_CHAR_PAIRKERNING:
+ buffer.append("PairKerning=", rItem.StaticWhichCast(EE_CHAR_PAIRKERNING));
+ break;
+ case EE_CHAR_KERNING:
+ buffer.append("Kerning=", rItem.StaticWhichCast(EE_CHAR_KERNING), rPool);
+ break;
+ case EE_CHAR_WLM:
+ buffer.append("WordLineMode=", rItem.StaticWhichCast(EE_CHAR_WLM));
+ break;
+ case EE_CHAR_XMLATTRIBS:
+ buffer.str.append("XMLAttribs=...");
+ break;
+ }
+ return buffer.str.makeStringAndClear();
+}
+
+static void DbgOutItemSet(FILE* fp, const SfxItemSet& rSet, bool bSearchInParent, bool bShowALL)
+{
+ for ( sal_uInt16 nWhich = EE_PARA_START; nWhich <= EE_CHAR_END; nWhich++ )
+ {
+ fprintf( fp, "\nWhich: %i\t", nWhich );
+ if ( rSet.GetItemState( nWhich, bSearchInParent ) == SfxItemState::DEFAULT )
+ fprintf( fp, "ITEM_OFF " );
+ else if ( rSet.GetItemState( nWhich, bSearchInParent ) == SfxItemState::DONTCARE )
+ fprintf( fp, "ITEM_DC " );
+ else if ( rSet.GetItemState( nWhich, bSearchInParent ) == SfxItemState::SET )
+ fprintf( fp, "ITEM_ON *" );
+
+ if ( !bShowALL && ( rSet.GetItemState( nWhich, bSearchInParent ) != SfxItemState::SET ) )
+ continue;
+
+ const SfxPoolItem& rItem = rSet.Get( nWhich, bSearchInParent );
+ OString aDebStr = DbgOutItem( *rSet.GetPool(), rItem );
+ fprintf( fp, "%s", aDebStr.getStr() );
+ }
+}
+
+void EditEngine::DumpData(const EditEngine* pEE, bool bInfoBox)
+{
+ if (!pEE)
+ return;
+
+ FILE* fp = fopen( "editenginedump.log", "w" );
+ if ( fp == nullptr )
+ {
+ OSL_FAIL( "Log file could not be created!" );
+ return;
+ }
+
+ const SfxItemPool& rPool = *pEE->GetEmptyItemSet().GetPool();
+
+ fprintf( fp, "================================================================================" );
+ fprintf( fp, "\n================== Document ================================================" );
+ fprintf( fp, "\n================================================================================" );
+ for ( sal_Int32 nPortion = 0; nPortion < pEE->pImpEditEngine->GetParaPortions().Count(); nPortion++)
+ {
+ ParaPortion* pPPortion = pEE->pImpEditEngine->GetParaPortions()[nPortion];
+ fprintf( fp, "\nParagraph %" SAL_PRIdINT32 ": Length = %" SAL_PRIdINT32 ", Invalid = %i\nText = '%s'",
+ nPortion, pPPortion->GetNode()->Len(), pPPortion->IsInvalid(),
+ OUStringToOString(pPPortion->GetNode()->GetString(), RTL_TEXTENCODING_UTF8).getStr() );
+ fprintf( fp, "\nVorlage:" );
+ SfxStyleSheet* pStyle = pPPortion->GetNode()->GetStyleSheet();
+ if ( pStyle )
+ fprintf( fp, " %s", OUStringToOString( pStyle->GetName(), RTL_TEXTENCODING_UTF8).getStr() );
+ fprintf( fp, "\nParagraph attribute:" );
+ DbgOutItemSet( fp, pPPortion->GetNode()->GetContentAttribs().GetItems(), false, false );
+
+ fprintf( fp, "\nCharacter attribute:" );
+ bool bZeroAttr = false;
+ for ( sal_Int32 z = 0; z < pPPortion->GetNode()->GetCharAttribs().Count(); ++z )
+ {
+ const std::unique_ptr<EditCharAttrib>& rAttr = pPPortion->GetNode()->GetCharAttribs().GetAttribs()[z];
+ OString aCharAttribs =
+ "\nA"
+ + OString::number(nPortion)
+ + ": "
+ + OString::number(rAttr->GetItem()->Which())
+ + "\t"
+ + OString::number(rAttr->GetStart())
+ + "\t"
+ + OString::number(rAttr->GetEnd());
+ if ( rAttr->IsEmpty() )
+ bZeroAttr = true;
+ fprintf(fp, "%s => ", aCharAttribs.getStr());
+
+ OString aDebStr = DbgOutItem( rPool, *rAttr->GetItem() );
+ fprintf( fp, "%s", aDebStr.getStr() );
+ }
+ if ( bZeroAttr )
+ fprintf( fp, "\nNULL-Attribute!" );
+
+ const sal_Int32 nTextPortions = pPPortion->GetTextPortions().Count();
+ OStringBuffer aPortionStr("\nText portions: #"
+ + OString::number(nTextPortions)
+ + " \nA"
+ + OString::number(nPortion)
+ + ": Paragraph Length = "
+ + OString::number(pPPortion->GetNode()->Len())
+ + "\nA"
+ + OString::number(nPortion)
+ + ": ");
+ sal_Int32 n = 0;
+ for ( sal_Int32 z = 0; z < nTextPortions; ++z )
+ {
+ TextPortion& rPortion = pPPortion->GetTextPortions()[z];
+ aPortionStr.append(" "
+ + OString::number(rPortion.GetLen())
+ + "("
+ + OString::number(rPortion.GetSize().Width())
+ + ")"
+ "["
+ + OString::number(static_cast<sal_Int32>(rPortion.GetKind()))
+ + "];");
+ n += rPortion.GetLen();
+ }
+ aPortionStr.append("\nA"
+ + OString::number(nPortion)
+ + ": Total length: "
+ + OString::number(n));
+ if ( pPPortion->GetNode()->Len() != n )
+ aPortionStr.append(" => Error !!!");
+ fprintf(fp, "%s", aPortionStr.getStr());
+
+ fprintf( fp, "\n\nLines:" );
+ // First the content ...
+ for ( sal_Int32 nLine = 0; nLine < pPPortion->GetLines().Count(); nLine++ )
+ {
+ EditLine& rLine = pPPortion->GetLines()[nLine];
+
+ OString aLine(OUStringToOString(pPPortion->GetNode()->Copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart()), RTL_TEXTENCODING_ASCII_US));
+ fprintf( fp, "\nLine %" SAL_PRIdINT32 "\t>%s<", nLine, aLine.getStr() );
+ }
+ // then the internal data ...
+ for ( sal_Int32 nLine = 0; nLine < pPPortion->GetLines().Count(); nLine++ )
+ {
+ EditLine& rLine = pPPortion->GetLines()[nLine];
+ fprintf( fp, "\nLine %" SAL_PRIdINT32 ":\tStart: %" SAL_PRIdINT32 ",\tEnd: %" SAL_PRIdINT32, nLine, rLine.GetStart(), rLine.GetEnd() );
+ fprintf( fp, "\t\tPortions: %" SAL_PRIdINT32 " - %" SAL_PRIdINT32 ".\tHight: %i, Ascent=%i", rLine.GetStartPortion(), rLine.GetEndPortion(), rLine.GetHeight(), rLine.GetMaxAscent() );
+ }
+
+ fprintf( fp, "\n-----------------------------------------------------------------------------" );
+ }
+
+ if ( pEE->pImpEditEngine->GetStyleSheetPool() )
+ {
+ SfxStyleSheetIterator aIter( pEE->pImpEditEngine->GetStyleSheetPool(), SfxStyleFamily::All );
+ sal_uInt16 nStyles = aIter.Count();
+ fprintf( fp, "\n\n================================================================================" );
+ fprintf( fp, "\n================== Stylesheets =============================================" );
+ fprintf( fp, "\n================================================================================" );
+ fprintf( fp, "\n#Template: %" SAL_PRIuUINT32 "\n", sal_uInt32(nStyles) );
+ SfxStyleSheetBase* pStyle = aIter.First();
+ while ( pStyle )
+ {
+ fprintf( fp, "\nTemplate: %s", OUStringToOString( pStyle->GetName(), RTL_TEXTENCODING_ASCII_US ).getStr() );
+ fprintf( fp, "\nParent: %s", OUStringToOString( pStyle->GetParent(), RTL_TEXTENCODING_ASCII_US ).getStr() );
+ fprintf( fp, "\nFollow: %s", OUStringToOString( pStyle->GetFollow(), RTL_TEXTENCODING_ASCII_US ).getStr() );
+ DbgOutItemSet( fp, pStyle->GetItemSet(), false, false );
+ fprintf( fp, "\n----------------------------------" );
+
+ pStyle = aIter.Next();
+ }
+ }
+
+ fprintf( fp, "\n\n================================================================================" );
+ fprintf( fp, "\n================== Defaults ================================================" );
+ fprintf( fp, "\n================================================================================" );
+ DbgOutItemSet( fp, pEE->pImpEditEngine->GetEmptyItemSet(), true, true );
+
+ fprintf( fp, "\n\n================================================================================" );
+ fprintf( fp, "\n================== EditEngine & Views ======================================" );
+ fprintf( fp, "\n================================================================================" );
+ fprintf( fp, "\nControl: %x", unsigned( pEE->GetControlWord() ) );
+ fprintf( fp, "\nRefMapMode: %i", int( pEE->pImpEditEngine->pRefDev->GetMapMode().GetMapUnit() ) );
+ fprintf( fp, "\nPaperSize: %" SAL_PRIdINT64 " x %" SAL_PRIdINT64, sal_Int64(pEE->GetPaperSize().Width()), sal_Int64(pEE->GetPaperSize().Height()) );
+ fprintf( fp, "\nMaxAutoPaperSize: %" SAL_PRIdINT64 " x %" SAL_PRIdINT64, sal_Int64(pEE->GetMaxAutoPaperSize().Width()), sal_Int64(pEE->GetMaxAutoPaperSize().Height()) );
+ fprintf( fp, "\nMinAutoPaperSize: %" SAL_PRIdINT64 " x %" SAL_PRIdINT64 , sal_Int64(pEE->GetMinAutoPaperSize().Width()), sal_Int64(pEE->GetMinAutoPaperSize().Height()) );
+ fprintf( fp, "\nCalculateLayout: %i", pEE->IsUpdateLayout() );
+ fprintf( fp, "\nNumber of Views: %" SAL_PRI_SIZET "i", pEE->GetViewCount() );
+ for ( size_t nView = 0; nView < pEE->GetViewCount(); nView++ )
+ {
+ EditView* pV = pEE->GetView( nView );
+ DBG_ASSERT( pV, "View not found!" );
+ fprintf( fp, "\nView %zu: Focus=%i", nView, pV->GetWindow()->HasFocus() );
+ tools::Rectangle aR( pV->GetOutputArea() );
+ fprintf( fp, "\n OutputArea: nX=%" SAL_PRIdINT64 ", nY=%" SAL_PRIdINT64 ", dX=%" SAL_PRIdINT64 ", dY=%" SAL_PRIdINT64 ", MapMode = %i",
+ sal_Int64(aR.Left()), sal_Int64(aR.Top()), sal_Int64(aR.GetSize().Width()), sal_Int64(aR.GetSize().Height()) , int( pV->GetWindow()->GetMapMode().GetMapUnit() ) );
+ aR = pV->GetVisArea();
+ fprintf( fp, "\n VisArea: nX=%" SAL_PRIdINT64 ", nY=%" SAL_PRIdINT64 ", dX=%" SAL_PRIdINT64 ", dY=%" SAL_PRIdINT64,
+ sal_Int64(aR.Left()), sal_Int64(aR.Top()), sal_Int64(aR.GetSize().Width()), sal_Int64(aR.GetSize().Height()) );
+ ESelection aSel = pV->GetSelection();
+ fprintf( fp, "\n Selection: Start=%" SAL_PRIdINT32 ",%" SAL_PRIdINT32 ", End=%" SAL_PRIdINT32 ",%" SAL_PRIdINT32, aSel.nStartPara, aSel.nStartPos, aSel.nEndPara, aSel.nEndPos );
+ }
+ if ( pEE->GetActiveView() )
+ {
+ fprintf( fp, "\n\n================================================================================" );
+ fprintf( fp, "\n================== Current View ===========================================" );
+ fprintf( fp, "\n================================================================================" );
+ DbgOutItemSet( fp, pEE->GetActiveView()->GetAttribs(), true, false );
+ }
+ fclose( fp );
+ if ( bInfoBox )
+ {
+ std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(nullptr,
+ VclMessageType::Info, VclButtonsType::Ok,
+ "Dumped editenginedump.log!" ));
+ xInfoBox->run();
+ }
+}
+#endif
+
+#if OSL_DEBUG_LEVEL > 0
+bool ParaPortion::DbgCheckTextPortions(ParaPortion const& rPara)
+{
+ // check, if Portion length ok:
+ sal_uInt16 nXLen = 0;
+ for (sal_Int32 nPortion = 0; nPortion < rPara.aTextPortionList.Count(); nPortion++)
+ {
+ nXLen = nXLen + rPara.aTextPortionList[nPortion].GetLen();
+ }
+ return nXLen == rPara.pNode->Len();
+}
+#endif
+
+#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
+void CheckOrderedList(const CharAttribList::AttribsType& rAttribs)
+{
+ sal_Int32 nPrev = 0;
+ for (const std::unique_ptr<EditCharAttrib>& rAttr : rAttribs)
+ {
+ sal_Int32 const nCur = rAttr->GetStart();
+ assert(nCur >= nPrev);
+ nPrev = nCur;
+ }
+}
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/editdoc.cxx b/editeng/source/editeng/editdoc.cxx
new file mode 100644
index 0000000000..d892bd1c3a
--- /dev/null
+++ b/editeng/source/editeng/editdoc.cxx
@@ -0,0 +1,3006 @@
+/* -*- 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/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 <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+
+#include <svl/grabbagitem.hxx>
+#include <svl/voiditem.hxx>
+#include <tools/debug.hxx>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <libxml/xmlwriter.h>
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <limits>
+#include <memory>
+#include <set>
+#include <string_view>
+#include <utility>
+
+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] =
+{
+ // _nSID, _bNeedsPoolRegistration, _bShareable
+ { SID_ATTR_FRAMEDIRECTION, false, true }, // EE_PARA_WRITINGDIR
+ { 0, true, true }, // EE_PARA_XMLATTRIBS
+ { SID_ATTR_PARA_HANGPUNCTUATION, false, true }, // EE_PARA_HANGINGPUNCTUATION
+ { SID_ATTR_PARA_FORBIDDEN_RULES, false, true }, // EE_PARA_FORBIDDENRULES
+ { SID_ATTR_PARA_SCRIPTSPACE, false, true }, // EE_PARA_ASIANCJKSPACING
+ { SID_ATTR_NUMBERING_RULE, false, true }, // EE_PARA_NUMBULL
+ { 0, false, true }, // EE_PARA_HYPHENATE
+ { 0, false, true }, // EE_PARA_HYPHENATE_NO_CAPS
+ { 0, false, true }, // EE_PARA_HYPHENATE_NO_LAST_WORD
+ { 0, false, true }, // EE_PARA_BULLETSTATE
+ { 0, false, true }, // EE_PARA_OUTLLRSPACE
+ { SID_ATTR_PARA_OUTLLEVEL, false, true }, // EE_PARA_OUTLLEVEL
+ { SID_ATTR_PARA_BULLET, false, true }, // EE_PARA_BULLET
+ { SID_ATTR_LRSPACE, false, true }, // EE_PARA_LRSPACE
+ { SID_ATTR_ULSPACE, false, true }, // EE_PARA_ULSPACE
+ { SID_ATTR_PARA_LINESPACE, false, true }, // EE_PARA_SBL
+ { SID_ATTR_PARA_ADJUST, false, true }, // EE_PARA_JUST
+ { SID_ATTR_TABSTOP, false, true }, // EE_PARA_TABS
+ { SID_ATTR_ALIGN_HOR_JUSTIFY_METHOD, false, true }, // EE_PARA_JUST_METHOD
+ { SID_ATTR_ALIGN_VER_JUSTIFY, false, true }, // EE_PARA_VER_JUST
+ { SID_ATTR_CHAR_COLOR, true, true }, // EE_CHAR_COLOR
+ { SID_ATTR_CHAR_FONT, true, true }, // EE_CHAR_FONTINFO
+ { SID_ATTR_CHAR_FONTHEIGHT, false, true }, // EE_CHAR_FONTHEIGHT
+ { SID_ATTR_CHAR_SCALEWIDTH, false, true }, // EE_CHAR_FONTWIDTH
+ { SID_ATTR_CHAR_WEIGHT, false, true }, // EE_CHAR_WEIGHT
+ { SID_ATTR_CHAR_UNDERLINE, false, true }, // EE_CHAR_UNDERLINE
+ { SID_ATTR_CHAR_STRIKEOUT, false, true }, // EE_CHAR_STRIKEOUT
+ { SID_ATTR_CHAR_POSTURE, false, true }, // EE_CHAR_ITALIC
+ { SID_ATTR_CHAR_CONTOUR, false, true }, // EE_CHAR_OUTLINE
+ { SID_ATTR_CHAR_SHADOWED, false, true }, // EE_CHAR_SHADOW
+ { SID_ATTR_CHAR_ESCAPEMENT, false, true }, // EE_CHAR_ESCAPEMENT
+ { SID_ATTR_CHAR_AUTOKERN, false, true }, // EE_CHAR_PAIRKERNING
+ { SID_ATTR_CHAR_KERNING, false, true }, // EE_CHAR_KERNING
+ { SID_ATTR_CHAR_WORDLINEMODE, false, true }, // EE_CHAR_WLM
+ { SID_ATTR_CHAR_LANGUAGE, false, true }, // EE_CHAR_LANGUAGE
+ { SID_ATTR_CHAR_CJK_LANGUAGE, false, true }, // EE_CHAR_LANGUAGE_CJK
+ { SID_ATTR_CHAR_CTL_LANGUAGE, false, true }, // EE_CHAR_LANGUAGE_CTL
+ { SID_ATTR_CHAR_CJK_FONT, true, true }, // EE_CHAR_FONTINFO_CJK
+ { SID_ATTR_CHAR_CTL_FONT, true, true }, // EE_CHAR_FONTINFO_CTL
+ { SID_ATTR_CHAR_CJK_FONTHEIGHT, false, true }, // EE_CHAR_FONTHEIGHT_CJK
+ { SID_ATTR_CHAR_CTL_FONTHEIGHT, false, true }, // EE_CHAR_FONTHEIGHT_CTL
+ { SID_ATTR_CHAR_CJK_WEIGHT, false, true }, // EE_CHAR_WEIGHT_CJK
+ { SID_ATTR_CHAR_CTL_WEIGHT, false, true }, // EE_CHAR_WEIGHT_CTL
+ { SID_ATTR_CHAR_CJK_POSTURE, false, true }, // EE_CHAR_ITALIC_CJK
+ { SID_ATTR_CHAR_CTL_POSTURE, false, true }, // EE_CHAR_ITALIC_CTL
+ { SID_ATTR_CHAR_EMPHASISMARK, false, true }, // EE_CHAR_EMPHASISMARK
+ { SID_ATTR_CHAR_RELIEF, false, true }, // EE_CHAR_RELIEF
+ { 0, false, true }, // EE_CHAR_RUBI_DUMMY
+ { 0, true, true }, // EE_CHAR_XMLATTRIBS
+ { SID_ATTR_CHAR_OVERLINE, false, true }, // EE_CHAR_OVERLINE
+ { SID_ATTR_CHAR_CASEMAP, false, true }, // EE_CHAR_CASEMAP
+ { SID_ATTR_CHAR_GRABBAG, false, true }, // EE_CHAR_GRABBAG
+ { SID_ATTR_CHAR_BACK_COLOR, false, true }, // EE_CHAR_BKGCOLOR
+ { 0, false, true }, // EE_FEATURE_TAB
+ { 0, false, true }, // EE_FEATURE_LINEBR
+ { SID_ATTR_CHAR_CHARSETCOLOR, false, true }, // EE_FEATURE_NOTCONV
+ { SID_FIELD, true, true }, // EE_FEATURE_FIELD
+};
+
+EditCharAttrib* MakeCharAttrib( SfxItemPool& rPool, const SfxPoolItem& rAttr, sal_Int32 nS, sal_Int32 nE )
+{
+ // Create a new attribute in the pool
+ switch( rAttr.Which() )
+ {
+ case EE_CHAR_LANGUAGE:
+ case EE_CHAR_LANGUAGE_CJK:
+ case EE_CHAR_LANGUAGE_CTL:
+ {
+ return new EditCharAttribLanguage(rPool, rAttr, nS, nE);
+ }
+ break;
+ case EE_CHAR_COLOR:
+ {
+ return new EditCharAttribColor(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_FONTINFO:
+ case EE_CHAR_FONTINFO_CJK:
+ case EE_CHAR_FONTINFO_CTL:
+ {
+ return new EditCharAttribFont(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_FONTHEIGHT:
+ case EE_CHAR_FONTHEIGHT_CJK:
+ case EE_CHAR_FONTHEIGHT_CTL:
+ {
+ return new EditCharAttribFontHeight(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_FONTWIDTH:
+ {
+ return new EditCharAttribFontWidth(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_WEIGHT:
+ case EE_CHAR_WEIGHT_CJK:
+ case EE_CHAR_WEIGHT_CTL:
+ {
+ return new EditCharAttribWeight(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_UNDERLINE:
+ {
+ return new EditCharAttribUnderline(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_OVERLINE:
+ {
+ return new EditCharAttribOverline(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_EMPHASISMARK:
+ {
+ return new EditCharAttribEmphasisMark(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_RELIEF:
+ {
+ return new EditCharAttribRelief(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_STRIKEOUT:
+ {
+ return new EditCharAttribStrikeout(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_ITALIC:
+ case EE_CHAR_ITALIC_CJK:
+ case EE_CHAR_ITALIC_CTL:
+ {
+ return new EditCharAttribItalic(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_OUTLINE:
+ {
+ return new EditCharAttribOutline(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_SHADOW:
+ {
+ return new EditCharAttribShadow(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_ESCAPEMENT:
+ {
+ return new EditCharAttribEscapement(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_PAIRKERNING:
+ {
+ return new EditCharAttribPairKerning(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_KERNING:
+ {
+ return new EditCharAttribKerning(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_WLM:
+ {
+ return new EditCharAttribWordLineMode(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_XMLATTRIBS:
+ {
+ return new EditCharAttrib(rPool, rAttr, nS, nE); // Attribute is only for holding XML information...
+ }
+ break;
+ case EE_CHAR_CASEMAP:
+ {
+ return new EditCharAttribCaseMap(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_CHAR_GRABBAG:
+ {
+ return new EditCharAttribGrabBag(rPool, rAttr, nS, nE );
+ }
+ break;
+ case EE_FEATURE_TAB:
+ {
+ return new EditCharAttribTab(rPool, rAttr, nS );
+ }
+ break;
+ case EE_FEATURE_LINEBR:
+ {
+ return new EditCharAttribLineBreak(rPool, rAttr, nS );
+ }
+ break;
+ case EE_FEATURE_FIELD:
+ {
+ return new EditCharAttribField(rPool, rAttr, nS );
+ }
+ break;
+ case EE_CHAR_BKGCOLOR:
+ {
+ return new EditCharAttribBackgroundColor(rPool, rAttr, nS, nE );
+ }
+ break;
+ default:
+ break;
+ }
+
+ OSL_FAIL( "Invalid Attribute!" );
+ return nullptr;
+}
+
+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)
+{
+}
+
+ExtraPortionInfo::~ExtraPortionInfo()
+{
+}
+
+void ExtraPortionInfo::SaveOrgDXArray( const sal_Int32* pDXArray, sal_Int32 nLen )
+{
+ if (pDXArray)
+ {
+ pOrgDXArray.reset(new sal_Int32[nLen]);
+ memcpy( pOrgDXArray.get(), pDXArray, nLen * sizeof(sal_Int32) );
+ }
+ 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 && o3tl::make_unsigned(nPos) < maPortions.size() ? maPortions[nPos].get() : nullptr;
+}
+
+const ParaPortion* ParaPortionList::operator [](sal_Int32 nPos) const
+{
+ return 0 <= nPos && o3tl::make_unsigned(nPos) < maPortions.size() ? maPortions[nPos].get() : nullptr;
+}
+
+std::unique_ptr<ParaPortion> ParaPortionList::Release(sal_Int32 nPos)
+{
+ if (nPos < 0 || maPortions.size() <= o3tl::make_unsigned(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 || maPortions.size() <= o3tl::make_unsigned(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 || maPortions.size() < o3tl::make_unsigned(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();
+}
+
+tools::Long ParaPortionList::GetYOffset(const ParaPortion* pPPortion) const
+{
+ tools::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(tools::Long nYOffset) const
+{
+ tools::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 && o3tl::make_unsigned(nPos) < maPortions.size() ? maPortions[nPos].get() : nullptr;
+}
+
+ParaPortion* ParaPortionList::SafeGetObject(sal_Int32 nPos)
+{
+ return 0 <= nPos && o3tl::make_unsigned(nPos) < 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( SfxItemSet aParaAttribs ) :
+ aPrevParaAttribs(std::move( aParaAttribs))
+{
+}
+
+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));
+
+ if (sal_Int32 nDefTabDistance = rItem.GetDefaultDistance())
+ {
+ pNewItem->SetDefaultDistance(
+ OutputDevice::LogicToLogic(nDefTabDistance, eSourceUnit, eDestUnit));
+ }
+
+ 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( sal_Int32 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 )
+{
+ 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;
+
+ std::size_t 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 ( std::size_t 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;
+ rAttribs.erase(rAttribs.begin()+nAttr);
+ }
+ else
+ {
+ ++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 )
+{
+ 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;
+
+ std::size_t 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;
+ rAttribs.erase(rAttribs.begin()+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();
+
+ std::size_t nAttr = 0;
+ CharAttribList::AttribsType& rPrevAttribs = pPrevNode->GetCharAttribs().GetAttribs();
+ EditCharAttrib* pAttrib = GetAttrib(rPrevAttribs, nAttr);
+ while ( pAttrib )
+ {
+ if ( pAttrib->GetEnd() < nCut )
+ {
+ // remain unchanged...
+ nAttr++;
+ }
+ 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 );
+ }
+ nAttr++;
+ }
+ 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;
+ nAttr++;
+ }
+ 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 );
+ }
+ 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
+
+ std::size_t 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:
+ std::size_t nTmpAttr = 0;
+ EditCharAttrib* pTmpAttrib = GetAttrib( aCharAttribList.GetAttribs(), nTmpAttr );
+ while ( !bMelted && pTmpAttrib )
+ {
+ ++nTmpAttr;
+ 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())
+ {
+ --nTmpAttr; // to cancel earlier increment...
+ aCharAttribList.Remove(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_Int32 ContentNode::GetExpandedLen() const
+{
+ sal_Int32 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( GetString().subView(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;
+
+ if (rAttr.Which() == EE_FEATURE_FIELD)
+ {
+ sal_Int32 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, rtl::OUStringChar(c));
+}
+
+void ContentNode::Insert(std::u16string_view rStr, sal_Int32 nPos)
+{
+ maString = maString.replaceAt(nPos, 0, rStr);
+}
+
+void ContentNode::Append(std::u16string_view 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, u"");
+}
+
+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
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ContentNode"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("maString"), BAD_CAST(maString.toUtf8().getStr()));
+ aContentAttribs.dumpAsXml(pWriter);
+ aCharAttribList.dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+void ContentNode::checkAndDeleteEmptyAttribs() const
+{
+ // Delete empty attributes, but only if paragraph is not empty!
+ if (GetCharAttribs().HasEmptyAttribs() && Len())
+ {
+ const_cast<ContentNode*>(this)->GetCharAttribs().DeleteEmptyAttribs();
+ }
+}
+
+ContentAttribs::ContentAttribs( SfxItemPool& rPool )
+: pStyle(nullptr)
+, aAttribSet( rPool )
+{
+}
+
+
+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;
+ }
+
+ // if there's a default tab size defined for this item use that instead
+ if (rTabs.GetDefaultDistance())
+ nDefTab = rTabs.GetDefaultDistance();
+
+ // 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
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ContentAttribs"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("style"), "%s", pStyle->GetName().toUtf8().getStr());
+ aAttribSet.dumpAsXml(pWriter);
+ (void)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),
+ bModified(false),
+ bDisableAttributeExpanding(false)
+{
+ // Don't create an empty node, Clear() will be called in EditEngine-CTOR
+};
+
+EditDoc::~EditDoc()
+{
+ maContents.clear();
+}
+
+void CreateFont( SvxFont& rFont, const SfxItemSet& rSet, bool bSearchInParent, SvtScriptType nScriptType )
+{
+ vcl::Font aPrevFont( rFont );
+ rFont.SetAlignment( ALIGN_BASELINE );
+
+ 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 ) )
+ {
+ auto& aColor = rSet.Get( EE_CHAR_BKGCOLOR ).GetValue();
+ rFont.SetTransparent(aColor.IsTransparent());
+ rFont.SetFillColor(aColor);
+ }
+ 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 )
+{
+ SfxItemSetFixed<EE_PARA_START, EE_CHAR_END> aTmpSet( GetItemPool() );
+ CreateFont(maDefFont, aTmpSet);
+ maDefFont.SetVertical( IsEffectivelyVertical() );
+ maDefFont.SetOrientation( Degree10(IsEffectivelyVertical() ? (IsTopToBottom() ? 2700 : 900) : 0) );
+
+ for ( sal_Int32 nNode = 0; nNode < Count(); nNode++ )
+ {
+ ContentNode* pNode = GetObject( nNode );
+ pNode->GetCharAttribs().GetDefFont() = maDefFont;
+ if ( bUseStyles )
+ pNode->CreateDefFont();
+ }
+}
+
+bool EditDoc::IsEffectivelyVertical() 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::GetVertical() 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 && o3tl::make_unsigned(nPos) < maContents.size() ? maContents[nPos].get() : nullptr;
+}
+
+ContentNode* EditDoc::GetObject(sal_Int32 nPos)
+{
+ return 0 <= nPos && o3tl::make_unsigned(nPos) < 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 || o3tl::make_unsigned(nPos) >= 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 || o3tl::make_unsigned(nPos) >= maContents.size())
+ {
+ SAL_WARN( "editeng", "EditDoc::Release - out of bounds pos " << nPos);
+ return;
+ }
+ // coverity[leaked_storage] - this is on purpose, ownership should be transferred to undo/redo
+ (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_Int32 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_Int32 EditDoc::GetTextLen() const
+{
+ sal_Int32 nLen = 0;
+ for ( sal_Int32 nNode = 0; nNode < Count(); nNode++ )
+ {
+ const ContentNode* pNode = GetObject( nNode );
+ nLen += pNode->GetExpandedLen();
+ }
+ return nLen;
+}
+
+EditPaM EditDoc::Clear()
+{
+ maContents.clear();
+
+ ContentNode* pNode = new ContentNode( GetItemPool() );
+ Insert(0, pNode);
+
+ CreateDefFont(false);
+
+ SetModified(false);
+
+ return EditPaM( pNode, 0 );
+}
+
+namespace
+{
+struct ClearSpellErrorsHandler
+{
+ void operator() (std::unique_ptr<ContentNode> const & rNode)
+ {
+ rNode->DestroyWrongList();
+ }
+};
+}
+
+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() );
+
+ maContents.clear();
+
+ 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, std::u16string_view rStr )
+{
+ DBG_ASSERT( rStr.find( 0x0A ) == std::u16string_view::npos, "EditDoc::InsertText: Newlines prohibited in paragraph!" );
+ DBG_ASSERT( rStr.find( 0x0D ) == std::u16string_view::npos, "EditDoc::InsertText: Newlines prohibited in paragraph!" );
+ DBG_ASSERT( rStr.find( '\t' ) == std::u16string_view::npos, "EditDoc::InsertText: Newlines prohibited in paragraph!" );
+ assert(aPaM.GetNode());
+
+ aPaM.GetNode()->Insert( rStr, aPaM.GetIndex() );
+ aPaM.GetNode()->ExpandAttribs( aPaM.GetIndex(), rStr.size() );
+ aPaM.SetIndex( aPaM.GetIndex() + rStr.size() );
+
+ 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, std::move(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( rtl::OUStringChar(CH_FEATURE), aPaM.GetIndex() );
+ aPaM.GetNode()->ExpandAttribs( aPaM.GetIndex(), 1 );
+
+ // 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.
+ 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 );
+
+ 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();
+ 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;
+ bool bNeedsSorting = 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 ...
+ std::size_t 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 )
+ {
+ bNeedsSorting = true;
+ 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 )
+ {
+ bNeedsSorting = true;
+ 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 ...
+ {
+ bNeedsSorting = true;
+ 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?!" );
+ rAttribs.erase(rAttribs.begin()+nAttr);
+ }
+ else
+ {
+ nAttr++;
+ }
+ pAttr = GetAttrib(rAttribs, nAttr);
+ }
+
+ if ( bChanged )
+ {
+ // char attributes need to be sorted by start again
+ if (bNeedsSorting)
+ 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!" );
+
+ std::size_t 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);
+ (void)xmlTextWriterSetIndentString(pWriter, BAD_CAST(" "));
+ (void)xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr);
+ bOwns = true;
+ }
+
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("EditDoc"));
+ for (auto const & i : maContents)
+ {
+ i->dumpAsXml(pWriter);
+ }
+ (void)xmlTextWriterEndElement(pWriter);
+
+ if (bOwns)
+ {
+ (void)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()
+: 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
+ // (InsertTextObject!) 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()
+{
+#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();
+ 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();
+}
+
+void CharAttribList::DeleteEmptyAttribs()
+{
+ std::erase_if(aAttribs, [](const std::unique_ptr<EditCharAttrib>& aAttrib) { return aAttrib->IsEmpty(); } );
+ 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
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("CharAttribList"));
+ for (auto const & i : aAttribs) {
+ i->dumpAsXml(pWriter);
+ }
+ (void)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();
+ SetSecondaryPool(nullptr);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/editeng.cxx b/editeng/source/editeng/editeng.cxx
new file mode 100644
index 0000000000..4dbb93ce2c
--- /dev/null
+++ b/editeng/source/editeng/editeng.cxx
@@ -0,0 +1,2938 @@
+/* -*- 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 <memory>
+#include <utility>
+
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <config_global.h>
+#include <o3tl/safeint.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weld.hxx>
+#include <vcl/window.hxx>
+
+#include <tools/stream.hxx>
+
+#include <editeng/svxfont.hxx>
+#include "impedit.hxx"
+#include <editeng/editeng.hxx>
+#include <editeng/editview.hxx>
+#include <editeng/editstat.hxx>
+#include <editeng/eerdll.hxx>
+#include <editeng/editrids.hrc>
+#include <editeng/flditem.hxx>
+#include <editeng/txtrange.hxx>
+#include <editeng/cmapitem.hxx>
+
+#include <editeng/autokernitem.hxx>
+#include <editeng/contouritem.hxx>
+#include <editeng/colritem.hxx>
+#include <editeng/crossedoutitem.hxx>
+#include <editeng/escapementitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/kernitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <editeng/postitem.hxx>
+#include <editeng/shdditem.hxx>
+#include <editeng/udlnitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <editeng/wrlmitem.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/emphasismarkitem.hxx>
+#include <editeng/charscaleitem.hxx>
+#include <editeng/charreliefitem.hxx>
+
+#include <sot/exchange.hxx>
+#include <sot/formats.hxx>
+
+#include <editeng/numitem.hxx>
+#include <rtl/strbuf.hxx>
+#include <sal/log.hxx>
+#include <vcl/help.hxx>
+#include <vcl/transfer.hxx>
+#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
+#include <com/sun/star/frame/Desktop.hpp>
+
+#if OSL_DEBUG_LEVEL > 1
+#include <editeng/frmdiritem.hxx>
+#endif
+#include <basegfx/polygon/b2dpolygon.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::linguistic2;
+
+
+#if (OSL_DEBUG_LEVEL > 1) || defined ( DBG_UTIL )
+static bool bDebugPaint = false;
+#endif
+
+
+static rtl::Reference<SfxItemPool> pGlobalPool;
+
+EditEngine::EditEngine( SfxItemPool* pItemPool )
+{
+ pImpEditEngine.reset( new ImpEditEngine( this, pItemPool ) );
+}
+
+EditEngine::~EditEngine()
+{
+}
+
+void EditEngine::EnableUndo( bool bEnable )
+{
+ pImpEditEngine->EnableUndo( bEnable );
+}
+
+bool EditEngine::IsUndoEnabled() const
+{
+ return pImpEditEngine->IsUndoEnabled();
+}
+
+bool EditEngine::IsInUndo() const
+{
+ return pImpEditEngine->IsInUndo();
+}
+
+EditUndoManager& EditEngine::GetUndoManager()
+{
+ return pImpEditEngine->GetUndoManager();
+}
+
+EditUndoManager* EditEngine::SetUndoManager(EditUndoManager* pNew)
+{
+ return pImpEditEngine->SetUndoManager(pNew);
+}
+
+void EditEngine::UndoActionStart( sal_uInt16 nId )
+{
+ DBG_ASSERT( !pImpEditEngine->IsInUndo(), "Calling UndoActionStart in Undomode!" );
+ if ( !pImpEditEngine->IsInUndo() )
+ pImpEditEngine->UndoActionStart( nId );
+}
+
+void EditEngine::UndoActionStart(sal_uInt16 nId, const ESelection& rSel)
+{
+ pImpEditEngine->UndoActionStart(nId, rSel);
+}
+
+void EditEngine::UndoActionEnd()
+{
+ DBG_ASSERT( !pImpEditEngine->IsInUndo(), "Calling UndoActionEnd in Undomode!" );
+ if ( !pImpEditEngine->IsInUndo() )
+ pImpEditEngine->UndoActionEnd();
+}
+
+bool EditEngine::HasTriedMergeOnLastAddUndo() const
+{
+ return pImpEditEngine->mbLastTryMerge;
+}
+
+void EditEngine::SetRefDevice( OutputDevice* pRefDev )
+{
+ pImpEditEngine->SetRefDevice( pRefDev );
+}
+
+OutputDevice* EditEngine::GetRefDevice() const
+{
+ return pImpEditEngine->GetRefDevice();
+}
+
+void EditEngine::SetRefMapMode( const MapMode& rMapMode )
+{
+ pImpEditEngine->SetRefMapMode( rMapMode );
+}
+
+MapMode const & EditEngine::GetRefMapMode() const
+{
+ return pImpEditEngine->GetRefMapMode();
+}
+
+void EditEngine::SetBackgroundColor( const Color& rColor )
+{
+ pImpEditEngine->SetBackgroundColor( rColor );
+}
+
+Color const & EditEngine::GetBackgroundColor() const
+{
+ return pImpEditEngine->GetBackgroundColor();
+}
+
+Color EditEngine::GetAutoColor() const
+{
+ return pImpEditEngine->GetAutoColor();
+}
+
+void EditEngine::EnableAutoColor( bool b )
+{
+ pImpEditEngine->EnableAutoColor( b );
+}
+
+void EditEngine::ForceAutoColor( bool b )
+{
+ pImpEditEngine->ForceAutoColor( b );
+}
+
+bool EditEngine::IsForceAutoColor() const
+{
+ return pImpEditEngine->IsForceAutoColor();
+}
+
+const SfxItemSet& EditEngine::GetEmptyItemSet() const
+{
+ return pImpEditEngine->GetEmptyItemSet();
+}
+
+void EditEngine::Draw( OutputDevice& rOutDev, const tools::Rectangle& rOutRect )
+{
+ Draw( rOutDev, rOutRect, Point( 0, 0 ) );
+}
+
+void EditEngine::Draw( OutputDevice& rOutDev, const Point& rStartPos, Degree10 nOrientation )
+{
+ // Create with 2 points, as with positive points it will end up with
+ // LONGMAX as Size, Bottom and Right in the range > LONGMAX.
+ tools::Rectangle aBigRect( -0x3FFFFFFF, -0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF );
+ if( rOutDev.GetConnectMetaFile() )
+ rOutDev.Push();
+ Point aStartPos( rStartPos );
+ if ( IsEffectivelyVertical() )
+ {
+ aStartPos.AdjustX(GetPaperSize().Width() );
+ rStartPos.RotateAround(aStartPos, nOrientation);
+ }
+ pImpEditEngine->Paint(rOutDev, aBigRect, aStartPos, false, nOrientation);
+ if( rOutDev.GetConnectMetaFile() )
+ rOutDev.Pop();
+}
+
+void EditEngine::Draw( OutputDevice& rOutDev, const tools::Rectangle& rOutRect, const Point& rStartDocPos )
+{
+ Draw( rOutDev, rOutRect, rStartDocPos, true );
+}
+
+void EditEngine::Draw( OutputDevice& rOutDev, const tools::Rectangle& rOutRect, const Point& rStartDocPos, bool bClip )
+{
+#if defined( DBG_UTIL ) || (OSL_DEBUG_LEVEL > 1)
+ if ( bDebugPaint )
+ DumpData(this, false);
+#endif
+
+ // Align to the pixel boundary, so that it becomes exactly the same
+ // as Paint ()
+ tools::Rectangle aOutRect( rOutDev.LogicToPixel( rOutRect ) );
+ aOutRect = rOutDev.PixelToLogic( aOutRect );
+
+ Point aStartPos;
+ if ( !IsEffectivelyVertical() )
+ {
+ aStartPos.setX( aOutRect.Left() - rStartDocPos.X() );
+ aStartPos.setY( aOutRect.Top() - rStartDocPos.Y() );
+ }
+ else
+ {
+ aStartPos.setX( aOutRect.Right() + rStartDocPos.Y() );
+ aStartPos.setY( aOutRect.Top() - rStartDocPos.X() );
+ }
+
+ bool bClipRegion = rOutDev.IsClipRegion();
+ bool bMetafile = rOutDev.GetConnectMetaFile();
+ vcl::Region aOldRegion = rOutDev.GetClipRegion();
+
+ // If one existed => intersection!
+ // Use Push/pop for creating the Meta file
+ if ( bMetafile )
+ rOutDev.Push();
+
+ // Always use the Intersect method, it is a must for Metafile!
+ if ( bClip )
+ {
+ // Clip only if necessary...
+ if ( rStartDocPos.X() || rStartDocPos.Y() ||
+ ( rOutRect.GetHeight() < static_cast<tools::Long>(GetTextHeight()) ) ||
+ ( rOutRect.GetWidth() < static_cast<tools::Long>(CalcTextWidth()) ) )
+ {
+ // Some printer drivers cause problems if characters graze the
+ // ClipRegion, therefore rather add a pixel more ...
+ tools::Rectangle aClipRect( aOutRect );
+ if ( rOutDev.GetOutDevType() == OUTDEV_PRINTER )
+ {
+ Size aPixSz( 1, 0 );
+ aPixSz = rOutDev.PixelToLogic( aPixSz );
+ aClipRect.AdjustRight(aPixSz.Width() );
+ aClipRect.AdjustBottom(aPixSz.Width() );
+ }
+ rOutDev.IntersectClipRegion( aClipRect );
+ }
+ }
+
+ pImpEditEngine->Paint( rOutDev, aOutRect, aStartPos );
+
+ if ( bMetafile )
+ rOutDev.Pop();
+ else if ( bClipRegion )
+ rOutDev.SetClipRegion( aOldRegion );
+ else
+ rOutDev.SetClipRegion();
+}
+
+void EditEngine::InsertView(EditView* pEditView, size_t nIndex)
+{
+
+ if ( nIndex > pImpEditEngine->GetEditViews().size() )
+ nIndex = pImpEditEngine->GetEditViews().size();
+
+ ImpEditEngine::ViewsType& rViews = pImpEditEngine->GetEditViews();
+ rViews.insert(rViews.begin()+nIndex, pEditView);
+
+ EditSelection aStartSel = pImpEditEngine->GetEditDoc().GetStartPaM();
+ pEditView->pImpEditView->SetEditSelection( aStartSel );
+ if ( !pImpEditEngine->GetActiveView() )
+ pImpEditEngine->SetActiveView( pEditView );
+
+ pEditView->pImpEditView->AddDragAndDropListeners();
+}
+
+EditView* EditEngine::RemoveView( EditView* pView )
+{
+
+ pView->HideCursor();
+ EditView* pRemoved = nullptr;
+ ImpEditEngine::ViewsType& rViews = pImpEditEngine->GetEditViews();
+ ImpEditEngine::ViewsType::iterator it = std::find(rViews.begin(), rViews.end(), pView);
+
+ DBG_ASSERT( it != rViews.end(), "RemoveView with invalid index" );
+ if (it != rViews.end())
+ {
+ pRemoved = *it;
+ rViews.erase(it);
+ if ( pImpEditEngine->GetActiveView() == pView )
+ {
+ pImpEditEngine->SetActiveView( nullptr );
+ pImpEditEngine->GetSelEngine().SetCurView( nullptr );
+ }
+ pView->pImpEditView->RemoveDragAndDropListeners();
+
+ }
+ return pRemoved;
+}
+
+void EditEngine::RemoveView(size_t nIndex)
+{
+ ImpEditEngine::ViewsType& rViews = pImpEditEngine->GetEditViews();
+ if (nIndex >= rViews.size())
+ return;
+
+ EditView* pView = rViews[nIndex];
+ if ( pView )
+ RemoveView( pView );
+}
+
+EditView* EditEngine::GetView(size_t nIndex) const
+{
+ return pImpEditEngine->GetEditViews()[nIndex];
+}
+
+size_t EditEngine::GetViewCount() const
+{
+ return pImpEditEngine->GetEditViews().size();
+}
+
+bool EditEngine::HasView( EditView* pView ) const
+{
+ ImpEditEngine::ViewsType& rViews = pImpEditEngine->GetEditViews();
+ return std::find(rViews.begin(), rViews.end(), pView) != rViews.end();
+}
+
+EditView* EditEngine::GetActiveView() const
+{
+ return pImpEditEngine->GetActiveView();
+}
+
+void EditEngine::SetActiveView(EditView* pView)
+{
+ pImpEditEngine->SetActiveView(pView);
+}
+
+void EditEngine::SetDefTab( sal_uInt16 nDefTab )
+{
+ pImpEditEngine->GetEditDoc().SetDefTab( nDefTab );
+ if ( pImpEditEngine->IsFormatted() )
+ {
+ pImpEditEngine->FormatFullDoc();
+ pImpEditEngine->UpdateViews();
+ }
+}
+
+void EditEngine::SetPaperSize( const Size& rNewSize )
+{
+
+ Size aOldSize( pImpEditEngine->GetPaperSize() );
+ pImpEditEngine->SetValidPaperSize( rNewSize );
+ Size aNewSize( pImpEditEngine->GetPaperSize() );
+
+ bool bAutoPageSize = pImpEditEngine->GetStatus().AutoPageSize();
+ if ( !(bAutoPageSize || ( aNewSize.Width() != aOldSize.Width() )) )
+ return;
+
+ for (EditView* pView : pImpEditEngine->aEditViews)
+ {
+ if ( bAutoPageSize )
+ pView->pImpEditView->RecalcOutputArea();
+ else if ( pView->pImpEditView->DoAutoSize() )
+ {
+ pView->pImpEditView->ResetOutputArea( tools::Rectangle(
+ pView->pImpEditView->GetOutputArea().TopLeft(), aNewSize ) );
+ }
+ }
+
+ if ( bAutoPageSize || pImpEditEngine->IsFormatted() )
+ {
+ // Changing the width has no effect for AutoPageSize, as this is
+ // determined by the text width.
+ // Optimization first after Vobis delivery was enabled ...
+ pImpEditEngine->FormatFullDoc();
+
+ pImpEditEngine->UpdateViews( pImpEditEngine->GetActiveView() );
+
+ if ( pImpEditEngine->IsUpdateLayout() && pImpEditEngine->GetActiveView() )
+ pImpEditEngine->pActiveView->ShowCursor( false, false );
+ }
+}
+
+const Size& EditEngine::GetPaperSize() const
+{
+ return pImpEditEngine->GetPaperSize();
+}
+
+void EditEngine::SetVertical(bool bVertical)
+{
+ pImpEditEngine->SetVertical(bVertical);
+}
+
+void EditEngine::SetRotation(TextRotation nRotation)
+{
+ pImpEditEngine->SetRotation(nRotation);
+}
+
+TextRotation EditEngine::GetRotation() const
+{
+ return pImpEditEngine->GetRotation();
+}
+
+bool EditEngine::IsEffectivelyVertical() const
+{
+ return pImpEditEngine->IsEffectivelyVertical();
+}
+
+bool EditEngine::IsTopToBottom() const
+{
+ return pImpEditEngine->IsTopToBottom();
+}
+
+bool EditEngine::GetVertical() const
+{
+ return pImpEditEngine->GetVertical();
+}
+
+void EditEngine::SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing)
+{
+ pImpEditEngine->SetTextColumns(nColumns, nSpacing);
+}
+
+void EditEngine::SetFixedCellHeight( bool bUseFixedCellHeight )
+{
+ pImpEditEngine->SetFixedCellHeight( bUseFixedCellHeight );
+}
+
+void EditEngine::SetDefaultHorizontalTextDirection( EEHorizontalTextDirection eHTextDir )
+{
+ pImpEditEngine->SetDefaultHorizontalTextDirection( eHTextDir );
+}
+
+EEHorizontalTextDirection EditEngine::GetDefaultHorizontalTextDirection() const
+{
+ return pImpEditEngine->GetDefaultHorizontalTextDirection();
+}
+
+SvtScriptType EditEngine::GetScriptType( const ESelection& rSelection ) const
+{
+ EditSelection aSel( pImpEditEngine->CreateSel( rSelection ) );
+ return pImpEditEngine->GetItemScriptType( aSel );
+}
+
+editeng::LanguageSpan EditEngine::GetLanguage(const EditPaM& rPaM) const
+{
+ return pImpEditEngine->GetLanguage(rPaM);
+}
+
+editeng::LanguageSpan EditEngine::GetLanguage( sal_Int32 nPara, sal_Int32 nPos ) const
+{
+ ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara );
+ DBG_ASSERT( pNode, "GetLanguage - nPara is invalid!" );
+ return pNode ? pImpEditEngine->GetLanguage( EditPaM( pNode, nPos ) ) : editeng::LanguageSpan{};
+}
+
+
+void EditEngine::TransliterateText( const ESelection& rSelection, TransliterationFlags nTransliterationMode )
+{
+ pImpEditEngine->TransliterateText( pImpEditEngine->CreateSel( rSelection ), nTransliterationMode );
+}
+
+EditSelection EditEngine::TransliterateText(const EditSelection& rSelection, TransliterationFlags nTransliterationMode)
+{
+ return pImpEditEngine->TransliterateText(rSelection, nTransliterationMode);
+}
+
+void EditEngine::SetAsianCompressionMode( CharCompressType n )
+{
+ pImpEditEngine->SetAsianCompressionMode( n );
+}
+
+void EditEngine::SetKernAsianPunctuation( bool b )
+{
+ pImpEditEngine->SetKernAsianPunctuation( b );
+}
+
+void EditEngine::SetAddExtLeading( bool b )
+{
+ pImpEditEngine->SetAddExtLeading( b );
+}
+
+void EditEngine::SetPolygon( const basegfx::B2DPolyPolygon& rPolyPolygon )
+{
+ SetPolygon( rPolyPolygon, nullptr );
+}
+
+void EditEngine::SetPolygon(const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::B2DPolyPolygon* pLinePolyPolygon)
+{
+ bool bSimple(false);
+
+ if(pLinePolyPolygon && 1 == rPolyPolygon.count())
+ {
+ if(rPolyPolygon.getB2DPolygon(0).isClosed())
+ {
+ // open polygon
+ bSimple = true;
+ }
+ }
+
+ TextRanger* pRanger = new TextRanger( rPolyPolygon, pLinePolyPolygon, 30, 2, 2, bSimple, true );
+ pImpEditEngine->SetTextRanger( std::unique_ptr<TextRanger>(pRanger) );
+ pImpEditEngine->SetPaperSize( pRanger->GetBoundRect().GetSize() );
+}
+
+void EditEngine::ClearPolygon()
+{
+ pImpEditEngine->SetTextRanger( nullptr );
+}
+
+const Size& EditEngine::GetMinAutoPaperSize() const
+{
+ return pImpEditEngine->GetMinAutoPaperSize();
+}
+
+void EditEngine::SetMinAutoPaperSize( const Size& rSz )
+{
+ pImpEditEngine->SetMinAutoPaperSize( rSz );
+}
+
+const Size& EditEngine::GetMaxAutoPaperSize() const
+{
+ return pImpEditEngine->GetMaxAutoPaperSize();
+}
+
+void EditEngine::SetMaxAutoPaperSize( const Size& rSz )
+{
+ pImpEditEngine->SetMaxAutoPaperSize( rSz );
+}
+
+void EditEngine::SetMinColumnWrapHeight(tools::Long nVal)
+{
+ pImpEditEngine->SetMinColumnWrapHeight(nVal);
+}
+
+OUString EditEngine::GetText( LineEnd eEnd ) const
+{
+ return pImpEditEngine->GetEditDoc().GetText( eEnd );
+}
+
+OUString EditEngine::GetText( const ESelection& rESelection ) const
+{
+ EditSelection aSel( pImpEditEngine->CreateSel( rESelection ) );
+ return pImpEditEngine->GetSelected( aSel );
+}
+
+sal_Int32 EditEngine::GetTextLen() const
+{
+ return pImpEditEngine->GetEditDoc().GetTextLen();
+}
+
+sal_Int32 EditEngine::GetParagraphCount() const
+{
+ return pImpEditEngine->maEditDoc.Count();
+}
+
+sal_Int32 EditEngine::GetLineCount( sal_Int32 nParagraph ) const
+{
+ if ( !pImpEditEngine->IsFormatted() )
+ pImpEditEngine->FormatDoc();
+ return pImpEditEngine->GetLineCount( nParagraph );
+}
+
+sal_Int32 EditEngine::GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine ) const
+{
+ if ( !pImpEditEngine->IsFormatted() )
+ pImpEditEngine->FormatDoc();
+ return pImpEditEngine->GetLineLen( nParagraph, nLine );
+}
+
+void EditEngine::GetLineBoundaries( /*out*/sal_Int32& rStart, /*out*/sal_Int32& rEnd, sal_Int32 nParagraph, sal_Int32 nLine ) const
+{
+ if ( !pImpEditEngine->IsFormatted() )
+ pImpEditEngine->FormatDoc();
+ return pImpEditEngine->GetLineBoundaries( rStart, rEnd, nParagraph, nLine );
+}
+
+sal_Int32 EditEngine::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const
+{
+ if ( !pImpEditEngine->IsFormatted() )
+ pImpEditEngine->FormatDoc();
+ return pImpEditEngine->GetLineNumberAtIndex( nPara, nIndex );
+}
+
+sal_uInt32 EditEngine::GetLineHeight( sal_Int32 nParagraph )
+{
+ // If someone calls GetLineHeight() with an empty Engine.
+ if ( !pImpEditEngine->IsFormatted() )
+ pImpEditEngine->FormatDoc();
+ return pImpEditEngine->GetLineHeight( nParagraph, 0 );
+}
+
+tools::Rectangle EditEngine::GetParaBounds( sal_Int32 nPara )
+{
+ if ( !pImpEditEngine->IsFormatted() )
+ pImpEditEngine->FormatDoc();
+
+ Point aPnt = GetDocPosTopLeft( nPara );
+
+ if( IsEffectivelyVertical() )
+ {
+ sal_Int32 nTextHeight = pImpEditEngine->GetTextHeight();
+ sal_Int32 nParaWidth = pImpEditEngine->CalcParaWidth( nPara, true );
+ sal_Int32 nParaHeight = pImpEditEngine->GetParaHeight( nPara );
+
+ return tools::Rectangle( nTextHeight - aPnt.Y() - nParaHeight, 0, nTextHeight - aPnt.Y(), nParaWidth );
+ }
+ else
+ {
+ sal_Int32 nParaWidth = pImpEditEngine->CalcParaWidth( nPara, true );
+ sal_Int32 nParaHeight = pImpEditEngine->GetParaHeight( nPara );
+
+ return tools::Rectangle( 0, aPnt.Y(), nParaWidth, aPnt.Y() + nParaHeight );
+ }
+}
+
+sal_uInt32 EditEngine::GetTextHeight( sal_Int32 nParagraph ) const
+{
+ if ( !pImpEditEngine->IsFormatted() )
+ pImpEditEngine->FormatDoc();
+
+ sal_uInt32 nHeight = pImpEditEngine->GetParaHeight( nParagraph );
+ return nHeight;
+}
+
+OUString EditEngine::GetWord( sal_Int32 nPara, sal_Int32 nIndex )
+{
+ ESelection aESel( nPara, nIndex, nPara, nIndex );
+ EditSelection aSel( pImpEditEngine->CreateSel( aESel ) );
+ aSel = pImpEditEngine->SelectWord( aSel );
+ return pImpEditEngine->GetSelected( aSel );
+}
+
+ESelection EditEngine::GetWord( const ESelection& rSelection, sal_uInt16 nWordType ) const
+{
+ // ImpEditEngine-Iteration-Methods should be const!
+ EditEngine* pE = const_cast<EditEngine*>(this);
+
+ EditSelection aSel( pE->pImpEditEngine->CreateSel( rSelection ) );
+ aSel = pE->pImpEditEngine->SelectWord( aSel, nWordType );
+ return pE->pImpEditEngine->CreateESel( aSel );
+}
+
+void EditEngine::CheckIdleFormatter()
+{
+ pImpEditEngine->CheckIdleFormatter();
+}
+
+bool EditEngine::IsIdleFormatterActive() const
+{
+ return pImpEditEngine->aIdleFormatter.IsActive();
+}
+
+ParaPortion* EditEngine::FindParaPortion(ContentNode const * pNode)
+{
+ return pImpEditEngine->FindParaPortion(pNode);
+}
+
+const ParaPortion* EditEngine::FindParaPortion(ContentNode const * pNode) const
+{
+ return pImpEditEngine->FindParaPortion(pNode);
+}
+
+const ParaPortion* EditEngine::GetPrevVisPortion(const ParaPortion* pCurPortion) const
+{
+ return pImpEditEngine->GetPrevVisPortion(pCurPortion);
+}
+
+SvtScriptType EditEngine::GetScriptType(const EditSelection& rSel) const
+{
+ return pImpEditEngine->GetItemScriptType(rSel);
+}
+
+void EditEngine::RemoveParaPortion(sal_Int32 nNode)
+{
+ pImpEditEngine->GetParaPortions().Remove(nNode);
+}
+
+void EditEngine::SetCallParaInsertedOrDeleted(bool b)
+{
+ pImpEditEngine->SetCallParaInsertedOrDeleted(b);
+}
+
+bool EditEngine::IsCallParaInsertedOrDeleted() const
+{
+ return pImpEditEngine->IsCallParaInsertedOrDeleted();
+}
+
+void EditEngine::AppendDeletedNodeInfo(DeletedNodeInfo* pInfo)
+{
+ pImpEditEngine->aDeletedNodes.push_back(std::unique_ptr<DeletedNodeInfo>(pInfo));
+}
+
+void EditEngine::UpdateSelections()
+{
+ pImpEditEngine->UpdateSelections();
+}
+
+void EditEngine::InsertContent(ContentNode* pNode, sal_Int32 nPos)
+{
+ pImpEditEngine->InsertContent(pNode, nPos);
+}
+
+EditPaM EditEngine::SplitContent(sal_Int32 nNode, sal_Int32 nSepPos)
+{
+ return pImpEditEngine->SplitContent(nNode, nSepPos);
+}
+
+EditPaM EditEngine::ConnectContents(sal_Int32 nLeftNode, bool bBackward)
+{
+ return pImpEditEngine->ConnectContents(nLeftNode, bBackward);
+}
+
+void EditEngine::InsertFeature(const EditSelection& rEditSelection, const SfxPoolItem& rItem)
+{
+ pImpEditEngine->ImpInsertFeature(rEditSelection, rItem);
+}
+
+EditSelection EditEngine::MoveParagraphs(const Range& rParagraphs, sal_Int32 nNewPos)
+{
+ return pImpEditEngine->MoveParagraphs(rParagraphs, nNewPos, nullptr);
+}
+
+void EditEngine::RemoveCharAttribs(sal_Int32 nPara, sal_uInt16 nWhich, bool bRemoveFeatures)
+{
+ pImpEditEngine->RemoveCharAttribs(nPara, nWhich, bRemoveFeatures);
+}
+
+void EditEngine::RemoveCharAttribs(const EditSelection& rSel, bool bRemoveParaAttribs, sal_uInt16 nWhich)
+{
+ const EERemoveParaAttribsMode eMode = bRemoveParaAttribs?
+ EERemoveParaAttribsMode::RemoveAll :
+ EERemoveParaAttribsMode::RemoveCharItems;
+ pImpEditEngine->RemoveCharAttribs(rSel, eMode, nWhich);
+}
+
+void EditEngine::RemoveCharAttribs(const EditSelection& rSel, EERemoveParaAttribsMode eMode, sal_uInt16 nWhich)
+{
+ pImpEditEngine->RemoveCharAttribs(rSel, eMode, nWhich);
+}
+
+EditEngine::ViewsType& EditEngine::GetEditViews()
+{
+ return pImpEditEngine->GetEditViews();
+}
+
+const EditEngine::ViewsType& EditEngine::GetEditViews() const
+{
+ return pImpEditEngine->GetEditViews();
+}
+
+void EditEngine::SetUndoMode(bool b)
+{
+ pImpEditEngine->SetUndoMode(b);
+}
+
+void EditEngine::FormatAndLayout(EditView* pCurView, bool bCalledFromUndo)
+{
+ pImpEditEngine->FormatAndLayout(pCurView, bCalledFromUndo);
+}
+
+void EditEngine::Undo(EditView* pView)
+{
+ pImpEditEngine->Undo(pView);
+}
+
+void EditEngine::Redo(EditView* pView)
+{
+ pImpEditEngine->Redo(pView);
+}
+
+uno::Reference<datatransfer::XTransferable> EditEngine::CreateTransferable(const EditSelection& rSelection)
+{
+ return pImpEditEngine->CreateTransferable(rSelection);
+}
+
+void EditEngine::ParaAttribsToCharAttribs(ContentNode* pNode)
+{
+ pImpEditEngine->ParaAttribsToCharAttribs(pNode);
+}
+
+EditPaM EditEngine::CreateEditPaM(const EPaM& rEPaM)
+{
+ return pImpEditEngine->CreateEditPaM(rEPaM);
+}
+
+EditPaM EditEngine::ConnectParagraphs(
+ ContentNode* pLeft, ContentNode* pRight, bool bBackward)
+{
+ return pImpEditEngine->ImpConnectParagraphs(pLeft, pRight, bBackward);
+}
+
+EditPaM EditEngine::InsertField(const EditSelection& rEditSelection, const SvxFieldItem& rFld)
+{
+ return pImpEditEngine->InsertField(rEditSelection, rFld);
+}
+
+EditPaM EditEngine::InsertText(const EditSelection& aCurEditSelection, const OUString& rStr)
+{
+ return pImpEditEngine->InsertText(aCurEditSelection, rStr);
+}
+
+EditSelection EditEngine::InsertText(const EditTextObject& rTextObject, const EditSelection& rSel)
+{
+ return pImpEditEngine->InsertText(rTextObject, rSel);
+}
+
+EditSelection EditEngine::InsertText(
+ uno::Reference<datatransfer::XTransferable > const & rxDataObj,
+ const OUString& rBaseURL, const EditPaM& rPaM, bool bUseSpecial, SotClipboardFormatId format)
+{
+ return pImpEditEngine->PasteText(rxDataObj, rBaseURL, rPaM, bUseSpecial, format);
+}
+
+EditPaM EditEngine::EndOfWord(const EditPaM& rPaM)
+{
+ return pImpEditEngine->EndOfWord(rPaM);
+}
+
+EditPaM EditEngine::GetPaM(const Point& aDocPos, bool bSmart)
+{
+ return pImpEditEngine->GetPaM(aDocPos, bSmart);
+}
+
+EditSelection EditEngine::SelectWord(
+ const EditSelection& rCurSelection, sal_Int16 nWordType)
+{
+ return pImpEditEngine->SelectWord(rCurSelection, nWordType);
+}
+
+tools::Long EditEngine::GetXPos(
+ const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart) const
+{
+ return pImpEditEngine->GetXPos(pParaPortion, pLine, nIndex, bPreferPortionStart);
+}
+
+Range EditEngine::GetLineXPosStartEnd(
+ const ParaPortion* pParaPortion, const EditLine* pLine) const
+{
+ return pImpEditEngine->GetLineXPosStartEnd(pParaPortion, pLine);
+}
+
+bool EditEngine::IsFormatted() const
+{
+ return pImpEditEngine->IsFormatted();
+}
+
+EditPaM EditEngine::CursorLeft(const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode)
+{
+ return pImpEditEngine->CursorLeft(rPaM, nCharacterIteratorMode);
+}
+
+EditPaM EditEngine::CursorRight(const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode)
+{
+ return pImpEditEngine->CursorRight(rPaM, nCharacterIteratorMode);
+}
+
+InternalEditStatus& EditEngine::GetInternalEditStatus()
+{
+ return pImpEditEngine->GetStatus();
+}
+
+EditDoc& EditEngine::GetEditDoc()
+{
+ return pImpEditEngine->GetEditDoc();
+}
+
+const EditDoc& EditEngine::GetEditDoc() const
+{
+ return pImpEditEngine->GetEditDoc();
+}
+
+void EditEngine::dumpAsXmlEditDoc(xmlTextWriterPtr pWriter) const
+{
+ pImpEditEngine->GetEditDoc().dumpAsXml(pWriter);
+}
+
+ParaPortionList& EditEngine::GetParaPortions()
+{
+ return pImpEditEngine->GetParaPortions();
+}
+
+const ParaPortionList& EditEngine::GetParaPortions() const
+{
+ return pImpEditEngine->GetParaPortions();
+}
+
+void EditEngine::SeekCursor(ContentNode* pNode, sal_Int32 nPos, SvxFont& rFont)
+{
+ pImpEditEngine->SeekCursor(pNode, nPos, rFont);
+}
+
+EditPaM EditEngine::DeleteSelection(const EditSelection& rSel)
+{
+ return pImpEditEngine->ImpDeleteSelection(rSel);
+}
+
+ESelection EditEngine::CreateESelection(const EditSelection& rSel) const
+{
+ return pImpEditEngine->CreateESel(rSel);
+}
+
+EditSelection EditEngine::CreateSelection(const ESelection& rSel)
+{
+ return pImpEditEngine->CreateSel(rSel);
+}
+
+const SfxItemSet& EditEngine::GetBaseParaAttribs(sal_Int32 nPara) const
+{
+ return pImpEditEngine->GetParaAttribs(nPara);
+}
+
+void EditEngine::SetParaAttribsOnly(sal_Int32 nPara, const SfxItemSet& rSet)
+{
+ pImpEditEngine->SetParaAttribs(nPara, rSet);
+}
+
+void EditEngine::SetAttribs(const EditSelection& rSel, const SfxItemSet& rSet, SetAttribsMode nSpecial)
+{
+ pImpEditEngine->SetAttribs(rSel, rSet, nSpecial);
+}
+
+OUString EditEngine::GetSelected(const EditSelection& rSel) const
+{
+ return pImpEditEngine->GetSelected(rSel);
+}
+
+EditPaM EditEngine::DeleteSelected(const EditSelection& rSel)
+{
+ return pImpEditEngine->DeleteSelected(rSel);
+}
+
+void EditEngine::HandleBeginPasteOrDrop(PasteOrDropInfos& rInfos)
+{
+ pImpEditEngine->aBeginPasteOrDropHdl.Call(rInfos);
+}
+
+void EditEngine::HandleEndPasteOrDrop(PasteOrDropInfos& rInfos)
+{
+ pImpEditEngine->aEndPasteOrDropHdl.Call(rInfos);
+}
+
+bool EditEngine::HasText() const
+{
+ return pImpEditEngine->ImplHasText();
+}
+
+const EditSelectionEngine& EditEngine::GetSelectionEngine() const
+{
+ return pImpEditEngine->aSelEngine;
+}
+
+void EditEngine::SetInSelectionMode(bool b)
+{
+ pImpEditEngine->mbInSelection = b;
+}
+
+bool EditEngine::PostKeyEvent( const KeyEvent& rKeyEvent, EditView* pEditView, vcl::Window const * pFrameWin )
+{
+ DBG_ASSERT( pEditView, "no View - no cookie !" );
+
+ bool bDone = true;
+
+ bool bModified = false;
+ bool bMoved = false;
+ bool bAllowIdle = true;
+ bool bReadOnly = pEditView->IsReadOnly();
+
+ GetCursorFlags nNewCursorFlags = GetCursorFlags::NONE;
+ bool bSetCursorFlags = true;
+
+ EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() );
+ DBG_ASSERT( !aCurSel.IsInvalid(), "Blinde Selection in EditEngine::PostKeyEvent" );
+
+ OUString aAutoText( pImpEditEngine->GetAutoCompleteText() );
+ if (!pImpEditEngine->GetAutoCompleteText().isEmpty())
+ pImpEditEngine->SetAutoCompleteText(OUString(), true);
+
+ sal_uInt16 nCode = rKeyEvent.GetKeyCode().GetCode();
+ KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction();
+ if ( eFunc != KeyFuncType::DONTKNOW )
+ {
+ switch ( eFunc )
+ {
+ case KeyFuncType::UNDO:
+ {
+ if ( !bReadOnly )
+ pEditView->Undo();
+ return true;
+ }
+ case KeyFuncType::REDO:
+ {
+ if ( !bReadOnly )
+ pEditView->Redo();
+ return true;
+ }
+
+ default: // is then possible edited below.
+ eFunc = KeyFuncType::DONTKNOW;
+ }
+ }
+
+ if ( eFunc == KeyFuncType::DONTKNOW )
+ {
+ switch ( nCode )
+ {
+#if defined( DBG_UTIL ) || (OSL_DEBUG_LEVEL > 1)
+ case KEY_F1:
+ {
+ if ( rKeyEvent.GetKeyCode().IsMod1() && rKeyEvent.GetKeyCode().IsMod2() )
+ {
+ sal_Int32 nParas = GetParagraphCount();
+ Point aPos;
+ Point aViewStart( pEditView->GetOutputArea().TopLeft() );
+ tools::Long n20 = 40 * pImpEditEngine->nOnePixelInRef;
+ for ( sal_Int32 n = 0; n < nParas; n++ )
+ {
+ tools::Long nH = GetTextHeight( n );
+ Point P1( aViewStart.X() + n20 + n20*(n%2), aViewStart.Y() + aPos.Y() );
+ Point P2( P1 );
+ P2.AdjustX(n20 );
+ P2.AdjustY(nH );
+ pEditView->GetWindow()->GetOutDev()->SetLineColor();
+ pEditView->GetWindow()->GetOutDev()->SetFillColor( (n%2) ? COL_YELLOW : COL_LIGHTGREEN );
+ pEditView->GetWindow()->GetOutDev()->DrawRect( tools::Rectangle( P1, P2 ) );
+ aPos.AdjustY(nH );
+ }
+ }
+ bDone = false;
+ }
+ break;
+ case KEY_F11:
+ {
+ if ( rKeyEvent.GetKeyCode().IsMod1() && rKeyEvent.GetKeyCode().IsMod2() )
+ {
+ bDebugPaint = !bDebugPaint;
+ OStringBuffer aInfo("DebugPaint: ");
+ aInfo.append(bDebugPaint ? "On" : "Off");
+ std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pEditView->GetWindow()->GetFrameWeld(),
+ VclMessageType::Info, VclButtonsType::Ok,
+ OStringToOUString(aInfo, RTL_TEXTENCODING_ASCII_US)));
+ xInfoBox->run();
+
+ }
+ bDone = false;
+ }
+ break;
+ case KEY_F12:
+ {
+ if ( rKeyEvent.GetKeyCode().IsMod1() && rKeyEvent.GetKeyCode().IsMod2() )
+ DumpData(this, true);
+ bDone = false;
+ }
+ break;
+#endif
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ case KEY_HOME:
+ case KEY_END:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN:
+ case css::awt::Key::MOVE_WORD_FORWARD:
+ case css::awt::Key::SELECT_WORD_FORWARD:
+ case css::awt::Key::MOVE_WORD_BACKWARD:
+ case css::awt::Key::SELECT_WORD_BACKWARD:
+ case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
+ case css::awt::Key::MOVE_TO_END_OF_LINE:
+ case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
+ case css::awt::Key::SELECT_TO_END_OF_LINE:
+ case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
+ case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
+ case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
+ case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
+ case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
+ case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
+ case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
+ case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
+ {
+ if ( !rKeyEvent.GetKeyCode().IsMod2() || ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) )
+ {
+ if ( ImpEditEngine::DoVisualCursorTraveling() && ( ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) /* || ( nCode == KEY_HOME ) || ( nCode == KEY_END ) */ ) )
+ bSetCursorFlags = false; // Will be manipulated within visual cursor move
+
+ aCurSel = pImpEditEngine->MoveCursor( rKeyEvent, pEditView );
+
+ if ( aCurSel.HasRange() ) {
+ Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
+ pEditView->pImpEditView->CutCopy( aSelection, false );
+ }
+
+ bMoved = true;
+ if ( nCode == KEY_HOME )
+ nNewCursorFlags |= GetCursorFlags::StartOfLine;
+ else if ( nCode == KEY_END )
+ nNewCursorFlags |= GetCursorFlags::EndOfLine;
+
+ }
+#if OSL_DEBUG_LEVEL > 1
+ GetLanguage( pImpEditEngine->GetEditDoc().GetPos( aCurSel.Max().GetNode() ), aCurSel.Max().GetIndex() );
+#endif
+ }
+ break;
+ case KEY_BACKSPACE:
+ case KEY_DELETE:
+ case css::awt::Key::DELETE_WORD_BACKWARD:
+ case css::awt::Key::DELETE_WORD_FORWARD:
+ case css::awt::Key::DELETE_TO_BEGIN_OF_PARAGRAPH:
+ case css::awt::Key::DELETE_TO_END_OF_PARAGRAPH:
+ {
+ if ( !bReadOnly && !rKeyEvent.GetKeyCode().IsMod2() )
+ {
+ // check if we are behind a bullet and using the backspace key
+ ContentNode *pNode = aCurSel.Min().GetNode();
+ const SvxNumberFormat *pFmt = pImpEditEngine->GetNumberFormat( pNode );
+ if (pFmt && nCode == KEY_BACKSPACE &&
+ !aCurSel.HasRange() && aCurSel.Min().GetIndex() == 0)
+ {
+ // if the bullet is still visible, just make it invisible.
+ // Otherwise continue as usual.
+
+
+ sal_Int32 nPara = pImpEditEngine->GetEditDoc().GetPos( pNode );
+ SfxBoolItem aBulletState( pImpEditEngine->GetParaAttrib( nPara, EE_PARA_BULLETSTATE ) );
+
+ if ( aBulletState.GetValue() )
+ {
+
+ aBulletState.SetValue( false );
+ SfxItemSet aSet( pImpEditEngine->GetParaAttribs( nPara ) );
+ aSet.Put( aBulletState );
+ pImpEditEngine->SetParaAttribs( nPara, aSet );
+
+ // have this and the following paragraphs formatted and repainted.
+ // (not painting a numbering in the list may cause the following
+ // numberings to have different numbers than before and thus the
+ // length may have changed as well )
+ pImpEditEngine->FormatAndLayout( pImpEditEngine->GetActiveView() );
+
+ break;
+ }
+ }
+
+ sal_uInt8 nDel = 0;
+ DeleteMode nMode = DeleteMode::Simple;
+ switch( nCode )
+ {
+ case css::awt::Key::DELETE_WORD_BACKWARD:
+ nMode = DeleteMode::RestOfWord;
+ nDel = DEL_LEFT;
+ break;
+ case css::awt::Key::DELETE_WORD_FORWARD:
+ nMode = DeleteMode::RestOfWord;
+ nDel = DEL_RIGHT;
+ break;
+ case css::awt::Key::DELETE_TO_BEGIN_OF_PARAGRAPH:
+ nMode = DeleteMode::RestOfContent;
+ nDel = DEL_LEFT;
+ break;
+ case css::awt::Key::DELETE_TO_END_OF_PARAGRAPH:
+ nMode = DeleteMode::RestOfContent;
+ nDel = DEL_RIGHT;
+ break;
+ default:
+ nDel = ( nCode == KEY_DELETE ) ? DEL_RIGHT : DEL_LEFT;
+ nMode = rKeyEvent.GetKeyCode().IsMod1() ? DeleteMode::RestOfWord : DeleteMode::Simple;
+ if ( ( nMode == DeleteMode::RestOfWord ) && rKeyEvent.GetKeyCode().IsShift() )
+ nMode = DeleteMode::RestOfContent;
+ break;
+ }
+
+ pEditView->pImpEditView->DrawSelectionXOR();
+ pImpEditEngine->UndoActionStart( EDITUNDO_DELETE );
+ aCurSel = pImpEditEngine->DeleteLeftOrRight( aCurSel, nDel, nMode );
+ pImpEditEngine->UndoActionEnd();
+ bModified = true;
+ bAllowIdle = false;
+ }
+ }
+ break;
+ case KEY_TAB:
+ {
+ if ( !bReadOnly && !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() )
+ {
+ bool bShift = rKeyEvent.GetKeyCode().IsShift();
+ if ( !bShift )
+ {
+ bool bSel = pEditView->HasSelection();
+ if ( bSel )
+ pImpEditEngine->UndoActionStart( EDITUNDO_INSERT );
+ if ( pImpEditEngine->GetStatus().DoAutoCorrect() )
+ aCurSel = pImpEditEngine->AutoCorrect( aCurSel, 0, !pEditView->IsInsertMode(), pFrameWin );
+ aCurSel = pImpEditEngine->InsertTab( aCurSel );
+ if ( bSel )
+ pImpEditEngine->UndoActionEnd();
+ bModified = true;
+ }
+ }
+ else
+ bDone = false;
+ }
+ break;
+ case KEY_RETURN:
+ {
+ if ( !bReadOnly )
+ {
+ pEditView->pImpEditView->DrawSelectionXOR();
+ if ( !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() )
+ {
+ pImpEditEngine->UndoActionStart( EDITUNDO_INSERT );
+ if ( rKeyEvent.GetKeyCode().IsShift() )
+ {
+ aCurSel = pImpEditEngine->AutoCorrect( aCurSel, 0, !pEditView->IsInsertMode(), pFrameWin );
+ aCurSel = pImpEditEngine->InsertLineBreak( aCurSel );
+ }
+ else
+ {
+ if (aAutoText.isEmpty())
+ {
+ if ( pImpEditEngine->GetStatus().DoAutoCorrect() )
+ aCurSel = pImpEditEngine->AutoCorrect( aCurSel, 0, !pEditView->IsInsertMode(), pFrameWin );
+ aCurSel = pImpEditEngine->InsertParaBreak( aCurSel );
+ }
+ else
+ {
+ DBG_ASSERT( !aCurSel.HasRange(), "Selection on complete?!" );
+ EditPaM aStart( pImpEditEngine->WordLeft( aCurSel.Max() ) );
+ aCurSel = pImpEditEngine->InsertText(
+ EditSelection( aStart, aCurSel.Max() ), aAutoText );
+ pImpEditEngine->SetAutoCompleteText( OUString(), true );
+ }
+ }
+ pImpEditEngine->UndoActionEnd();
+ bModified = true;
+ }
+ }
+ }
+ break;
+ case KEY_INSERT:
+ {
+ if ( !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() )
+ pEditView->SetInsertMode( !pEditView->IsInsertMode() );
+ }
+ break;
+ default:
+ {
+ #if (OSL_DEBUG_LEVEL > 1) && defined(DBG_UTIL)
+ if ( ( nCode == KEY_W ) && rKeyEvent.GetKeyCode().IsMod1() && rKeyEvent.GetKeyCode().IsMod2() )
+ {
+ SfxItemSet aAttribs = pEditView->GetAttribs();
+ const SvxFrameDirectionItem& rCurrentWritingMode = (const SvxFrameDirectionItem&)aAttribs.Get( EE_PARA_WRITINGDIR );
+ SvxFrameDirectionItem aNewItem( SvxFrameDirection::Horizontal_LR_TB, EE_PARA_WRITINGDIR );
+ if ( rCurrentWritingMode.GetValue() != SvxFrameDirection::Horizontal_RL_TB )
+ aNewItem.SetValue( SvxFrameDirection::Horizontal_RL_TB );
+ aAttribs.Put( aNewItem );
+ pEditView->SetAttribs( aAttribs );
+ }
+ #endif
+ if ( !bReadOnly && IsSimpleCharInput( rKeyEvent ) )
+ {
+ sal_Unicode nCharCode = rKeyEvent.GetCharCode();
+ pEditView->pImpEditView->DrawSelectionXOR();
+ // Autocorrection?
+ if ( ( pImpEditEngine->GetStatus().DoAutoCorrect() ) &&
+ ( SvxAutoCorrect::IsAutoCorrectChar( nCharCode ) ||
+ pImpEditEngine->IsNbspRunNext() ) )
+ {
+ aCurSel = pImpEditEngine->AutoCorrect(
+ aCurSel, nCharCode, !pEditView->IsInsertMode(), pFrameWin );
+ }
+ else
+ {
+ aCurSel = pImpEditEngine->InsertTextUserInput( aCurSel, nCharCode, !pEditView->IsInsertMode() );
+ }
+ // AutoComplete ???
+ if ( pImpEditEngine->GetStatus().DoAutoComplete() && ( nCharCode != ' ' ) )
+ {
+ // Only at end of word...
+ sal_Int32 nIndex = aCurSel.Max().GetIndex();
+ if ( ( nIndex >= aCurSel.Max().GetNode()->Len() ) ||
+ ( pImpEditEngine->aWordDelimiters.indexOf( aCurSel.Max().GetNode()->GetChar( nIndex ) ) != -1 ) )
+ {
+ EditPaM aStart( pImpEditEngine->WordLeft( aCurSel.Max() ) );
+ OUString aWord = pImpEditEngine->GetSelected( EditSelection( aStart, aCurSel.Max() ) );
+ if ( aWord.getLength() >= 3 )
+ {
+ OUString aComplete;
+
+ LanguageType eLang = pImpEditEngine->GetLanguage( EditPaM( aStart.GetNode(), aStart.GetIndex()+1)).nLang;
+ LanguageTag aLanguageTag( eLang);
+
+ if (!pImpEditEngine->xLocaleDataWrapper.isInitialized())
+ pImpEditEngine->xLocaleDataWrapper.init( SvtSysLocale().GetLocaleData().getComponentContext(), aLanguageTag);
+ else
+ pImpEditEngine->xLocaleDataWrapper.changeLocale( aLanguageTag);
+
+ if (!pImpEditEngine->xTransliterationWrapper.isInitialized())
+ pImpEditEngine->xTransliterationWrapper.init( SvtSysLocale().GetLocaleData().getComponentContext(), eLang);
+ else
+ pImpEditEngine->xTransliterationWrapper.changeLocale( eLang);
+
+ const ::utl::TransliterationWrapper* pTransliteration = pImpEditEngine->xTransliterationWrapper.get();
+ Sequence< i18n::CalendarItem2 > xItem = pImpEditEngine->xLocaleDataWrapper->getDefaultCalendarDays();
+ sal_Int32 nCount = xItem.getLength();
+ const i18n::CalendarItem2* pArr = xItem.getConstArray();
+ for( sal_Int32 n = 0; n <= nCount; ++n )
+ {
+ const OUString& rDay = pArr[n].FullName;
+ if( pTransliteration->isMatch( aWord, rDay) )
+ {
+ aComplete = rDay;
+ break;
+ }
+ }
+
+ if ( aComplete.isEmpty() )
+ {
+ xItem = pImpEditEngine->xLocaleDataWrapper->getDefaultCalendarMonths();
+ sal_Int32 nMonthCount = xItem.getLength();
+ const i18n::CalendarItem2* pMonthArr = xItem.getConstArray();
+ for( sal_Int32 n = 0; n <= nMonthCount; ++n )
+ {
+ const OUString& rMon = pMonthArr[n].FullName;
+ if( pTransliteration->isMatch( aWord, rMon) )
+ {
+ aComplete = rMon;
+ break;
+ }
+ }
+ }
+
+ if( !aComplete.isEmpty() && ( ( aWord.getLength() + 1 ) < aComplete.getLength() ) )
+ {
+ pImpEditEngine->SetAutoCompleteText( aComplete, false );
+ Point aPos = pImpEditEngine->PaMtoEditCursor( aCurSel.Max() ).TopLeft();
+ aPos = pEditView->pImpEditView->GetWindowPos( aPos );
+ aPos = pEditView->pImpEditView->GetWindow()->LogicToPixel( aPos );
+ aPos = pEditView->GetWindow()->OutputToScreenPixel( aPos );
+ aPos.AdjustY( -3 );
+ Help::ShowQuickHelp( pEditView->GetWindow(), tools::Rectangle( aPos, Size( 1, 1 ) ), aComplete, QuickHelpFlags::Bottom|QuickHelpFlags::Left );
+ }
+ }
+ }
+ }
+ bModified = true;
+ }
+ else
+ bDone = false;
+ }
+ }
+ }
+
+ pEditView->pImpEditView->SetEditSelection( aCurSel );
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ pEditView->pImpEditView->DrawSelectionXOR();
+ }
+ pImpEditEngine->UpdateSelections();
+
+ if ( ( !IsEffectivelyVertical() && ( nCode != KEY_UP ) && ( nCode != KEY_DOWN ) ) ||
+ ( IsEffectivelyVertical() && ( nCode != KEY_LEFT ) && ( nCode != KEY_RIGHT ) ))
+ {
+ pEditView->pImpEditView->nTravelXPos = TRAVEL_X_DONTKNOW;
+ }
+
+ if ( /* ( nCode != KEY_HOME ) && ( nCode != KEY_END ) && */
+ ( !IsEffectivelyVertical() && ( nCode != KEY_LEFT ) && ( nCode != KEY_RIGHT ) ) ||
+ ( IsEffectivelyVertical() && ( nCode != KEY_UP ) && ( nCode != KEY_DOWN ) ))
+ {
+ pEditView->pImpEditView->SetCursorBidiLevel( CURSOR_BIDILEVEL_DONTKNOW );
+ }
+
+ if ( bSetCursorFlags )
+ pEditView->pImpEditView->nExtraCursorFlags = nNewCursorFlags;
+
+ if ( bModified )
+ {
+ DBG_ASSERT( !bReadOnly, "ReadOnly but modified???" );
+ // Idle-Formatter only when AnyInput.
+ if ( bAllowIdle && pImpEditEngine->GetStatus().UseIdleFormatter()
+ && Application::AnyInput( VclInputFlags::KEYBOARD) )
+ pImpEditEngine->IdleFormatAndLayout( pEditView );
+ else
+ pImpEditEngine->FormatAndLayout( pEditView );
+ }
+ else if ( bMoved )
+ {
+ bool bGotoCursor = pEditView->pImpEditView->DoAutoScroll();
+ pEditView->pImpEditView->ShowCursor( bGotoCursor, true );
+ pImpEditEngine->CallStatusHdl();
+ }
+
+ return bDone;
+}
+
+sal_uInt32 EditEngine::GetTextHeight() const
+{
+
+ if ( !pImpEditEngine->IsFormatted() )
+ pImpEditEngine->FormatDoc();
+
+ sal_uInt32 nHeight = !IsEffectivelyVertical() ? pImpEditEngine->GetTextHeight() : pImpEditEngine->CalcTextWidth( true );
+ return nHeight;
+}
+
+sal_uInt32 EditEngine::GetTextHeightNTP() const
+{
+
+ if ( !pImpEditEngine->IsFormatted() )
+ pImpEditEngine->FormatDoc();
+
+ if ( IsEffectivelyVertical() )
+ return pImpEditEngine->CalcTextWidth( true );
+
+ return pImpEditEngine->GetTextHeightNTP();
+}
+
+sal_uInt32 EditEngine::CalcTextWidth()
+{
+
+ if ( !pImpEditEngine->IsFormatted() )
+ pImpEditEngine->FormatDoc();
+
+ sal_uInt32 nWidth = !IsEffectivelyVertical() ? pImpEditEngine->CalcTextWidth( true ) : pImpEditEngine->GetTextHeight();
+ return nWidth;
+}
+
+bool EditEngine::SetUpdateLayout(bool bUpdate, bool bRestoring)
+{
+ bool bPrevUpdateLayout = pImpEditEngine->SetUpdateLayout( bUpdate );
+ if (pImpEditEngine->pActiveView)
+ {
+ // Not an activation if we are restoring the previous update mode.
+ pImpEditEngine->pActiveView->ShowCursor(false, false, /*bActivate=*/!bRestoring);
+ }
+ return bPrevUpdateLayout;
+}
+
+bool EditEngine::IsUpdateLayout() const
+{
+ return pImpEditEngine->IsUpdateLayout();
+}
+
+void EditEngine::Clear()
+{
+ pImpEditEngine->Clear();
+}
+
+void EditEngine::SetText( const OUString& rText )
+{
+ pImpEditEngine->SetText( rText );
+ if ( !rText.isEmpty() && pImpEditEngine->IsUpdateLayout() )
+ pImpEditEngine->FormatAndLayout();
+}
+
+ErrCode EditEngine::Read( SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat, SvKeyValueIterator* pHTTPHeaderAttrs /* = NULL */ )
+{
+ bool bUndoEnabled = pImpEditEngine->IsUndoEnabled();
+ pImpEditEngine->EnableUndo( false );
+ pImpEditEngine->SetText( OUString() );
+ EditPaM aPaM( pImpEditEngine->GetEditDoc().GetStartPaM() );
+ pImpEditEngine->Read( rInput, rBaseURL, eFormat, EditSelection( aPaM, aPaM ), pHTTPHeaderAttrs );
+ pImpEditEngine->EnableUndo( bUndoEnabled );
+ return rInput.GetError();
+}
+
+void EditEngine::Write( SvStream& rOutput, EETextFormat eFormat )
+{
+ EditPaM aStartPaM( pImpEditEngine->GetEditDoc().GetStartPaM() );
+ EditPaM aEndPaM( pImpEditEngine->GetEditDoc().GetEndPaM() );
+ pImpEditEngine->Write( rOutput, eFormat, EditSelection( aStartPaM, aEndPaM ) );
+}
+
+std::unique_ptr<EditTextObject> EditEngine::CreateTextObject()
+{
+ return pImpEditEngine->CreateTextObject();
+}
+
+std::unique_ptr<EditTextObject> EditEngine::CreateTextObject( const ESelection& rESelection )
+{
+ EditSelection aSel( pImpEditEngine->CreateSel( rESelection ) );
+ return pImpEditEngine->CreateTextObject( aSel );
+}
+
+std::unique_ptr<EditTextObject> EditEngine::GetEmptyTextObject() const
+{
+ return pImpEditEngine->GetEmptyTextObject();
+}
+
+
+void EditEngine::SetText( const EditTextObject& rTextObject )
+{
+ pImpEditEngine->SetText( rTextObject );
+ pImpEditEngine->FormatAndLayout();
+}
+
+void EditEngine::ShowParagraph( sal_Int32 nParagraph, bool bShow )
+{
+ pImpEditEngine->ShowParagraph( nParagraph, bShow );
+}
+
+void EditEngine::SetNotifyHdl( const Link<EENotify&,void>& rLink )
+{
+ pImpEditEngine->SetNotifyHdl( rLink );
+}
+
+Link<EENotify&,void> const & EditEngine::GetNotifyHdl() const
+{
+ return pImpEditEngine->GetNotifyHdl();
+}
+
+void EditEngine::SetStatusEventHdl( const Link<EditStatus&, void>& rLink )
+{
+ pImpEditEngine->SetStatusEventHdl( rLink );
+}
+
+Link<EditStatus&, void> const & EditEngine::GetStatusEventHdl() const
+{
+ return pImpEditEngine->GetStatusEventHdl();
+}
+
+void EditEngine::SetHtmlImportHdl( const Link<HtmlImportInfo&,void>& rLink )
+{
+ pImpEditEngine->aHtmlImportHdl = rLink;
+}
+
+const Link<HtmlImportInfo&,void>& EditEngine::GetHtmlImportHdl() const
+{
+ return pImpEditEngine->aHtmlImportHdl;
+}
+
+void EditEngine::SetRtfImportHdl( const Link<RtfImportInfo&,void>& rLink )
+{
+ pImpEditEngine->aRtfImportHdl = rLink;
+}
+
+const Link<RtfImportInfo&,void>& EditEngine::GetRtfImportHdl() const
+{
+ return pImpEditEngine->aRtfImportHdl;
+}
+
+void EditEngine::SetBeginMovingParagraphsHdl( const Link<MoveParagraphsInfo&,void>& rLink )
+{
+ pImpEditEngine->aBeginMovingParagraphsHdl = rLink;
+}
+
+void EditEngine::SetEndMovingParagraphsHdl( const Link<MoveParagraphsInfo&,void>& rLink )
+{
+ pImpEditEngine->aEndMovingParagraphsHdl = rLink;
+}
+
+void EditEngine::SetBeginPasteOrDropHdl( const Link<PasteOrDropInfos&,void>& rLink )
+{
+
+ pImpEditEngine->aBeginPasteOrDropHdl = rLink;
+}
+
+void EditEngine::SetEndPasteOrDropHdl( const Link<PasteOrDropInfos&,void>& rLink )
+{
+ pImpEditEngine->aEndPasteOrDropHdl = rLink;
+}
+
+std::unique_ptr<EditTextObject> EditEngine::CreateTextObject( sal_Int32 nPara, sal_Int32 nParas )
+{
+ DBG_ASSERT( 0 <= nPara && nPara < pImpEditEngine->GetEditDoc().Count(), "CreateTextObject: Startpara out of Range" );
+ DBG_ASSERT( nParas <= pImpEditEngine->GetEditDoc().Count() - nPara, "CreateTextObject: Endpara out of Range" );
+
+ ContentNode* pStartNode = pImpEditEngine->GetEditDoc().GetObject( nPara );
+ ContentNode* pEndNode = pImpEditEngine->GetEditDoc().GetObject( nPara+nParas-1 );
+ DBG_ASSERT( pStartNode, "Start-Paragraph does not exist: CreateTextObject" );
+ DBG_ASSERT( pEndNode, "End-Paragraph does not exist: CreateTextObject" );
+
+ if ( pStartNode && pEndNode )
+ {
+ EditSelection aTmpSel;
+ aTmpSel.Min() = EditPaM( pStartNode, 0 );
+ aTmpSel.Max() = EditPaM( pEndNode, pEndNode->Len() );
+ return pImpEditEngine->CreateTextObject( aTmpSel );
+ }
+ return nullptr;
+}
+
+void EditEngine::RemoveParagraph( sal_Int32 nPara )
+{
+ DBG_ASSERT( pImpEditEngine->GetEditDoc().Count() > 1, "The first paragraph should not be deleted!" );
+ if( pImpEditEngine->GetEditDoc().Count() <= 1 )
+ return;
+
+ ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara );
+ const ParaPortion* pPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nPara );
+ DBG_ASSERT( pPortion && pNode, "Paragraph not found: RemoveParagraph" );
+ if ( pNode && pPortion )
+ {
+ // No Undo encapsulation needed.
+ pImpEditEngine->ImpRemoveParagraph( nPara );
+ pImpEditEngine->InvalidateFromParagraph( nPara );
+ pImpEditEngine->UpdateSelections();
+ if (pImpEditEngine->IsUpdateLayout())
+ pImpEditEngine->FormatAndLayout();
+ }
+}
+
+sal_Int32 EditEngine::GetTextLen( sal_Int32 nPara ) const
+{
+ ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara );
+ DBG_ASSERT( pNode, "Paragraph not found: GetTextLen" );
+ if ( pNode )
+ return pNode->Len();
+ return 0;
+}
+
+OUString EditEngine::GetText( sal_Int32 nPara ) const
+{
+ OUString aStr;
+ if ( 0 <= nPara && nPara < pImpEditEngine->GetEditDoc().Count() )
+ aStr = pImpEditEngine->GetEditDoc().GetParaAsString( nPara );
+ return aStr;
+}
+
+void EditEngine::SetModifyHdl( const Link<LinkParamNone*,void>& rLink )
+{
+ pImpEditEngine->SetModifyHdl( rLink );
+}
+
+void EditEngine::ClearModifyFlag()
+{
+ pImpEditEngine->SetModifyFlag( false );
+}
+
+void EditEngine::SetModified()
+{
+ pImpEditEngine->SetModifyFlag( true );
+}
+
+bool EditEngine::IsModified() const
+{
+ return pImpEditEngine->IsModified();
+}
+
+bool EditEngine::IsInSelectionMode() const
+{
+ return ( pImpEditEngine->IsInSelectionMode() ||
+ pImpEditEngine->GetSelEngine().IsInSelection() );
+}
+
+void EditEngine::InsertParagraph( sal_Int32 nPara, const EditTextObject& rTxtObj, bool bAppend )
+{
+ if ( nPara > GetParagraphCount() )
+ {
+ SAL_WARN_IF( nPara != EE_PARA_APPEND, "editeng", "Paragraph number too large, but not EE_PARA_APPEND!" );
+ nPara = GetParagraphCount();
+ }
+
+ pImpEditEngine->UndoActionStart( EDITUNDO_INSERT );
+
+ // No Undo compounding needed.
+ EditPaM aPaM( pImpEditEngine->InsertParagraph( nPara ) );
+ // When InsertParagraph from the outside, no hard attributes
+ // should be taken over!
+ pImpEditEngine->RemoveCharAttribs( nPara );
+ pImpEditEngine->InsertText( rTxtObj, EditSelection( aPaM, aPaM ) );
+
+ if ( bAppend && nPara )
+ pImpEditEngine->ConnectContents( nPara-1, /*bBackwards=*/false );
+
+ pImpEditEngine->UndoActionEnd();
+
+ if (pImpEditEngine->IsUpdateLayout())
+ pImpEditEngine->FormatAndLayout();
+}
+
+void EditEngine::InsertParagraph(sal_Int32 nPara, const OUString& rTxt)
+{
+ if ( nPara > GetParagraphCount() )
+ {
+ SAL_WARN_IF( nPara != EE_PARA_APPEND, "editeng", "Paragraph number too large, but not EE_PARA_APPEND!" );
+ nPara = GetParagraphCount();
+ }
+
+ pImpEditEngine->UndoActionStart( EDITUNDO_INSERT );
+ EditPaM aPaM( pImpEditEngine->InsertParagraph( nPara ) );
+ // When InsertParagraph from the outside, no hard attributes
+ // should be taken over!
+ pImpEditEngine->RemoveCharAttribs( nPara );
+ pImpEditEngine->UndoActionEnd();
+ pImpEditEngine->ImpInsertText( EditSelection( aPaM, aPaM ), rTxt );
+ if (pImpEditEngine->IsUpdateLayout())
+ pImpEditEngine->FormatAndLayout();
+}
+
+void EditEngine::SetText(sal_Int32 nPara, const OUString& rTxt)
+{
+ std::optional<EditSelection> pSel = pImpEditEngine->SelectParagraph( nPara );
+ if ( pSel )
+ {
+ pImpEditEngine->UndoActionStart( EDITUNDO_INSERT );
+ pImpEditEngine->ImpInsertText( *pSel, rTxt );
+ pImpEditEngine->UndoActionEnd();
+ if (pImpEditEngine->IsUpdateLayout())
+ pImpEditEngine->FormatAndLayout();
+ }
+}
+
+void EditEngine::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet )
+{
+ pImpEditEngine->SetParaAttribs( nPara, rSet );
+ if ( pImpEditEngine->IsUpdateLayout() )
+ pImpEditEngine->FormatAndLayout();
+}
+
+const SfxItemSet& EditEngine::GetParaAttribs( sal_Int32 nPara ) const
+{
+ return pImpEditEngine->GetParaAttribs( nPara );
+}
+
+bool EditEngine::HasParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const
+{
+ return pImpEditEngine->HasParaAttrib( nPara, nWhich );
+}
+
+const SfxPoolItem& EditEngine::GetParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const
+{
+ return pImpEditEngine->GetParaAttrib( nPara, nWhich );
+}
+
+void EditEngine::SetCharAttribs(sal_Int32 nPara, const SfxItemSet& rSet)
+{
+ EditSelection aSel(pImpEditEngine->ConvertSelection(nPara, 0, nPara, GetTextLen(nPara)));
+ // This is called by sd::View::OnBeginPasteOrDrop(), updating the cursor position on undo is not
+ // wanted.
+ pImpEditEngine->SetAttribs(aSel, rSet, /*nSpecial=*/SetAttribsMode::NONE, /*bSetSelection=*/false);
+ if (pImpEditEngine->IsUpdateLayout())
+ pImpEditEngine->FormatAndLayout();
+}
+
+void EditEngine::GetCharAttribs( sal_Int32 nPara, std::vector<EECharAttrib>& rLst ) const
+{
+ pImpEditEngine->GetCharAttribs( nPara, rLst );
+}
+
+SfxItemSet EditEngine::GetAttribs( const ESelection& rSel, EditEngineAttribs nOnlyHardAttrib )
+{
+ EditSelection aSel( pImpEditEngine->
+ ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) );
+ return pImpEditEngine->GetAttribs( aSel, nOnlyHardAttrib );
+}
+
+SfxItemSet EditEngine::GetAttribs( sal_Int32 nPara, sal_Int32 nStart, sal_Int32 nEnd, GetAttribsFlags nFlags ) const
+{
+ return pImpEditEngine->GetAttribs( nPara, nStart, nEnd, nFlags );
+}
+
+void EditEngine::RemoveAttribs( const ESelection& rSelection, bool bRemoveParaAttribs, sal_uInt16 nWhich )
+{
+ const EERemoveParaAttribsMode eMode = bRemoveParaAttribs?
+ EERemoveParaAttribsMode::RemoveAll :
+ EERemoveParaAttribsMode::RemoveCharItems;
+
+ pImpEditEngine->UndoActionStart( EDITUNDO_RESETATTRIBS );
+ EditSelection aSel( pImpEditEngine->ConvertSelection( rSelection.nStartPara, rSelection.nStartPos, rSelection.nEndPara, rSelection.nEndPos ) );
+ pImpEditEngine->RemoveCharAttribs( aSel, eMode, nWhich );
+ pImpEditEngine->UndoActionEnd();
+ if (pImpEditEngine->IsUpdateLayout())
+ pImpEditEngine->FormatAndLayout();
+}
+
+vcl::Font EditEngine::GetStandardFont( sal_Int32 nPara )
+{
+ return GetStandardSvxFont( nPara );
+}
+
+SvxFont EditEngine::GetStandardSvxFont( sal_Int32 nPara )
+{
+ ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara );
+ return pNode->GetCharAttribs().GetDefFont();
+}
+
+void EditEngine::StripPortions()
+{
+ ScopedVclPtrInstance< VirtualDevice > aTmpDev;
+ tools::Rectangle aBigRect( Point( 0, 0 ), Size( 0x7FFFFFFF, 0x7FFFFFFF ) );
+ if ( IsEffectivelyVertical() )
+ {
+ if( IsTopToBottom() )
+ {
+ aBigRect.SetRight( 0 );
+ aBigRect.SetLeft( -0x7FFFFFFF );
+ }
+ else
+ {
+ aBigRect.SetTop( -0x7FFFFFFF );
+ aBigRect.SetBottom( 0 );
+ }
+ }
+ pImpEditEngine->Paint(*aTmpDev, aBigRect, Point(), true);
+}
+
+void EditEngine::GetPortions( sal_Int32 nPara, std::vector<sal_Int32>& rList )
+{
+ if ( !pImpEditEngine->IsFormatted() )
+ pImpEditEngine->FormatFullDoc();
+
+ const ParaPortion* pParaPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nPara );
+ if ( pParaPortion )
+ {
+ sal_Int32 nEnd = 0;
+ sal_Int32 nTextPortions = pParaPortion->GetTextPortions().Count();
+ for ( sal_Int32 n = 0; n < nTextPortions; n++ )
+ {
+ nEnd = nEnd + pParaPortion->GetTextPortions()[n].GetLen();
+ rList.push_back( nEnd );
+ }
+ }
+}
+
+void EditEngine::SetFlatMode( bool bFlat)
+{
+ pImpEditEngine->SetFlatMode( bFlat );
+}
+
+bool EditEngine::IsFlatMode() const
+{
+ return !( pImpEditEngine->GetStatus().UseCharAttribs() );
+}
+
+void EditEngine::SetSingleLine(bool bValue)
+{
+ if (bValue == pImpEditEngine->GetStatus().IsSingleLine())
+ return;
+
+ if (bValue)
+ pImpEditEngine->GetStatus().TurnOnFlags(EEControlBits::SINGLELINE);
+ else
+ pImpEditEngine->GetStatus().TurnOffFlags(EEControlBits::SINGLELINE);
+}
+
+void EditEngine::SetControlWord( EEControlBits nWord )
+{
+
+ if ( nWord == pImpEditEngine->GetStatus().GetControlWord() )
+ return;
+
+ EEControlBits nPrev = pImpEditEngine->GetStatus().GetControlWord();
+ pImpEditEngine->GetStatus().GetControlWord() = nWord;
+
+ EEControlBits nChanges = nPrev ^ nWord;
+ if ( pImpEditEngine->IsFormatted() )
+ {
+ // possibly reformat:
+ if ( ( nChanges & EEControlBits::USECHARATTRIBS ) ||
+ ( nChanges & EEControlBits::ONECHARPERLINE ) ||
+ ( nChanges & EEControlBits::STRETCHING ) ||
+ ( nChanges & EEControlBits::OUTLINER ) ||
+ ( nChanges & EEControlBits::NOCOLORS ) ||
+ ( nChanges & EEControlBits::OUTLINER2 ) )
+ {
+ if ( nChanges & EEControlBits::USECHARATTRIBS )
+ {
+ pImpEditEngine->GetEditDoc().CreateDefFont( true );
+ }
+
+ pImpEditEngine->FormatFullDoc();
+ pImpEditEngine->UpdateViews( pImpEditEngine->GetActiveView() );
+ }
+ }
+
+ bool bSpellingChanged = bool(nChanges & EEControlBits::ONLINESPELLING);
+
+ if ( !bSpellingChanged )
+ return;
+
+ pImpEditEngine->StopOnlineSpellTimer();
+ if (nWord & EEControlBits::ONLINESPELLING)
+ {
+ // Create WrongList, start timer...
+ sal_Int32 nNodes = pImpEditEngine->GetEditDoc().Count();
+ for ( sal_Int32 n = 0; n < nNodes; n++ )
+ {
+ ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( n );
+ pNode->CreateWrongList();
+ }
+ if (pImpEditEngine->IsFormatted())
+ pImpEditEngine->StartOnlineSpellTimer();
+ }
+ else
+ {
+ tools::Long nY = 0;
+ sal_Int32 nNodes = pImpEditEngine->GetEditDoc().Count();
+ for ( sal_Int32 n = 0; n < nNodes; n++ )
+ {
+ ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( n );
+ const ParaPortion* pPortion = pImpEditEngine->GetParaPortions()[n];
+ bool bWrongs = false;
+ if (pNode->GetWrongList() != nullptr)
+ bWrongs = !pNode->GetWrongList()->empty();
+ pNode->DestroyWrongList();
+ if ( bWrongs )
+ {
+ pImpEditEngine->aInvalidRect.SetLeft( 0 );
+ pImpEditEngine->aInvalidRect.SetRight( pImpEditEngine->GetPaperSize().Width() );
+ pImpEditEngine->aInvalidRect.SetTop( nY+1 );
+ pImpEditEngine->aInvalidRect.SetBottom( nY+pPortion->GetHeight()-1 );
+ pImpEditEngine->UpdateViews( pImpEditEngine->pActiveView );
+ }
+ nY += pPortion->GetHeight();
+ }
+ }
+}
+
+EEControlBits EditEngine::GetControlWord() const
+{
+ return pImpEditEngine->GetStatus().GetControlWord();
+}
+
+tools::Long EditEngine::GetFirstLineStartX( sal_Int32 nParagraph )
+{
+
+ tools::Long nX = 0;
+ const ParaPortion* pPPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nParagraph );
+ if ( pPPortion )
+ {
+ DBG_ASSERT( pImpEditEngine->IsFormatted() || !pImpEditEngine->IsFormatting(), "GetFirstLineStartX: Doc not formatted - unable to format!" );
+ if ( !pImpEditEngine->IsFormatted() )
+ pImpEditEngine->FormatDoc();
+ const EditLine& rFirstLine = pPPortion->GetLines()[0];
+ nX = rFirstLine.GetStartPosX();
+ }
+ return nX;
+}
+
+Point EditEngine::GetDocPos( const Point& rPaperPos ) const
+{
+ Point aDocPos( rPaperPos );
+ if ( IsEffectivelyVertical() )
+ {
+ if ( IsTopToBottom() )
+ {
+ aDocPos.setX( rPaperPos.Y() );
+ aDocPos.setY( GetPaperSize().Width() - rPaperPos.X() );
+ }
+ else
+ {
+ aDocPos.setX( rPaperPos.Y() );
+ aDocPos.setY( rPaperPos.X() );
+ }
+ }
+ return aDocPos;
+}
+
+Point EditEngine::GetDocPosTopLeft( sal_Int32 nParagraph )
+{
+ const ParaPortion* pPPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nParagraph );
+ DBG_ASSERT( pPPortion, "Paragraph not found: GetWindowPosTopLeft" );
+ Point aPoint;
+ if ( pPPortion )
+ {
+
+ // If someone calls GetLineHeight() with an empty Engine.
+ DBG_ASSERT( pImpEditEngine->IsFormatted() || !pImpEditEngine->IsFormatting(), "GetDocPosTopLeft: Doc not formatted - unable to format!" );
+ if ( !pImpEditEngine->IsFormatted() )
+ pImpEditEngine->FormatAndLayout();
+ if ( pPPortion->GetLines().Count() )
+ {
+ // Correct it if large Bullet.
+ const EditLine& rFirstLine = pPPortion->GetLines()[0];
+ aPoint.setX( rFirstLine.GetStartPosX() );
+ }
+ else
+ {
+ const SvxLRSpaceItem& rLRItem = pImpEditEngine->GetLRSpaceItem( pPPortion->GetNode() );
+ sal_Int32 nSpaceBefore = 0;
+ pImpEditEngine->GetSpaceBeforeAndMinLabelWidth( pPPortion->GetNode(), &nSpaceBefore );
+ short nX = static_cast<short>(rLRItem.GetTextLeft()
+ + rLRItem.GetTextFirstLineOffset()
+ + nSpaceBefore);
+
+ aPoint.setX(pImpEditEngine->scaleXSpacingValue(nX));
+ }
+ aPoint.setY( pImpEditEngine->GetParaPortions().GetYOffset( pPPortion ) );
+ }
+ return aPoint;
+}
+
+const SvxNumberFormat* EditEngine::GetNumberFormat( sal_Int32 ) const
+{
+ // derived objects may override this function to give access to
+ // bullet information (see Outliner)
+ return nullptr;
+}
+
+bool EditEngine::IsRightToLeft( sal_Int32 nPara ) const
+{
+ return pImpEditEngine->IsRightToLeft( nPara );
+}
+
+bool EditEngine::IsTextPos( const Point& rPaperPos, sal_uInt16 nBorder )
+{
+
+ if ( !pImpEditEngine->IsFormatted() )
+ pImpEditEngine->FormatDoc();
+
+ // take unrotated positions for calculation here
+ Point aDocPos = GetDocPos( rPaperPos );
+
+ if ( ( aDocPos.Y() > 0 ) && ( o3tl::make_unsigned(aDocPos.Y()) < pImpEditEngine->GetTextHeight() ) )
+ return pImpEditEngine->IsTextPos(aDocPos, nBorder);
+ return false;
+}
+
+void EditEngine::SetEditTextObjectPool( SfxItemPool* pPool )
+{
+ pImpEditEngine->SetEditTextObjectPool( pPool );
+}
+
+SfxItemPool* EditEngine::GetEditTextObjectPool() const
+{
+ return pImpEditEngine->GetEditTextObjectPool();
+}
+
+void EditEngine::QuickSetAttribs( const SfxItemSet& rSet, const ESelection& rSel )
+{
+
+ EditSelection aSel( pImpEditEngine->
+ ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) );
+
+ pImpEditEngine->SetAttribs( aSel, rSet );
+}
+
+void EditEngine::QuickMarkInvalid( const ESelection& rSel )
+{
+ DBG_ASSERT( rSel.nStartPara < pImpEditEngine->GetEditDoc().Count(), "MarkInvalid: Start out of Range!" );
+ DBG_ASSERT( rSel.nEndPara < pImpEditEngine->GetEditDoc().Count(), "MarkInvalid: End out of Range!" );
+ for ( sal_Int32 nPara = rSel.nStartPara; nPara <= rSel.nEndPara; nPara++ )
+ {
+ ParaPortion* pPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nPara );
+ if ( pPortion )
+ pPortion->MarkSelectionInvalid( 0 );
+ }
+}
+
+void EditEngine::QuickInsertText(const OUString& rText, const ESelection& rSel)
+{
+
+ EditSelection aSel( pImpEditEngine->
+ ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) );
+
+ pImpEditEngine->ImpInsertText( aSel, rText );
+}
+
+void EditEngine::QuickDelete( const ESelection& rSel )
+{
+
+ EditSelection aSel( pImpEditEngine->
+ ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) );
+
+ pImpEditEngine->ImpDeleteSelection( aSel );
+}
+
+void EditEngine::QuickMarkToBeRepainted( sal_Int32 nPara )
+{
+ ParaPortion* pPortion = pImpEditEngine->GetParaPortions().SafeGetObject( nPara );
+ if ( pPortion )
+ pPortion->SetMustRepaint( true );
+}
+
+void EditEngine::QuickInsertLineBreak( const ESelection& rSel )
+{
+
+ EditSelection aSel( pImpEditEngine->
+ ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) );
+
+ pImpEditEngine->InsertLineBreak( aSel );
+}
+
+void EditEngine::QuickInsertField( const SvxFieldItem& rFld, const ESelection& rSel )
+{
+
+ EditSelection aSel( pImpEditEngine->
+ ConvertSelection( rSel.nStartPara, rSel.nStartPos, rSel.nEndPara, rSel.nEndPos ) );
+
+ pImpEditEngine->ImpInsertFeature( aSel, rFld );
+}
+
+void EditEngine::QuickFormatDoc( bool bFull )
+{
+ if ( bFull )
+ pImpEditEngine->FormatFullDoc();
+ else
+ pImpEditEngine->FormatDoc();
+
+ // Don't pass active view, maybe selection is not updated yet...
+ pImpEditEngine->UpdateViews();
+}
+
+void EditEngine::SetStyleSheet(const EditSelection& aSel, SfxStyleSheet* pStyle)
+{
+ pImpEditEngine->SetStyleSheet(aSel, pStyle);
+}
+
+void EditEngine::SetStyleSheet( sal_Int32 nPara, SfxStyleSheet* pStyle )
+{
+ pImpEditEngine->SetStyleSheet( nPara, pStyle );
+}
+
+const SfxStyleSheet* EditEngine::GetStyleSheet( sal_Int32 nPara ) const
+{
+ return pImpEditEngine->GetStyleSheet( nPara );
+}
+
+SfxStyleSheet* EditEngine::GetStyleSheet( sal_Int32 nPara )
+{
+ return pImpEditEngine->GetStyleSheet( nPara );
+}
+
+void EditEngine::SetStyleSheetPool( SfxStyleSheetPool* pSPool )
+{
+ pImpEditEngine->SetStyleSheetPool( pSPool );
+}
+
+SfxStyleSheetPool* EditEngine::GetStyleSheetPool()
+{
+ return pImpEditEngine->GetStyleSheetPool();
+}
+
+void EditEngine::SetWordDelimiters( const OUString& rDelimiters )
+{
+ pImpEditEngine->aWordDelimiters = rDelimiters;
+ if (pImpEditEngine->aWordDelimiters.indexOf(CH_FEATURE) == -1)
+ pImpEditEngine->aWordDelimiters += OUStringChar(CH_FEATURE);
+}
+
+const OUString& EditEngine::GetWordDelimiters() const
+{
+ return pImpEditEngine->aWordDelimiters;
+}
+
+void EditEngine::EraseVirtualDevice()
+{
+ pImpEditEngine->EraseVirtualDevice();
+}
+
+void EditEngine::SetSpeller( Reference< XSpellChecker1 > const &xSpeller )
+{
+ pImpEditEngine->SetSpeller( xSpeller );
+}
+
+Reference< XSpellChecker1 > const & EditEngine::GetSpeller()
+{
+ return pImpEditEngine->GetSpeller();
+}
+
+void EditEngine::SetHyphenator( Reference< XHyphenator > const & xHyph )
+{
+ pImpEditEngine->SetHyphenator( xHyph );
+}
+
+void EditEngine::GetAllMisspellRanges( std::vector<editeng::MisspellRanges>& rRanges ) const
+{
+ pImpEditEngine->GetAllMisspellRanges(rRanges);
+}
+
+void EditEngine::SetAllMisspellRanges( const std::vector<editeng::MisspellRanges>& rRanges )
+{
+ pImpEditEngine->SetAllMisspellRanges(rRanges);
+}
+
+void EditEngine::SetForbiddenCharsTable(const std::shared_ptr<SvxForbiddenCharactersTable>& xForbiddenChars)
+{
+ ImpEditEngine::SetForbiddenCharsTable( xForbiddenChars );
+}
+
+void EditEngine::SetDefaultLanguage( LanguageType eLang )
+{
+ pImpEditEngine->SetDefaultLanguage( eLang );
+}
+
+LanguageType EditEngine::GetDefaultLanguage() const
+{
+ return pImpEditEngine->GetDefaultLanguage();
+}
+
+bool EditEngine::SpellNextDocument()
+{
+ return false;
+}
+
+EESpellState EditEngine::HasSpellErrors()
+{
+ if ( !pImpEditEngine->GetSpeller().is() )
+ return EESpellState::NoSpeller;
+
+ return pImpEditEngine->HasSpellErrors();
+}
+
+void EditEngine::ClearSpellErrors()
+{
+ pImpEditEngine->ClearSpellErrors();
+}
+
+bool EditEngine::SpellSentence(EditView const & rView, svx::SpellPortions& rToFill )
+{
+ return pImpEditEngine->SpellSentence( rView, rToFill );
+}
+
+void EditEngine::PutSpellingToSentenceStart( EditView const & rEditView )
+{
+ pImpEditEngine->PutSpellingToSentenceStart( rEditView );
+}
+
+void EditEngine::ApplyChangedSentence(EditView const & rEditView, const svx::SpellPortions& rNewPortions, bool bRecheck )
+{
+ pImpEditEngine->ApplyChangedSentence( rEditView, rNewPortions, bRecheck );
+}
+
+bool EditEngine::HasConvertibleTextPortion( LanguageType nLang )
+{
+ return pImpEditEngine->HasConvertibleTextPortion( nLang );
+}
+
+bool EditEngine::ConvertNextDocument()
+{
+ return false;
+}
+
+bool EditEngine::HasText( const SvxSearchItem& rSearchItem )
+{
+ return pImpEditEngine->HasText( rSearchItem );
+}
+
+void EditEngine::setGlobalScale(double fFontScaleX, double fFontScaleY, double fSpacingScaleX, double fSpacingScaleY)
+{
+ pImpEditEngine->setScale(fFontScaleX, fFontScaleY, fSpacingScaleX, fSpacingScaleY);
+}
+
+void EditEngine::getGlobalSpacingScale(double& rX, double& rY) const
+{
+ pImpEditEngine->getSpacingScale(rX, rY);
+}
+
+basegfx::B2DTuple EditEngine::getGlobalSpacingScale() const
+{
+ double x = 0.0;
+ double y = 0.0;
+ pImpEditEngine->getSpacingScale(x, y);
+ return {x, y};
+}
+
+void EditEngine::getGlobalFontScale(double& rX, double& rY) const
+{
+ pImpEditEngine->getFontScale(rX, rY);
+}
+
+basegfx::B2DTuple EditEngine::getGlobalFontScale() const
+{
+ double x = 0.0;
+ double y = 0.0;
+ pImpEditEngine->getFontScale(x, y);
+ return {x, y};
+}
+
+void EditEngine::setRoundFontSizeToPt(bool bRound) const
+{
+ pImpEditEngine->setRoundToNearestPt(bRound);
+}
+
+bool EditEngine::ShouldCreateBigTextObject() const
+{
+ sal_Int32 nTextPortions = 0;
+ sal_Int32 nParas = pImpEditEngine->GetEditDoc().Count();
+ for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ )
+ {
+ ParaPortion* pParaPortion = pImpEditEngine->GetParaPortions()[nPara];
+ nTextPortions = nTextPortions + pParaPortion->GetTextPortions().Count();
+ }
+ return nTextPortions >= pImpEditEngine->GetBigTextObjectStart();
+}
+
+sal_uInt16 EditEngine::GetFieldCount( sal_Int32 nPara ) const
+{
+ sal_uInt16 nFields = 0;
+ ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara );
+ if ( pNode )
+ {
+ for (auto const& attrib : pNode->GetCharAttribs().GetAttribs())
+ {
+ if (attrib->Which() == EE_FEATURE_FIELD)
+ ++nFields;
+ }
+ }
+
+ return nFields;
+}
+
+EFieldInfo EditEngine::GetFieldInfo( sal_Int32 nPara, sal_uInt16 nField ) const
+{
+ ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara );
+ if ( pNode )
+ {
+ sal_uInt16 nCurrentField = 0;
+ for (auto const& attrib : pNode->GetCharAttribs().GetAttribs())
+ {
+ const EditCharAttrib& rAttr = *attrib;
+ if (rAttr.Which() == EE_FEATURE_FIELD)
+ {
+ if ( nCurrentField == nField )
+ {
+ const SvxFieldItem* p = static_cast<const SvxFieldItem*>(rAttr.GetItem());
+ EFieldInfo aInfo(*p, nPara, rAttr.GetStart());
+ aInfo.aCurrentText = static_cast<const EditCharAttribField&>(rAttr).GetFieldValue();
+ return aInfo;
+ }
+
+ ++nCurrentField;
+ }
+ }
+ }
+ return EFieldInfo();
+}
+
+
+bool EditEngine::UpdateFields()
+{
+ bool bChanges = pImpEditEngine->UpdateFields();
+ if ( bChanges && pImpEditEngine->IsUpdateLayout())
+ pImpEditEngine->FormatAndLayout();
+ return bChanges;
+}
+
+bool EditEngine::UpdateFieldsOnly()
+{
+ return pImpEditEngine->UpdateFields();
+}
+
+void EditEngine::RemoveFields( const std::function<bool ( const SvxFieldData* )>& isFieldData )
+{
+ pImpEditEngine->UpdateFields();
+
+ sal_Int32 nParas = pImpEditEngine->GetEditDoc().Count();
+ for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ )
+ {
+ ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( nPara );
+ const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs();
+ for (size_t nAttr = rAttrs.size(); nAttr; )
+ {
+ const EditCharAttrib& rAttr = *rAttrs[--nAttr];
+ if (rAttr.Which() == EE_FEATURE_FIELD)
+ {
+ const SvxFieldData* pFldData = static_cast<const SvxFieldItem*>(rAttr.GetItem())->GetField();
+ if ( pFldData && ( isFieldData( pFldData ) ) )
+ {
+ DBG_ASSERT( dynamic_cast<const SvxFieldItem*>(rAttr.GetItem()), "no field item..." );
+ EditSelection aSel( EditPaM(pNode, rAttr.GetStart()), EditPaM(pNode, rAttr.GetEnd()) );
+ OUString aFieldText = static_cast<const EditCharAttribField&>(rAttr).GetFieldValue();
+ pImpEditEngine->ImpInsertText( aSel, aFieldText );
+ }
+ }
+ }
+ }
+}
+
+bool EditEngine::HasOnlineSpellErrors() const
+{
+ sal_Int32 nNodes = pImpEditEngine->GetEditDoc().Count();
+ for ( sal_Int32 n = 0; n < nNodes; n++ )
+ {
+ ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( n );
+ if ( pNode->GetWrongList() && !pNode->GetWrongList()->empty() )
+ return true;
+ }
+ return false;
+}
+
+void EditEngine::CompleteOnlineSpelling()
+{
+ if ( pImpEditEngine->GetStatus().DoOnlineSpelling() )
+ {
+ if( !pImpEditEngine->IsFormatted() )
+ pImpEditEngine->FormatAndLayout();
+
+ pImpEditEngine->StopOnlineSpellTimer();
+ pImpEditEngine->DoOnlineSpelling( nullptr, true, false );
+ }
+}
+
+sal_Int32 EditEngine::FindParagraph( tools::Long nDocPosY )
+{
+ return pImpEditEngine->GetParaPortions().FindParagraph( nDocPosY );
+}
+
+EPosition EditEngine::FindDocPosition( const Point& rDocPos ) const
+{
+ EPosition aPos;
+ // From the point of the API, this is const...
+ EditPaM aPaM = const_cast<EditEngine*>(this)->pImpEditEngine->GetPaM( rDocPos, false );
+ if ( aPaM.GetNode() )
+ {
+ aPos.nPara = pImpEditEngine->maEditDoc.GetPos( aPaM.GetNode() );
+ aPos.nIndex = aPaM.GetIndex();
+ }
+ return aPos;
+}
+
+tools::Rectangle EditEngine::GetCharacterBounds( const EPosition& rPos ) const
+{
+ tools::Rectangle aBounds;
+ ContentNode* pNode = pImpEditEngine->GetEditDoc().GetObject( rPos.nPara );
+
+ // Check against index, not paragraph
+ if ( pNode && ( rPos.nIndex < pNode->Len() ) )
+ {
+ aBounds = pImpEditEngine->PaMtoEditCursor( EditPaM( pNode, rPos.nIndex ), GetCursorFlags::TextOnly );
+ tools::Rectangle aR2 = pImpEditEngine->PaMtoEditCursor( EditPaM( pNode, rPos.nIndex+1 ), GetCursorFlags::TextOnly|GetCursorFlags::EndOfLine );
+ if ( aR2.Right() > aBounds.Right() )
+ aBounds.SetRight( aR2.Right() );
+ }
+ return aBounds;
+}
+
+ParagraphInfos EditEngine::GetParagraphInfos( sal_Int32 nPara )
+{
+
+ // This only works if not already in the format ...
+ if ( !pImpEditEngine->IsFormatted() )
+ pImpEditEngine->FormatDoc();
+
+ ParagraphInfos aInfos;
+ aInfos.bValid = pImpEditEngine->IsFormatted();
+ if ( pImpEditEngine->IsFormatted() )
+ {
+ const ParaPortion* pParaPortion = pImpEditEngine->GetParaPortions()[nPara];
+ const EditLine* pLine = (pParaPortion && pParaPortion->GetLines().Count()) ?
+ &pParaPortion->GetLines()[0] : nullptr;
+ DBG_ASSERT( pParaPortion && pLine, "GetParagraphInfos - Paragraph out of range" );
+ if ( pParaPortion && pLine )
+ {
+ aInfos.nFirstLineHeight = pLine->GetHeight();
+ aInfos.nFirstLineTextHeight = pLine->GetTxtHeight();
+ aInfos.nFirstLineMaxAscent = pLine->GetMaxAscent();
+ }
+ }
+ return aInfos;
+}
+
+css::uno::Reference< css::datatransfer::XTransferable >
+ EditEngine::CreateTransferable( const ESelection& rSelection ) const
+{
+ EditSelection aSel( pImpEditEngine->CreateSel( rSelection ) );
+ return pImpEditEngine->CreateTransferable( aSel );
+}
+
+
+// ====================== Virtual Methods ========================
+
+void EditEngine::DrawingText( const Point&, const OUString&, sal_Int32, sal_Int32,
+ std::span<const sal_Int32>, std::span<const sal_Bool>,
+ const SvxFont&, sal_Int32 /*nPara*/, sal_uInt8 /*nRightToLeft*/,
+ const EEngineData::WrongSpellVector*, const SvxFieldData*, bool, bool,
+ const css::lang::Locale*, const Color&, const Color&)
+
+{
+}
+
+void EditEngine::DrawingTab( const Point& /*rStartPos*/, tools::Long /*nWidth*/,
+ const OUString& /*rChar*/, const SvxFont& /*rFont*/,
+ sal_Int32 /*nPara*/, sal_uInt8 /*nRightToLeft*/, bool /*bEndOfLine*/,
+ bool /*bEndOfParagraph*/, const Color& /*rOverlineColor*/,
+ const Color& /*rTextLineColor*/)
+{
+}
+
+void EditEngine::PaintingFirstLine(sal_Int32, const Point&, const Point&, Degree10, OutputDevice&)
+{
+}
+
+void EditEngine::ParagraphInserted( sal_Int32 nPara )
+{
+
+ if ( GetNotifyHdl().IsSet() )
+ {
+ EENotify aNotify( EE_NOTIFY_PARAGRAPHINSERTED );
+ aNotify.nParagraph = nPara;
+ pImpEditEngine->GetNotifyHdl().Call( aNotify );
+ }
+}
+
+void EditEngine::ParagraphDeleted( sal_Int32 nPara )
+{
+
+ if ( GetNotifyHdl().IsSet() )
+ {
+ EENotify aNotify( EE_NOTIFY_PARAGRAPHREMOVED );
+ aNotify.nParagraph = nPara;
+ pImpEditEngine->GetNotifyHdl().Call( aNotify );
+ }
+}
+void EditEngine::ParagraphConnected( sal_Int32 /*nLeftParagraph*/, sal_Int32 /*nRightParagraph*/ )
+{
+}
+
+void EditEngine::ParaAttribsChanged( sal_Int32 /* nParagraph */ )
+{
+}
+
+void EditEngine::StyleSheetChanged( SfxStyleSheet* /* pStyle */ )
+{
+}
+
+void EditEngine::ParagraphHeightChanged( sal_Int32 nPara )
+{
+ if ( GetNotifyHdl().IsSet() )
+ {
+ EENotify aNotify( EE_NOTIFY_TextHeightChanged );
+ aNotify.nParagraph = nPara;
+ pImpEditEngine->GetNotifyHdl().Call( aNotify );
+ }
+
+ for (EditView* pView : pImpEditEngine->aEditViews)
+ pView->pImpEditView->ScrollStateChange();
+}
+
+OUString EditEngine::GetUndoComment( sal_uInt16 nId ) const
+{
+ OUString aComment;
+ switch ( nId )
+ {
+ case EDITUNDO_REMOVECHARS:
+ case EDITUNDO_CONNECTPARAS:
+ case EDITUNDO_DELCONTENT:
+ case EDITUNDO_DELETE:
+ case EDITUNDO_CUT:
+ aComment = EditResId(RID_EDITUNDO_DEL);
+ break;
+ case EDITUNDO_MOVEPARAGRAPHS:
+ case EDITUNDO_MOVEPARAS:
+ case EDITUNDO_DRAGANDDROP:
+ aComment = EditResId(RID_EDITUNDO_MOVE);
+ break;
+ case EDITUNDO_INSERTFEATURE:
+ case EDITUNDO_SPLITPARA:
+ case EDITUNDO_INSERTCHARS:
+ case EDITUNDO_PASTE:
+ case EDITUNDO_INSERT:
+ case EDITUNDO_READ:
+ aComment = EditResId(RID_EDITUNDO_INSERT);
+ break;
+ case EDITUNDO_REPLACEALL:
+ aComment = EditResId(RID_EDITUNDO_REPLACE);
+ break;
+ case EDITUNDO_ATTRIBS:
+ case EDITUNDO_PARAATTRIBS:
+ aComment = EditResId(RID_EDITUNDO_SETATTRIBS);
+ break;
+ case EDITUNDO_RESETATTRIBS:
+ aComment = EditResId(RID_EDITUNDO_RESETATTRIBS);
+ break;
+ case EDITUNDO_STYLESHEET:
+ aComment = EditResId(RID_EDITUNDO_SETSTYLE);
+ break;
+ case EDITUNDO_TRANSLITERATE:
+ aComment = EditResId(RID_EDITUNDO_TRANSLITERATE);
+ break;
+ case EDITUNDO_INDENTBLOCK:
+ case EDITUNDO_UNINDENTBLOCK:
+ aComment = EditResId(RID_EDITUNDO_INDENT);
+ break;
+ }
+ return aComment;
+}
+
+tools::Rectangle EditEngine::GetBulletArea( sal_Int32 )
+{
+ return tools::Rectangle( Point(), Point() );
+}
+
+OUString EditEngine::CalcFieldValue( const SvxFieldItem&, sal_Int32, sal_Int32, std::optional<Color>&, std::optional<Color>&, std::optional<FontLineStyle>& )
+{
+ return OUString(' ');
+}
+
+bool EditEngine::FieldClicked( const SvxFieldItem& )
+{
+ return false;
+}
+
+
+// ====================== Static Methods =======================
+
+rtl::Reference<SfxItemPool> EditEngine::CreatePool()
+{
+ return new EditEngineItemPool();
+}
+
+
+/** If we let the libc runtime clean us up, we trigger a crash */
+namespace
+{
+class TerminateListener : public ::cppu::WeakImplHelper< css::frame::XTerminateListener >
+{
+ void SAL_CALL queryTermination( const lang::EventObject& ) override
+ {}
+ void SAL_CALL notifyTermination( const lang::EventObject& ) override
+ {
+ pGlobalPool.clear();
+ }
+ virtual void SAL_CALL disposing( const ::css::lang::EventObject& ) override
+ {}
+};
+};
+
+SfxItemPool& EditEngine::GetGlobalItemPool()
+{
+ if ( !pGlobalPool )
+ {
+ pGlobalPool = CreatePool();
+#if defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+ // TerminateListener option not available, force it to leak
+ pGlobalPool->acquire();
+#else
+ uno::Reference< frame::XDesktop2 > xDesktop = frame::Desktop::create(comphelper::getProcessComponentContext());
+ uno::Reference< frame::XTerminateListener > xListener( new TerminateListener );
+ xDesktop->addTerminateListener( xListener );
+#endif
+ }
+ return *pGlobalPool;
+}
+
+void EditEngine::SetFontInfoInItemSet( SfxItemSet& rSet, const vcl::Font& rFont )
+{
+ SvxFont aSvxFont( rFont );
+ SetFontInfoInItemSet( rSet, aSvxFont );
+
+}
+
+void EditEngine::SetFontInfoInItemSet( SfxItemSet& rSet, const SvxFont& rFont )
+{
+ rSet.Put( SvxLanguageItem( rFont.GetLanguage(), EE_CHAR_LANGUAGE ) );
+ rSet.Put( SvxFontItem( rFont.GetFamilyType(), rFont.GetFamilyName(), OUString(), rFont.GetPitch(), rFont.GetCharSet(), EE_CHAR_FONTINFO ) );
+ rSet.Put( SvxFontHeightItem( rFont.GetFontSize().Height(), 100, EE_CHAR_FONTHEIGHT ) );
+ rSet.Put( SvxCharScaleWidthItem( 100, EE_CHAR_FONTWIDTH ) );
+ rSet.Put( SvxShadowedItem( rFont.IsShadow(), EE_CHAR_SHADOW ) );
+ rSet.Put( SvxEscapementItem( rFont.GetEscapement(), rFont.GetPropr(), EE_CHAR_ESCAPEMENT ) );
+ rSet.Put( SvxWeightItem( rFont.GetWeight(), EE_CHAR_WEIGHT ) );
+ rSet.Put( SvxColorItem( rFont.GetColor(), EE_CHAR_COLOR ) );
+ rSet.Put( SvxColorItem( rFont.GetFillColor(), EE_CHAR_BKGCOLOR ) );
+ rSet.Put( SvxUnderlineItem( rFont.GetUnderline(), EE_CHAR_UNDERLINE ) );
+ rSet.Put( SvxOverlineItem( rFont.GetOverline(), EE_CHAR_OVERLINE ) );
+ rSet.Put( SvxCrossedOutItem( rFont.GetStrikeout(), EE_CHAR_STRIKEOUT ) );
+ rSet.Put( SvxCaseMapItem( rFont.GetCaseMap(), EE_CHAR_CASEMAP ) );
+ rSet.Put( SvxPostureItem( rFont.GetItalic(), EE_CHAR_ITALIC ) );
+ rSet.Put( SvxContourItem( rFont.IsOutline(), EE_CHAR_OUTLINE ) );
+ rSet.Put( SvxAutoKernItem( rFont.IsKerning(), EE_CHAR_PAIRKERNING ) );
+ rSet.Put( SvxKerningItem( rFont.GetFixKerning(), EE_CHAR_KERNING ) );
+ rSet.Put( SvxWordLineModeItem( rFont.IsWordLineMode(), EE_CHAR_WLM ) );
+ rSet.Put( SvxEmphasisMarkItem( rFont.GetEmphasisMark(), EE_CHAR_EMPHASISMARK ) );
+ rSet.Put( SvxCharReliefItem( rFont.GetRelief(), EE_CHAR_RELIEF ) );
+}
+
+vcl::Font EditEngine::CreateFontFromItemSet( const SfxItemSet& rItemSet, SvtScriptType nScriptType )
+{
+ SvxFont aFont;
+ CreateFont( aFont, rItemSet, true, nScriptType );
+#if HAVE_P1155R3
+ return aFont;
+#else
+ return std::move(aFont);
+#endif
+}
+
+SvxFont EditEngine::CreateSvxFontFromItemSet( const SfxItemSet& rItemSet )
+{
+ SvxFont aFont;
+ CreateFont( aFont, rItemSet );
+ return aFont;
+}
+
+bool EditEngine::DoesKeyMoveCursor( const KeyEvent& rKeyEvent )
+{
+ bool bDoesMove = false;
+
+ switch ( rKeyEvent.GetKeyCode().GetCode() )
+ {
+ case KEY_UP:
+ case KEY_DOWN:
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ case KEY_HOME:
+ case KEY_END:
+ case KEY_PAGEUP:
+ case KEY_PAGEDOWN:
+ {
+ if ( !rKeyEvent.GetKeyCode().IsMod2() )
+ bDoesMove = true;
+ }
+ break;
+ }
+ return bDoesMove;
+}
+
+bool EditEngine::DoesKeyChangeText( const KeyEvent& rKeyEvent )
+{
+ bool bDoesChange = false;
+
+ KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction();
+ if ( eFunc != KeyFuncType::DONTKNOW )
+ {
+ switch ( eFunc )
+ {
+ case KeyFuncType::UNDO:
+ case KeyFuncType::REDO:
+ case KeyFuncType::CUT:
+ case KeyFuncType::PASTE: bDoesChange = true;
+ break;
+ default: // is then possibly edited below.
+ eFunc = KeyFuncType::DONTKNOW;
+ }
+ }
+ if ( eFunc == KeyFuncType::DONTKNOW )
+ {
+ switch ( rKeyEvent.GetKeyCode().GetCode() )
+ {
+ case KEY_DELETE:
+ case KEY_BACKSPACE: bDoesChange = true;
+ break;
+ case KEY_RETURN:
+ case KEY_TAB:
+ {
+ if ( !rKeyEvent.GetKeyCode().IsMod1() && !rKeyEvent.GetKeyCode().IsMod2() )
+ bDoesChange = true;
+ }
+ break;
+ default:
+ {
+ bDoesChange = IsSimpleCharInput( rKeyEvent );
+ }
+ }
+ }
+ return bDoesChange;
+}
+
+bool EditEngine::IsSimpleCharInput( const KeyEvent& rKeyEvent )
+{
+ return EditEngine::IsPrintable( rKeyEvent.GetCharCode() ) &&
+ ( KEY_MOD2 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT ) ) &&
+ ( KEY_MOD1 != (rKeyEvent.GetKeyCode().GetModifier() & ~KEY_SHIFT ) );
+}
+
+bool EditEngine::HasValidData( const css::uno::Reference< css::datatransfer::XTransferable >& rTransferable )
+{
+ bool bValidData = false;
+
+ if ( comphelper::LibreOfficeKit::isActive())
+ return true;
+
+ if ( rTransferable.is() )
+ {
+ // Every application that copies rtf or any other text format also copies plain text into the clipboard...
+ datatransfer::DataFlavor aFlavor;
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
+ bValidData = rTransferable->isDataFlavorSupported( aFlavor );
+ }
+
+ return bValidData;
+}
+
+/** sets a link that is called at the beginning of a drag operation at an edit view */
+void EditEngine::SetBeginDropHdl( const Link<EditView*,void>& rLink )
+{
+ pImpEditEngine->SetBeginDropHdl( rLink );
+}
+
+Link<EditView*,void> const & EditEngine::GetBeginDropHdl() const
+{
+ return pImpEditEngine->GetBeginDropHdl();
+}
+
+/** sets a link that is called at the end of a drag operation at an edit view */
+void EditEngine::SetEndDropHdl( const Link<EditView*,void>& rLink )
+{
+ pImpEditEngine->SetEndDropHdl( rLink );
+}
+
+Link<EditView*,void> const & EditEngine::GetEndDropHdl() const
+{
+ return pImpEditEngine->GetEndDropHdl();
+}
+
+void EditEngine::SetFirstWordCapitalization( bool bCapitalize )
+{
+ pImpEditEngine->SetFirstWordCapitalization( bCapitalize );
+}
+
+void EditEngine::SetReplaceLeadingSingleQuotationMark( bool bReplace )
+{
+ pImpEditEngine->SetReplaceLeadingSingleQuotationMark( bReplace );
+}
+
+bool EditEngine::IsHtmlImportHandlerSet() const
+{
+ return pImpEditEngine->aHtmlImportHdl.IsSet();
+}
+
+bool EditEngine::IsRtfImportHandlerSet() const
+{
+ return pImpEditEngine->aRtfImportHdl.IsSet();
+}
+
+bool EditEngine::IsImportRTFStyleSheetsSet() const
+{
+ return pImpEditEngine->GetStatus().DoImportRTFStyleSheets();
+}
+
+void EditEngine::CallHtmlImportHandler(HtmlImportInfo& rInfo)
+{
+ pImpEditEngine->aHtmlImportHdl.Call(rInfo);
+}
+
+void EditEngine::CallRtfImportHandler(RtfImportInfo& rInfo)
+{
+ pImpEditEngine->aRtfImportHdl.Call(rInfo);
+}
+
+EditPaM EditEngine::InsertParaBreak(const EditSelection& rEditSelection)
+{
+ return pImpEditEngine->ImpInsertParaBreak(rEditSelection);
+}
+
+EditPaM EditEngine::InsertLineBreak(const EditSelection& rEditSelection)
+{
+ return pImpEditEngine->InsertLineBreak(rEditSelection);
+}
+
+sal_Int32 EditEngine::GetOverflowingParaNum() const {
+ return pImpEditEngine->GetOverflowingParaNum();
+}
+
+sal_Int32 EditEngine::GetOverflowingLineNum() const {
+ return pImpEditEngine->GetOverflowingLineNum();
+}
+
+void EditEngine::ClearOverflowingParaNum() {
+ pImpEditEngine->ClearOverflowingParaNum();
+}
+
+bool EditEngine::IsPageOverflow() {
+ pImpEditEngine->CheckPageOverflow();
+ return pImpEditEngine->IsPageOverflow();
+}
+
+void EditEngine::DisableAttributeExpanding() {
+ pImpEditEngine->GetEditDoc().DisableAttributeExpanding();
+}
+
+void EditEngine::EnableSkipOutsideFormat(bool set)
+{
+ pImpEditEngine->EnableSkipOutsideFormat(set);
+}
+
+void EditEngine::SetLOKSpecialPaperSize(const Size& rSize)
+{
+ pImpEditEngine->SetLOKSpecialPaperSize(rSize);
+}
+
+const Size& EditEngine::GetLOKSpecialPaperSize() const
+{
+ return pImpEditEngine->GetLOKSpecialPaperSize();
+}
+
+EFieldInfo::EFieldInfo()
+{
+}
+
+
+EFieldInfo::EFieldInfo( const SvxFieldItem& rFieldItem, sal_Int32 nPara, sal_Int32 nPos ) :
+ pFieldItem( new SvxFieldItem( rFieldItem ) ),
+ aPosition( nPara, nPos )
+{
+}
+
+EFieldInfo::~EFieldInfo()
+{
+}
+
+EFieldInfo::EFieldInfo( const EFieldInfo& rFldInfo )
+{
+ *this = rFldInfo;
+}
+
+EFieldInfo& EFieldInfo::operator= ( const EFieldInfo& rFldInfo )
+{
+ if( this == &rFldInfo )
+ return *this;
+
+ pFieldItem.reset( rFldInfo.pFieldItem ? new SvxFieldItem( *rFldInfo.pFieldItem ) : nullptr );
+ aCurrentText = rFldInfo.aCurrentText;
+ aPosition = rFldInfo.aPosition;
+
+ return *this;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/editobj.cxx b/editeng/source/editeng/editobj.cxx
new file mode 100644
index 0000000000..762cac112d
--- /dev/null
+++ b/editeng/source/editeng/editobj.cxx
@@ -0,0 +1,759 @@
+/* -*- 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 <memory>
+
+#include <o3tl/safeint.hxx>
+#include <sal/log.hxx>
+
+#include <editeng/macros.hxx>
+#include <editeng/section.hxx>
+#include "editobj2.hxx"
+#include <editeng/editdata.hxx>
+#include <editeng/editeng.hxx>
+#include <editeng/flditem.hxx>
+
+#include <svl/sharedstringpool.hxx>
+
+#include <libxml/xmlwriter.h>
+#include <algorithm>
+#include <cassert>
+
+#if DEBUG_EDIT_ENGINE
+#include <iostream>
+using std::cout;
+using std::endl;
+#endif
+
+using namespace com::sun::star;
+
+
+XEditAttribute::XEditAttribute(SfxItemPool& rPool, const SfxPoolItem& rItem, sal_Int32 nS, sal_Int32 nE)
+: maItemHolder(rPool, &rItem)
+, nStart(nS)
+, nEnd(nE)
+{
+}
+
+bool XEditAttribute::IsFeature() const
+{
+ sal_uInt16 nWhich = GetItem()->Which();
+ return ((nWhich >= EE_FEATURE_START) && (nWhich <= EE_FEATURE_END));
+}
+
+void XEditAttribute::SetItem(SfxItemPool& rPool, const SfxPoolItem& rItem)
+{
+ maItemHolder = SfxPoolItemHolder(rPool, &rItem);
+}
+
+XParaPortionList::XParaPortionList(OutputDevice* pRefDev, sal_uInt32 nPW,
+ double fFontScaleX, double fFontScaleY,
+ double fSpacingScaleX, double fSpacingScaleY)
+ : pRefDevPtr(pRefDev)
+ , mfFontScaleX(fFontScaleX)
+ , mfFontScaleY(fFontScaleY)
+ , mfSpacingScaleX(fSpacingScaleX)
+ , mfSpacingScaleY(fSpacingScaleY)
+ , nPaperWidth(nPW)
+{
+}
+
+void XParaPortionList::push_back(XParaPortion* p)
+{
+ maList.push_back(std::unique_ptr<XParaPortion>(p));
+}
+
+const XParaPortion& XParaPortionList::operator [](size_t i) const
+{
+ return *maList[i];
+}
+
+ContentInfo::ContentInfo( SfxItemPool& rPool ) :
+ eFamily(SfxStyleFamily::Para),
+ aParaAttribs(rPool)
+{
+}
+
+// the real Copy constructor is nonsense, since I have to work with another Pool!
+ContentInfo::ContentInfo( const ContentInfo& rCopyFrom, SfxItemPool& rPoolToUse ) :
+ maText(rCopyFrom.maText),
+ aStyle(rCopyFrom.aStyle),
+ eFamily(rCopyFrom.eFamily),
+ aParaAttribs(rPoolToUse)
+{
+ // this should ensure that the Items end up in the correct Pool!
+ aParaAttribs.Set( rCopyFrom.GetParaAttribs() );
+
+ for (const XEditAttribute & rAttr : rCopyFrom.maCharAttribs)
+ {
+ maCharAttribs.emplace_back(rPoolToUse, *rAttr.GetItem(), rAttr.GetStart(), rAttr.GetEnd());
+ }
+
+ if ( rCopyFrom.GetWrongList() )
+ mpWrongs.reset(rCopyFrom.GetWrongList()->Clone());
+}
+
+ContentInfo::~ContentInfo()
+{
+ maCharAttribs.clear();
+}
+
+void ContentInfo::NormalizeString( svl::SharedStringPool& rPool )
+{
+ maText = rPool.intern(OUString(maText.getData()));
+}
+
+
+OUString ContentInfo::GetText() const
+{
+ rtl_uString* p = const_cast<rtl_uString*>(maText.getData());
+ return OUString(p);
+}
+
+void ContentInfo::SetText( const OUString& rStr )
+{
+ maText = svl::SharedString(rStr.pData, nullptr);
+}
+
+void ContentInfo::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("ContentInfo"));
+ (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("style"), BAD_CAST(aStyle.toUtf8().getStr()));
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("text"));
+ OUString aText = GetText();
+ // TODO share code with sax_fastparser::FastSaxSerializer::write().
+ (void)xmlTextWriterWriteString(pWriter, BAD_CAST(aText.replaceAll("\x01", "&#1;").toUtf8().getStr()));
+ (void)xmlTextWriterEndElement(pWriter);
+ aParaAttribs.dumpAsXml(pWriter);
+ for (auto const& rCharAttribs : maCharAttribs)
+ {
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("attribs"));
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("start"), "%" SAL_PRIdINT32, rCharAttribs.GetStart());
+ (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("end"), "%" SAL_PRIdINT32, rCharAttribs.GetEnd());
+ rCharAttribs.GetItem()->dumpAsXml(pWriter);
+ (void)xmlTextWriterEndElement(pWriter);
+ }
+ (void)xmlTextWriterEndElement(pWriter);
+}
+
+const WrongList* ContentInfo::GetWrongList() const
+{
+ return mpWrongs.get();
+}
+
+void ContentInfo::SetWrongList( WrongList* p )
+{
+ mpWrongs.reset(p);
+}
+
+// #i102062#
+bool ContentInfo::isWrongListEqual(const ContentInfo& rCompare) const
+{
+ if(GetWrongList() == rCompare.GetWrongList())
+ return true;
+
+ if(!GetWrongList() || !rCompare.GetWrongList())
+ return false;
+
+ return (*GetWrongList() == *rCompare.GetWrongList());
+}
+
+#if DEBUG_EDIT_ENGINE
+void ContentInfo::Dump() const
+{
+ cout << "--" << endl;
+ cout << "text: '" << OUString(const_cast<rtl_uString*>(maText.getData())) << "'" << endl;
+ cout << "style: '" << aStyle << "'" << endl;
+
+ for (auto const& attrib : aAttribs)
+ {
+ cout << "attribute: " << endl;
+ cout << " span: [begin=" << attrib.GetStart() << ", end=" << attrib.GetEnd() << "]" << endl;
+ cout << " feature: " << (attrib.IsFeature() ? "yes":"no") << endl;
+ }
+}
+#endif
+
+bool ContentInfo::Equals(const ContentInfo& rCompare, bool bComparePool) const
+{
+ return maText == rCompare.maText && aStyle == rCompare.aStyle && eFamily == rCompare.eFamily
+ && aParaAttribs.Equals(rCompare.aParaAttribs, bComparePool)
+ && maCharAttribs == rCompare.maCharAttribs;
+}
+
+EditTextObject::~EditTextObject() = default;
+
+std::unique_ptr<EditTextObject> EditTextObjectImpl::Clone() const
+{
+ return std::make_unique<EditTextObjectImpl>(*this);
+}
+
+bool EditTextObject::Equals( const EditTextObject& rCompare ) const
+{
+ return toImpl(*this).Equals(toImpl(rCompare), false /*bComparePool*/);
+}
+
+void EditTextObjectImpl::dumpAsXml(xmlTextWriterPtr pWriter) const
+{
+ bool bOwns = false;
+ if (!pWriter)
+ {
+ pWriter = xmlNewTextWriterFilename("editTextObject.xml", 0);
+ xmlTextWriterSetIndent(pWriter,1);
+ (void)xmlTextWriterSetIndentString(pWriter, BAD_CAST(" "));
+ (void)xmlTextWriterStartDocument(pWriter, nullptr, nullptr, nullptr);
+ bOwns = true;
+ }
+
+ (void)xmlTextWriterStartElement(pWriter, BAD_CAST("EditTextObject"));
+ sal_Int32 nCount = GetParagraphCount();
+ for (sal_Int32 i = 0; i < nCount; ++i)
+ {
+ maContents[i]->dumpAsXml(pWriter);
+ }
+ (void)xmlTextWriterEndElement(pWriter);
+
+ if (bOwns)
+ {
+ (void)xmlTextWriterEndDocument(pWriter);
+ xmlFreeTextWriter(pWriter);
+ }
+}
+
+#if DEBUG_EDIT_ENGINE
+void EditTextObjectImpl::Dump() const
+{
+ for (auto const& content : maContents)
+ content.Dump();
+}
+#endif
+
+static rtl::Reference<SfxItemPool> getEditEngineItemPool(SfxItemPool* pPool, MapUnit eDefaultMetric)
+{
+ // #i101239# ensure target is an EditEngineItemPool, so that at
+ // pool destruction time of an alien pool, the pool is still alive.
+ // When registering would happen at an alien pool which just uses an
+ // EditEngineItemPool as some sub-pool, that pool could already
+ // be decoupled and deleted which would lead to crashes.
+ for (; pPool; pPool = pPool->GetSecondaryPool())
+ if (dynamic_cast<EditEngineItemPool*>(pPool))
+ return pPool;
+
+ auto pRetval = EditEngine::CreatePool();
+ pRetval->SetDefaultMetric(eDefaultMetric);
+ return pRetval;
+}
+
+EditTextObjectImpl::EditTextObjectImpl(SfxItemPool* pP, MapUnit eDefaultMetric, bool bVertical,
+ TextRotation eRotation, SvtScriptType eScriptType)
+ : mpPool(getEditEngineItemPool(pP, eDefaultMetric))
+ , meUserType(OutlinerMode::DontKnow)
+ , meScriptType(eScriptType)
+ , meRotation(eRotation)
+ , meMetric(eDefaultMetric)
+ , mbVertical(bVertical)
+{
+}
+
+EditTextObjectImpl::EditTextObjectImpl( const EditTextObjectImpl& r )
+ : mpPool(r.mpPool)
+ , meUserType(r.meUserType)
+ , meScriptType(r.meScriptType)
+ , meRotation(r.meRotation)
+ , meMetric(r.meMetric)
+ , mbVertical(r.mbVertical)
+{
+ // Do not copy PortionInfo
+
+ maContents.reserve(r.maContents.size());
+ for (auto const& content : r.maContents)
+ maContents.push_back(std::unique_ptr<ContentInfo>(new ContentInfo(*content, *mpPool)));
+}
+
+EditTextObjectImpl::~EditTextObjectImpl()
+{
+ ClearPortionInfo();
+
+ // Remove contents before deleting the pool instance since each content
+ // has to access the pool instance in its destructor.
+ maContents.clear();
+}
+
+
+void EditTextObjectImpl::SetUserType( OutlinerMode n )
+{
+ meUserType = n;
+}
+
+void EditTextObjectImpl::NormalizeString( svl::SharedStringPool& rPool )
+{
+ for (auto const& content : maContents)
+ {
+ ContentInfo& rInfo = *content;
+ rInfo.NormalizeString(rPool);
+ }
+}
+
+std::vector<svl::SharedString> EditTextObjectImpl::GetSharedStrings() const
+{
+ std::vector<svl::SharedString> aSSs;
+ aSSs.reserve(maContents.size());
+ for (auto const& content : maContents)
+ {
+ const ContentInfo& rInfo = *content;
+ aSSs.push_back(rInfo.GetSharedString());
+ }
+ return aSSs;
+}
+
+bool EditTextObjectImpl::IsEffectivelyVertical() const
+{
+ return (mbVertical && meRotation == TextRotation::NONE) ||
+ (!mbVertical && meRotation != TextRotation::NONE);
+}
+
+bool EditTextObjectImpl::IsTopToBottom() const
+{
+ return (mbVertical && meRotation == TextRotation::NONE) ||
+ (!mbVertical && meRotation == TextRotation::TOPTOBOTTOM);
+}
+
+void EditTextObjectImpl::SetVertical( bool bVert)
+{
+ if (bVert != mbVertical)
+ {
+ mbVertical = bVert;
+ ClearPortionInfo();
+ }
+}
+
+bool EditTextObjectImpl::GetVertical() const
+{
+ return mbVertical;
+}
+
+void EditTextObjectImpl::SetRotation(TextRotation nRotation)
+{
+ if (meRotation != nRotation)
+ {
+ meRotation = nRotation;
+ ClearPortionInfo();
+ }
+}
+
+TextRotation EditTextObjectImpl::GetRotation() const
+{
+ return meRotation;
+}
+
+XEditAttribute EditTextObjectImpl::CreateAttrib( const SfxPoolItem& rItem, sal_Int32 nStart, sal_Int32 nEnd )
+{
+ return XEditAttribute(*mpPool, rItem, nStart, nEnd);
+}
+
+ContentInfo* EditTextObjectImpl::CreateAndInsertContent()
+{
+ maContents.push_back(std::unique_ptr<ContentInfo>(new ContentInfo(*mpPool)));
+ return maContents.back().get();
+}
+
+sal_Int32 EditTextObjectImpl::GetParagraphCount() const
+{
+ size_t nSize = maContents.size();
+ if (nSize > EE_PARA_MAX_COUNT)
+ {
+ SAL_WARN( "editeng", "EditTextObjectImpl::GetParagraphCount - overflow " << nSize);
+ return EE_PARA_MAX_COUNT;
+ }
+ return static_cast<sal_Int32>(nSize);
+}
+
+OUString EditTextObjectImpl::GetText(sal_Int32 nPara) const
+{
+ if (nPara < 0 || o3tl::make_unsigned(nPara) >= maContents.size())
+ return OUString();
+
+ return maContents[nPara]->GetText();
+}
+
+void EditTextObjectImpl::ClearPortionInfo()
+{
+ mpPortionInfo.reset();
+}
+
+bool EditTextObjectImpl::HasOnlineSpellErrors() const
+{
+ for (auto const& content : maContents)
+ {
+ if ( content->GetWrongList() && !content->GetWrongList()->empty() )
+ return true;
+ }
+ return false;
+}
+
+void EditTextObjectImpl::GetCharAttribs( sal_Int32 nPara, std::vector<EECharAttrib>& rLst ) const
+{
+ if (nPara < 0 || o3tl::make_unsigned(nPara) >= maContents.size())
+ return;
+
+ rLst.clear();
+ const ContentInfo& rC = *maContents[nPara];
+ for (const XEditAttribute & rAttr : rC.maCharAttribs)
+ {
+ EECharAttrib aEEAttr(rAttr.GetStart(), rAttr.GetEnd(), rAttr.GetItem());
+ rLst.push_back(aEEAttr);
+ }
+}
+
+bool EditTextObjectImpl::IsFieldObject() const
+{
+ return GetField() != nullptr;
+}
+
+const SvxFieldItem* EditTextObjectImpl::GetField() const
+{
+ if (maContents.size() == 1)
+ {
+ const ContentInfo& rC = *maContents[0];
+ if (rC.GetText().getLength() == 1)
+ {
+ size_t nAttribs = rC.maCharAttribs.size();
+ for (size_t nAttr = nAttribs; nAttr; )
+ {
+ const XEditAttribute& rX = rC.maCharAttribs[--nAttr];
+ if (rX.GetItem()->Which() == EE_FEATURE_FIELD)
+ return static_cast<const SvxFieldItem*>(rX.GetItem());
+ }
+ }
+ }
+ return nullptr;
+}
+
+const SvxFieldData* EditTextObjectImpl::GetFieldData(sal_Int32 nPara, size_t nPos, sal_Int32 nType) const
+{
+ if (nPara < 0 || o3tl::make_unsigned(nPara) >= maContents.size())
+ return nullptr;
+
+ const ContentInfo& rC = *maContents[nPara];
+ if (nPos >= rC.maCharAttribs.size())
+ // URL position is out-of-bound.
+ return nullptr;
+
+ size_t nCurPos = 0;
+ for (XEditAttribute const& rAttr : rC.maCharAttribs)
+ {
+ if (rAttr.GetItem()->Which() != EE_FEATURE_FIELD)
+ // Skip attributes that are not fields.
+ continue;
+
+ const SvxFieldItem* pField = static_cast<const SvxFieldItem*>(rAttr.GetItem());
+ const SvxFieldData* pFldData = pField->GetField();
+ if (nType != text::textfield::Type::UNSPECIFIED && nType != pFldData->GetClassId())
+ // Field type doesn't match. Skip it. UNSPECIFIED matches all field types.
+ continue;
+
+ if (nCurPos == nPos)
+ // Found it!
+ return pFldData;
+
+ ++nCurPos;
+ }
+
+ return nullptr; // field not found.
+}
+
+bool EditTextObjectImpl::HasField( sal_Int32 nType ) const
+{
+ size_t nParagraphs = maContents.size();
+ for (size_t nPara = 0; nPara < nParagraphs; ++nPara)
+ {
+ const ContentInfo& rC = *maContents[nPara];
+ size_t nAttrs = rC.maCharAttribs.size();
+ for (size_t nAttr = 0; nAttr < nAttrs; ++nAttr)
+ {
+ const XEditAttribute& rAttr = rC.maCharAttribs[nAttr];
+ if (rAttr.GetItem()->Which() != EE_FEATURE_FIELD)
+ continue;
+
+ if (nType == text::textfield::Type::UNSPECIFIED)
+ // Match any field type.
+ return true;
+
+ const SvxFieldData* pFldData = static_cast<const SvxFieldItem*>(rAttr.GetItem())->GetField();
+ if (pFldData && pFldData->GetClassId() == nType)
+ return true;
+ }
+ }
+ return false;
+}
+
+const SfxItemSet& EditTextObjectImpl::GetParaAttribs(sal_Int32 nPara) const
+{
+ const ContentInfo& rC = *maContents[nPara];
+ return rC.GetParaAttribs();
+}
+
+bool EditTextObjectImpl::RemoveCharAttribs( sal_uInt16 _nWhich )
+{
+ bool bChanged = false;
+
+ for ( size_t nPara = maContents.size(); nPara; )
+ {
+ ContentInfo& rC = *maContents[--nPara];
+
+ for (size_t nAttr = rC.maCharAttribs.size(); nAttr; )
+ {
+ XEditAttribute& rAttr = rC.maCharAttribs[--nAttr];
+ if ( !_nWhich || (rAttr.GetItem()->Which() == _nWhich) )
+ {
+ rC.maCharAttribs.erase(rC.maCharAttribs.begin()+nAttr);
+ bChanged = true;
+ }
+ }
+ }
+
+ if ( bChanged )
+ ClearPortionInfo();
+
+ return bChanged;
+}
+
+namespace {
+
+class FindByParagraph
+{
+ sal_Int32 mnPara;
+public:
+ explicit FindByParagraph(sal_Int32 nPara) : mnPara(nPara) {}
+ bool operator() (const editeng::Section& rAttr) const
+ {
+ return rAttr.mnParagraph == mnPara;
+ }
+};
+
+class FindBySectionStart
+{
+ sal_Int32 mnPara;
+ sal_Int32 mnStart;
+public:
+ FindBySectionStart(sal_Int32 nPara, sal_Int32 nStart) : mnPara(nPara), mnStart(nStart) {}
+ bool operator() (const editeng::Section& rAttr) const
+ {
+ return rAttr.mnParagraph == mnPara && rAttr.mnStart == mnStart;
+ }
+};
+
+}
+
+void EditTextObjectImpl::GetAllSections( std::vector<editeng::Section>& rAttrs ) const
+{
+ std::vector<editeng::Section> aAttrs;
+ aAttrs.reserve(maContents.size());
+ std::vector<size_t> aBorders;
+
+ for (size_t nPara = 0; nPara < maContents.size(); ++nPara)
+ {
+ aBorders.clear();
+ const ContentInfo& rC = *maContents[nPara];
+ aBorders.push_back(0);
+ aBorders.push_back(rC.GetText().getLength());
+ for (const XEditAttribute & rAttr : rC.maCharAttribs)
+ {
+ const SfxPoolItem* pItem = rAttr.GetItem();
+ if (!pItem)
+ continue;
+
+ aBorders.push_back(rAttr.GetStart());
+ aBorders.push_back(rAttr.GetEnd());
+ }
+
+ // Sort and remove duplicates for each paragraph.
+ std::sort(aBorders.begin(), aBorders.end());
+ auto itUniqueEnd = std::unique(aBorders.begin(), aBorders.end());
+ aBorders.erase(itUniqueEnd, aBorders.end());
+
+ // Create storage for each section. Note that this creates storage even
+ // for unformatted sections. The entries are sorted first by paragraph,
+ // then by section positions. They don't overlap with each other.
+
+ if (aBorders.size() == 1 && aBorders[0] == 0)
+ {
+ // Empty paragraph. Push an empty section.
+ aAttrs.emplace_back(nPara, 0, 0);
+ continue;
+ }
+
+ auto itBorder = aBorders.begin(), itBorderEnd = aBorders.end();
+ size_t nPrev = *itBorder;
+ size_t nCur;
+ for (++itBorder; itBorder != itBorderEnd; ++itBorder, nPrev = nCur)
+ {
+ nCur = *itBorder;
+ aAttrs.emplace_back(nPara, nPrev, nCur);
+ }
+ }
+
+ if (aAttrs.empty())
+ return;
+
+ // Go through all formatted paragraphs, and store format items.
+ std::vector<editeng::Section>::iterator itAttr = aAttrs.begin();
+ for (sal_Int32 nPara = 0; nPara < static_cast<sal_Int32>(maContents.size()); ++nPara)
+ {
+ const ContentInfo& rC = *maContents[nPara];
+
+ itAttr = std::find_if(itAttr, aAttrs.end(), FindByParagraph(nPara));
+ if (itAttr == aAttrs.end())
+ {
+ // This should never happen. There is a logic error somewhere...
+ assert(false);
+ return;
+ }
+
+ for (const XEditAttribute & rXAttr : rC.maCharAttribs)
+ {
+ const SfxPoolItem* pItem = rXAttr.GetItem();
+ if (!pItem)
+ continue;
+
+ sal_Int32 nStart = rXAttr.GetStart(), nEnd = rXAttr.GetEnd();
+
+ // Find the container whose start position matches.
+ std::vector<editeng::Section>::iterator itCurAttr = std::find_if(itAttr, aAttrs.end(), FindBySectionStart(nPara, nStart));
+ if (itCurAttr == aAttrs.end())
+ {
+ // This should never happen. There is a logic error somewhere...
+ assert(false);
+ return;
+ }
+
+ for (; itCurAttr != aAttrs.end() && itCurAttr->mnParagraph == nPara && itCurAttr->mnEnd <= nEnd; ++itCurAttr)
+ {
+ editeng::Section& rSecAttr = *itCurAttr;
+ // serious bug: will cause duplicate attributes to be exported
+ if (std::none_of(rSecAttr.maAttributes.begin(), rSecAttr.maAttributes.end(),
+ [&pItem](SfxPoolItem const*const pIt)
+ { return pIt->Which() == pItem->Which(); }))
+ {
+ rSecAttr.maAttributes.push_back(pItem);
+ }
+ else
+ {
+ SAL_WARN("editeng", "GetAllSections(): duplicate attribute suppressed");
+ }
+ }
+ }
+ }
+
+ rAttrs.swap(aAttrs);
+}
+
+void EditTextObjectImpl::GetStyleSheet(sal_Int32 nPara, OUString& rName, SfxStyleFamily& rFamily) const
+{
+ if (nPara < 0 || o3tl::make_unsigned(nPara) >= maContents.size())
+ return;
+
+ const ContentInfo& rC = *maContents[nPara];
+ rName = rC.GetStyle();
+ rFamily = rC.GetFamily();
+}
+
+void EditTextObjectImpl::SetStyleSheet(sal_Int32 nPara, const OUString& rName, const SfxStyleFamily& rFamily)
+{
+ if (nPara < 0 || o3tl::make_unsigned(nPara) >= maContents.size())
+ return;
+
+ ContentInfo& rC = *maContents[nPara];
+ rC.SetStyle(rName);
+ rC.SetFamily(rFamily);
+}
+
+bool EditTextObjectImpl::ImpChangeStyleSheets(
+ std::u16string_view rOldName, SfxStyleFamily eOldFamily,
+ const OUString& rNewName, SfxStyleFamily eNewFamily )
+{
+ const size_t nParagraphs = maContents.size();
+ bool bChanges = false;
+
+ for (size_t nPara = 0; nPara < nParagraphs; ++nPara)
+ {
+ ContentInfo& rC = *maContents[nPara];
+ if ( rC.GetFamily() == eOldFamily )
+ {
+ if ( rC.GetStyle() == rOldName )
+ {
+ rC.SetStyle(rNewName);
+ rC.SetFamily(eNewFamily);
+ bChanges = true;
+ }
+ }
+ }
+ return bChanges;
+}
+
+bool EditTextObjectImpl::ChangeStyleSheets(
+ std::u16string_view rOldName, SfxStyleFamily eOldFamily,
+ const OUString& rNewName, SfxStyleFamily eNewFamily)
+{
+ bool bChanges = ImpChangeStyleSheets( rOldName, eOldFamily, rNewName, eNewFamily );
+ if ( bChanges )
+ ClearPortionInfo();
+
+ return bChanges;
+}
+
+void EditTextObjectImpl::ChangeStyleSheetName( SfxStyleFamily eFamily,
+ std::u16string_view rOldName, const OUString& rNewName )
+{
+ ImpChangeStyleSheets( rOldName, eFamily, rNewName, eFamily );
+}
+
+bool EditTextObjectImpl::operator==( const EditTextObject& rCompare ) const
+{
+ return Equals(toImpl(rCompare), true);
+}
+
+bool EditTextObjectImpl::Equals( const EditTextObjectImpl& rCompare, bool bComparePool ) const
+{
+ if( this == &rCompare )
+ return true;
+
+ if( ( bComparePool && mpPool != rCompare.mpPool ) ||
+ ( meMetric != rCompare.meMetric ) ||
+ ( meUserType!= rCompare.meUserType ) ||
+ ( meScriptType != rCompare.meScriptType ) ||
+ ( mbVertical != rCompare.mbVertical ) ||
+ ( meRotation != rCompare.meRotation ) )
+ return false;
+
+ return std::equal(
+ maContents.begin(), maContents.end(), rCompare.maContents.begin(), rCompare.maContents.end(),
+ [bComparePool](const auto& c1, const auto& c2) { return c1->Equals(*c2, bComparePool); });
+}
+
+// #i102062#
+bool EditTextObjectImpl::isWrongListEqual(const EditTextObject& rComp) const
+{
+ const EditTextObjectImpl& rCompare = toImpl(rComp);
+ return std::equal(
+ maContents.begin(), maContents.end(), rCompare.maContents.begin(), rCompare.maContents.end(),
+ [](const auto& c1, const auto& c2) { return c1->isWrongListEqual(*c2); });
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/editobj2.hxx b/editeng/source/editeng/editobj2.hxx
new file mode 100644
index 0000000000..fd1f1437e9
--- /dev/null
+++ b/editeng/source/editeng/editobj2.hxx
@@ -0,0 +1,282 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <editeng/editobj.hxx>
+#include <editeng/fieldupdater.hxx>
+#include <editeng/outliner.hxx>
+#include <editdoc.hxx>
+
+#include <svl/sharedstring.hxx>
+#include <svl/languageoptions.hxx>
+#include <tools/long.hxx>
+#include <tools/mapunit.hxx>
+
+#include <memory>
+#include <vector>
+
+namespace editeng {
+
+struct Section;
+
+}
+
+namespace svl {
+
+class SharedStringPool;
+
+}
+
+class XEditAttribute
+{
+private:
+ SfxPoolItemHolder maItemHolder;
+ sal_Int32 nStart;
+ sal_Int32 nEnd;
+
+public:
+ XEditAttribute(SfxItemPool&, const SfxPoolItem&, sal_Int32 nStart, sal_Int32 nEnd );
+
+ const SfxPoolItem* GetItem() const { return maItemHolder.getItem(); }
+
+ sal_Int32& GetStart() { return nStart; }
+ sal_Int32& GetEnd() { return nEnd; }
+
+ sal_Int32 GetStart() const { return nStart; }
+ sal_Int32 GetEnd() const { return nEnd; }
+
+ sal_Int32 GetLen() const { return nEnd-nStart; }
+
+ bool IsFeature() const;
+ void SetItem(SfxItemPool&, const SfxPoolItem&);
+
+ inline bool operator==( const XEditAttribute& rCompare ) const;
+};
+
+inline bool XEditAttribute::operator==( const XEditAttribute& rCompare ) const
+{
+ return (nStart == rCompare.nStart) &&
+ (nEnd == rCompare.nEnd) &&
+ SfxPoolItem::areSame(GetItem(), rCompare.GetItem());
+}
+
+struct XParaPortion
+{
+ tools::Long nHeight;
+ sal_uInt16 nFirstLineOffset;
+
+ EditLineList aLines;
+ TextPortionList aTextPortions;
+};
+
+class XParaPortionList
+{
+ typedef std::vector<std::unique_ptr<XParaPortion> > ListType;
+ ListType maList;
+
+ VclPtr<OutputDevice> pRefDevPtr;
+ double mfFontScaleX;
+ double mfFontScaleY;
+ double mfSpacingScaleX;
+ double mfSpacingScaleY;
+ sal_uInt32 nPaperWidth;
+
+public:
+ XParaPortionList(OutputDevice* pRefDev, sal_uInt32 nPW, double fFontScaleX, double fFontScaleY, double fSpacingScaleX, double fSpacingScaleY);
+
+ void push_back(XParaPortion* p);
+ const XParaPortion& operator[](size_t i) const;
+
+ OutputDevice* GetRefDevPtr() const { return pRefDevPtr; }
+ sal_uInt32 GetPaperWidth() const { return nPaperWidth; }
+ bool RefDevIsVirtual() const {return pRefDevPtr->IsVirtual();}
+ const MapMode& GetRefMapMode() const { return pRefDevPtr->GetMapMode(); }
+ double getFontScaleX() const { return mfFontScaleX; }
+ double getFontScaleY() const { return mfFontScaleY; }
+ double getSpacingScaleX() const { return mfSpacingScaleX; }
+ double getSpacingScaleY() const { return mfSpacingScaleY; }
+};
+
+class ContentInfo
+{
+ friend class EditTextObjectImpl;
+
+private:
+ svl::SharedString maText;
+ OUString aStyle;
+
+ std::vector<XEditAttribute> maCharAttribs;
+ SfxStyleFamily eFamily;
+ SfxItemSetFixed<EE_PARA_START, EE_CHAR_END> aParaAttribs;
+ std::unique_ptr<WrongList>
+ mpWrongs;
+
+ ContentInfo( SfxItemPool& rPool );
+ ContentInfo( const ContentInfo& rCopyFrom, SfxItemPool& rPoolToUse );
+
+public:
+ ~ContentInfo();
+ ContentInfo(const ContentInfo&) = delete;
+ ContentInfo& operator=(const ContentInfo&) = delete;
+
+ void NormalizeString( svl::SharedStringPool& rPool );
+ const svl::SharedString& GetSharedString() const { return maText;}
+ OUString GetText() const;
+ void SetText( const OUString& rStr );
+
+ void dumpAsXml(xmlTextWriterPtr pWriter) const;
+
+ const std::vector<XEditAttribute>& GetCharAttribs() const { return maCharAttribs; }
+ std::vector<XEditAttribute>& GetCharAttribs() { return maCharAttribs; }
+
+ const OUString& GetStyle() const { return aStyle; }
+ SfxStyleFamily GetFamily() const { return eFamily; }
+
+ void SetStyle(const OUString& rStyle) { aStyle = rStyle; }
+ void SetFamily(const SfxStyleFamily& rFamily) { eFamily = rFamily; }
+
+ const SfxItemSet& GetParaAttribs() const { return aParaAttribs; }
+ SfxItemSet& GetParaAttribs() { return aParaAttribs; }
+
+ const WrongList* GetWrongList() const;
+ void SetWrongList( WrongList* p );
+ bool Equals( const ContentInfo& rCompare, bool bComparePool ) const;
+
+ // #i102062#
+ bool isWrongListEqual(const ContentInfo& rCompare) const;
+
+#if DEBUG_EDIT_ENGINE
+ void Dump() const;
+#endif
+};
+
+class EditTextObjectImpl final : public EditTextObject
+{
+public:
+ typedef std::vector<std::unique_ptr<ContentInfo> > ContentInfosType;
+
+private:
+ ContentInfosType maContents;
+ rtl::Reference<SfxItemPool> mpPool;
+ std::unique_ptr<XParaPortionList> mpPortionInfo;
+
+ OutlinerMode meUserType;
+ SvtScriptType meScriptType;
+ TextRotation meRotation;
+ MapUnit meMetric;
+
+ bool mbVertical;
+
+ bool ImpChangeStyleSheets( std::u16string_view rOldName, SfxStyleFamily eOldFamily,
+ const OUString& rNewName, SfxStyleFamily eNewFamily );
+
+public:
+ EditTextObjectImpl(SfxItemPool* pPool, MapUnit eDefaultMetric, bool bVertical,
+ TextRotation eRotation, SvtScriptType eScriptType);
+ EditTextObjectImpl( const EditTextObjectImpl& r );
+ virtual ~EditTextObjectImpl() override;
+
+ EditTextObjectImpl& operator=(const EditTextObjectImpl&) = delete;
+
+ virtual OutlinerMode GetUserType() const override { return meUserType;}
+ virtual void SetUserType( OutlinerMode n ) override;
+
+ virtual void NormalizeString( svl::SharedStringPool& rPool ) override;
+ virtual std::vector<svl::SharedString> GetSharedStrings() const override;
+
+ virtual bool IsEffectivelyVertical() const override;
+ virtual bool GetVertical() const override;
+ virtual bool IsTopToBottom() const override;
+ virtual void SetVertical( bool bVert) override;
+ virtual void SetRotation(TextRotation nRotation) override;
+ virtual TextRotation GetRotation() const override;
+
+ virtual SvtScriptType GetScriptType() const override { return meScriptType;}
+
+ virtual std::unique_ptr<EditTextObject> Clone() const override;
+
+ ContentInfo* CreateAndInsertContent();
+ XEditAttribute CreateAttrib( const SfxPoolItem& rItem, sal_Int32 nStart, sal_Int32 nEnd );
+
+ ContentInfosType& GetContents() { return maContents;}
+ const ContentInfosType& GetContents() const { return maContents;}
+ SfxItemPool* GetPool() { return mpPool.get(); }
+ virtual const SfxItemPool* GetPool() const override { return mpPool.get(); }
+ XParaPortionList* GetPortionInfo() const { return mpPortionInfo.get(); }
+ void SetPortionInfo( std::unique_ptr<XParaPortionList> pP )
+ { mpPortionInfo = std::move(pP); }
+
+ virtual sal_Int32 GetParagraphCount() const override;
+ virtual OUString GetText(sal_Int32 nParagraph) const override;
+
+ virtual void ClearPortionInfo() override;
+
+ virtual bool HasOnlineSpellErrors() const override;
+
+ virtual void GetCharAttribs( sal_Int32 nPara, std::vector<EECharAttrib>& rLst ) const override;
+
+ virtual bool RemoveCharAttribs( sal_uInt16 nWhich ) override;
+
+ virtual void GetAllSections( std::vector<editeng::Section>& rAttrs ) const override;
+
+ virtual bool IsFieldObject() const override;
+ virtual const SvxFieldItem* GetField() const override;
+ virtual const SvxFieldData* GetFieldData(sal_Int32 nPara, size_t nPos, sal_Int32 nType) const override;
+
+ virtual bool HasField( sal_Int32 nType = css::text::textfield::Type::UNSPECIFIED ) const override;
+
+ virtual const SfxItemSet& GetParaAttribs(sal_Int32 nPara) const override;
+
+ virtual void GetStyleSheet(sal_Int32 nPara, OUString& rName, SfxStyleFamily& eFamily) const override;
+ virtual void SetStyleSheet(sal_Int32 nPara, const OUString& rName, const SfxStyleFamily& eFamily) override;
+ virtual bool ChangeStyleSheets(
+ std::u16string_view rOldName, SfxStyleFamily eOldFamily, const OUString& rNewName, SfxStyleFamily eNewFamily) override;
+ virtual void ChangeStyleSheetName(SfxStyleFamily eFamily, std::u16string_view rOldName, const OUString& rNewName) override;
+
+ virtual editeng::FieldUpdater GetFieldUpdater() override { return editeng::FieldUpdater(*this); }
+
+ bool HasMetric() const { return meMetric != MapUnit::LASTENUMDUMMY; }
+ MapUnit GetMetric() const { return meMetric; }
+
+ virtual bool operator==( const EditTextObject& rCompare ) const override;
+ bool Equals( const EditTextObjectImpl& rCompare, bool bComparePool ) const;
+
+ // #i102062#
+ virtual bool isWrongListEqual(const EditTextObject& rCompare) const override;
+
+#if DEBUG_EDIT_ENGINE
+ virtual void Dump() const override;
+#endif
+ virtual void dumpAsXml(xmlTextWriterPtr pWriter) const override;
+};
+
+inline EditTextObjectImpl& toImpl(EditTextObject& rObj)
+{
+ assert(dynamic_cast<EditTextObjectImpl*>(&rObj));
+ return static_cast<EditTextObjectImpl&>(rObj);
+}
+
+inline const EditTextObjectImpl& toImpl(const EditTextObject& rObj)
+{
+ assert(dynamic_cast<const EditTextObjectImpl*>(&rObj));
+ return static_cast<const EditTextObjectImpl&>(rObj);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/editsel.cxx b/editeng/source/editeng/editsel.cxx
new file mode 100644
index 0000000000..3aeed7a6e3
--- /dev/null
+++ b/editeng/source/editeng/editsel.cxx
@@ -0,0 +1,94 @@
+/* -*- 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 "editsel.hxx"
+#include "impedit.hxx"
+#include <editeng/editview.hxx>
+
+
+
+EditSelFunctionSet::EditSelFunctionSet()
+{
+ pCurView = nullptr;
+}
+
+void EditSelFunctionSet::CreateAnchor()
+{
+ if ( pCurView )
+ pCurView->pImpEditView->CreateAnchor();
+}
+
+void EditSelFunctionSet::DestroyAnchor()
+{
+ // Only with multiple selection
+}
+
+void EditSelFunctionSet::SetCursorAtPoint( const Point& rPointPixel, bool )
+{
+ if ( pCurView )
+ pCurView->pImpEditView->SetCursorAtPoint( rPointPixel );
+}
+
+bool EditSelFunctionSet::IsSelectionAtPoint( const Point& rPointPixel )
+{
+ if ( pCurView )
+ return pCurView->pImpEditView->IsSelectionAtPoint( rPointPixel );
+
+ return false;
+}
+
+void EditSelFunctionSet::DeselectAtPoint( const Point& )
+{
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+// ! Implement when multiple selection is possible !
+// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+}
+
+void EditSelFunctionSet::BeginDrag()
+{
+ // Only with multiple selection
+}
+
+
+void EditSelFunctionSet::DeselectAll()
+{
+ if ( pCurView )
+ pCurView->pImpEditView->DeselectAll();
+}
+
+
+
+EditSelectionEngine::EditSelectionEngine() : SelectionEngine( nullptr )
+{
+ SetSelectionMode( SelectionMode::Range );
+ EnableDrag( true );
+}
+
+void EditSelectionEngine::SetCurView( EditView* pNewView )
+{
+ if ( GetFunctionSet() )
+ const_cast<EditSelFunctionSet*>(static_cast<const EditSelFunctionSet*>(GetFunctionSet()))->SetCurView( pNewView );
+
+ if ( pNewView )
+ SetWindow( pNewView->GetWindow() );
+ else
+ SetWindow( nullptr );
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/editsel.hxx b/editeng/source/editeng/editsel.hxx
new file mode 100644
index 0000000000..8d2adfb670
--- /dev/null
+++ b/editeng/source/editeng/editsel.hxx
@@ -0,0 +1,58 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <vcl/seleng.hxx>
+
+class EditView;
+
+class EditSelFunctionSet: public FunctionSet
+{
+private:
+ EditView* pCurView;
+
+public:
+ EditSelFunctionSet();
+
+ virtual void BeginDrag() override;
+
+ virtual void CreateAnchor() override;
+ virtual void DestroyAnchor() override;
+
+ virtual void SetCursorAtPoint( const Point& rPointPixel, bool bDontSelectAtCursor = false ) override;
+
+ virtual bool IsSelectionAtPoint( const Point& rPointPixel ) override;
+ virtual void DeselectAtPoint( const Point& rPointPixel ) override;
+ virtual void DeselectAll() override;
+
+ void SetCurView( EditView* pView ) { pCurView = pView; }
+};
+
+class EditSelectionEngine : public SelectionEngine
+{
+private:
+
+public:
+ EditSelectionEngine();
+
+ void SetCurView( EditView* pNewView );
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/editstt2.hxx b/editeng/source/editeng/editstt2.hxx
new file mode 100644
index 0000000000..334622b234
--- /dev/null
+++ b/editeng/source/editeng/editstt2.hxx
@@ -0,0 +1,99 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <editeng/editstat.hxx>
+
+class InternalEditStatus : public EditStatus
+{
+
+public:
+ void TurnOnFlags( EEControlBits nFlags )
+ { nControlBits |= nFlags; }
+
+ void TurnOffFlags( EEControlBits nFlags )
+ { nControlBits &= ~nFlags; }
+
+ bool UseCharAttribs() const
+ { return bool( nControlBits & EEControlBits::USECHARATTRIBS ); }
+
+ bool UseIdleFormatter() const
+ { return bool( nControlBits & EEControlBits::DOIDLEFORMAT); }
+
+ bool AllowPasteSpecial() const
+ { return bool( nControlBits & EEControlBits::PASTESPECIAL ); }
+
+ bool DoAutoIndenting() const
+ { return bool( nControlBits & EEControlBits::AUTOINDENTING ); }
+
+ bool DoUndoAttribs() const
+ { return bool( nControlBits & EEControlBits::UNDOATTRIBS ); }
+
+ bool OneCharPerLine() const
+ { return bool( nControlBits & EEControlBits::ONECHARPERLINE ); }
+
+ bool IsOutliner() const
+ { return bool( nControlBits & EEControlBits::OUTLINER ); }
+
+ bool DoNotUseColors() const
+ { return bool( nControlBits & EEControlBits::NOCOLORS ); }
+
+ bool AllowBigObjects() const
+ { return bool( nControlBits & EEControlBits::ALLOWBIGOBJS ); }
+
+ bool DoOnlineSpelling() const
+ { return bool( nControlBits & EEControlBits::ONLINESPELLING ); }
+
+ bool DoStretch() const
+ { return bool( nControlBits & EEControlBits::STRETCHING ); }
+
+ bool AutoPageSize() const
+ { return bool( nControlBits & EEControlBits::AUTOPAGESIZE ); }
+ bool AutoPageWidth() const
+ { return bool( nControlBits & EEControlBits::AUTOPAGESIZEX ); }
+ bool AutoPageHeight() const
+ { return bool( nControlBits & EEControlBits::AUTOPAGESIZEY ); }
+
+ bool MarkNonUrlFields() const
+ { return bool( nControlBits & EEControlBits::MARKNONURLFIELDS ); }
+
+ bool MarkUrlFields() const
+ { return bool( nControlBits & EEControlBits::MARKURLFIELDS ); }
+
+ bool DoImportRTFStyleSheets() const
+ { return bool( nControlBits & EEControlBits::RTFSTYLESHEETS ); }
+
+ bool DoAutoCorrect() const
+ { return bool( nControlBits & EEControlBits::AUTOCORRECT ); }
+
+ bool DoAutoComplete() const
+ { return bool( nControlBits & EEControlBits::AUTOCOMPLETE ); }
+
+ bool DoFormat100() const
+ { return bool( nControlBits & EEControlBits::FORMAT100 ); }
+
+ bool ULSpaceSummation() const
+ { return bool( nControlBits & EEControlBits::ULSPACESUMMATION ); }
+
+ bool IsSingleLine() const
+ { return bool( nControlBits & EEControlBits::SINGLELINE ); }
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/editundo.cxx b/editeng/source/editeng/editundo.cxx
new file mode 100644
index 0000000000..5854d1683e
--- /dev/null
+++ b/editeng/source/editeng/editundo.cxx
@@ -0,0 +1,661 @@
+/* -*- 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 <memory>
+#include "impedit.hxx"
+#include "editundo.hxx"
+#include <editeng/editview.hxx>
+#include <editeng/editeng.hxx>
+#include <utility>
+#include <osl/diagnose.h>
+
+
+static void lcl_DoSetSelection( EditView const * pView, sal_uInt16 nPara )
+{
+ EPaM aEPaM( nPara, 0 );
+ EditPaM aPaM( pView->GetImpEditEngine()->CreateEditPaM( aEPaM ) );
+ aPaM.SetIndex( aPaM.GetNode()->Len() );
+ EditSelection aSel( aPaM, aPaM );
+ pView->GetImpEditView()->SetEditSelection( aSel );
+}
+
+EditUndoManager::EditUndoManager(sal_uInt16 nMaxUndoActionCount )
+: SfxUndoManager(nMaxUndoActionCount),
+ mpEditEngine(nullptr)
+{
+}
+
+void EditUndoManager::SetEditEngine(EditEngine* pNew)
+{
+ mpEditEngine = pNew;
+}
+
+bool EditUndoManager::Undo()
+{
+ if ( !mpEditEngine || GetUndoActionCount() == 0 )
+ return false;
+
+ DBG_ASSERT( mpEditEngine->GetActiveView(), "Active View?" );
+
+ if ( !mpEditEngine->GetActiveView() )
+ {
+ if (!mpEditEngine->GetEditViews().empty())
+ mpEditEngine->SetActiveView(mpEditEngine->GetEditViews()[0]);
+ else
+ {
+ OSL_FAIL("Undo in engine is not possible without a View! ");
+ return false;
+ }
+ }
+
+ mpEditEngine->GetActiveView()->GetImpEditView()->DrawSelectionXOR(); // Remove the old selection
+
+ mpEditEngine->SetUndoMode( true );
+ bool bDone = SfxUndoManager::Undo();
+ mpEditEngine->SetUndoMode( false );
+
+ EditSelection aNewSel( mpEditEngine->GetActiveView()->GetImpEditView()->GetEditSelection() );
+ DBG_ASSERT( !aNewSel.IsInvalid(), "Invalid selection after Undo () ");
+ DBG_ASSERT( !aNewSel.DbgIsBuggy( mpEditEngine->GetEditDoc() ), "Broken selection afte Undo () ");
+
+ aNewSel.Min() = aNewSel.Max();
+ mpEditEngine->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel );
+ if (mpEditEngine->IsUpdateLayout())
+ mpEditEngine->FormatAndLayout( mpEditEngine->GetActiveView(), true );
+
+ return bDone;
+}
+
+bool EditUndoManager::Redo()
+{
+ if ( !mpEditEngine || GetRedoActionCount() == 0 )
+ return false;
+
+ DBG_ASSERT( mpEditEngine->GetActiveView(), "Active View?" );
+
+ if ( !mpEditEngine->GetActiveView() )
+ {
+ if (!mpEditEngine->GetEditViews().empty())
+ mpEditEngine->SetActiveView(mpEditEngine->GetEditViews()[0]);
+ else
+ {
+ OSL_FAIL( "Redo in Engine without View not possible!" );
+ return false;
+ }
+ }
+
+ mpEditEngine->GetActiveView()->GetImpEditView()->DrawSelectionXOR(); // Remove the old selection
+
+ mpEditEngine->SetUndoMode( true );
+ bool bDone = SfxUndoManager::Redo();
+ mpEditEngine->SetUndoMode( false );
+
+ EditSelection aNewSel( mpEditEngine->GetActiveView()->GetImpEditView()->GetEditSelection() );
+ DBG_ASSERT( !aNewSel.IsInvalid(), "Invalid selection after Undo () ");
+ DBG_ASSERT( !aNewSel.DbgIsBuggy( mpEditEngine->GetEditDoc() ), "Broken selection afte Undo () ");
+
+ aNewSel.Min() = aNewSel.Max();
+ mpEditEngine->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel );
+ if (mpEditEngine->IsUpdateLayout())
+ mpEditEngine->FormatAndLayout( mpEditEngine->GetActiveView() );
+
+ return bDone;
+}
+
+EditUndo::EditUndo(sal_uInt16 nI, EditEngine* pEE) :
+ nId(nI), mnViewShellId(-1), mpEditEngine(pEE)
+{
+ const EditView* pEditView = mpEditEngine ? mpEditEngine->GetActiveView() : nullptr;
+ const OutlinerViewShell* pViewShell = pEditView ? pEditView->GetImpEditView()->GetViewShell() : nullptr;
+ if (pViewShell)
+ mnViewShellId = pViewShell->GetViewShellId();
+}
+
+EditUndo::~EditUndo()
+{
+}
+
+
+sal_uInt16 EditUndo::GetId() const
+{
+ return nId;
+}
+
+bool EditUndo::CanRepeat(SfxRepeatTarget&) const
+{
+ return false;
+}
+
+OUString EditUndo::GetComment() const
+{
+ OUString aComment;
+
+ if (mpEditEngine)
+ aComment = mpEditEngine->GetUndoComment( GetId() );
+
+ return aComment;
+}
+
+ViewShellId EditUndo::GetViewShellId() const
+{
+ return mnViewShellId;
+}
+
+EditUndoDelContent::EditUndoDelContent(
+ EditEngine* pEE, ContentNode* pNode, sal_Int32 nPortion) :
+ EditUndo(EDITUNDO_DELCONTENT, pEE),
+ bDelObject(true),
+ nNode(nPortion),
+ pContentNode(pNode) {}
+
+EditUndoDelContent::~EditUndoDelContent()
+{
+ if ( bDelObject )
+ delete pContentNode;
+}
+
+void EditUndoDelContent::Undo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ GetEditEngine()->InsertContent( pContentNode, nNode );
+ bDelObject = false; // belongs to the Engine again
+ EditSelection aSel( EditPaM( pContentNode, 0 ), EditPaM( pContentNode, pContentNode->Len() ) );
+ GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection(aSel);
+}
+
+void EditUndoDelContent::Redo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+
+ EditEngine* pEE = GetEditEngine();
+
+ // pNode is no longer correct, if the paragraphs where merged
+ // in between Undos
+ pContentNode = pEE->GetEditDoc().GetObject( nNode );
+ DBG_ASSERT( pContentNode, "EditUndoDelContent::Redo(): Node?!" );
+
+ pEE->RemoveParaPortion(nNode);
+
+ // Do not delete node, depends on the undo!
+ pEE->GetEditDoc().Release( nNode );
+ if (pEE->IsCallParaInsertedOrDeleted())
+ pEE->ParagraphDeleted( nNode );
+
+ DeletedNodeInfo* pInf = new DeletedNodeInfo( pContentNode, nNode );
+ pEE->AppendDeletedNodeInfo(pInf);
+ pEE->UpdateSelections();
+
+ ContentNode* pN = ( nNode < pEE->GetEditDoc().Count() )
+ ? pEE->GetEditDoc().GetObject( nNode )
+ : pEE->GetEditDoc().GetObject( nNode-1 );
+ DBG_ASSERT( pN && ( pN != pContentNode ), "?! RemoveContent !? " );
+ EditPaM aPaM( pN, pN->Len() );
+
+ bDelObject = true; // belongs to the Engine again
+
+ pEE->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aPaM ) );
+}
+
+EditUndoConnectParas::EditUndoConnectParas(
+ EditEngine* pEE, sal_Int32 nN, sal_uInt16 nSP,
+ SfxItemSet _aLeftParaAttribs, SfxItemSet _aRightParaAttribs,
+ const SfxStyleSheet* pLeftStyle, const SfxStyleSheet* pRightStyle, bool bBkwrd) :
+ EditUndo(EDITUNDO_CONNECTPARAS, pEE),
+ nNode(nN),
+ nSepPos(nSP),
+ aLeftParaAttribs(std::move(_aLeftParaAttribs)),
+ aRightParaAttribs(std::move(_aRightParaAttribs)),
+ eLeftStyleFamily(SfxStyleFamily::All),
+ eRightStyleFamily(SfxStyleFamily::All),
+ bBackward(bBkwrd)
+{
+ if ( pLeftStyle )
+ {
+ aLeftStyleName = pLeftStyle->GetName();
+ eLeftStyleFamily = pLeftStyle->GetFamily();
+ }
+ if ( pRightStyle )
+ {
+ aRightStyleName = pRightStyle->GetName();
+ eRightStyleFamily = pRightStyle->GetFamily();
+ }
+}
+
+EditUndoConnectParas::~EditUndoConnectParas()
+{
+}
+
+void EditUndoConnectParas::Undo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+
+ // For SplitContent ParagraphInserted can not be called yet because the
+ // Outliner relies on the attributes to initialize the depth
+
+ bool bCall = GetEditEngine()->IsCallParaInsertedOrDeleted();
+ GetEditEngine()->SetCallParaInsertedOrDeleted(false);
+
+ EditPaM aPaM = GetEditEngine()->SplitContent(nNode, nSepPos);
+
+ GetEditEngine()->SetCallParaInsertedOrDeleted( bCall );
+ if (GetEditEngine()->IsCallParaInsertedOrDeleted())
+ {
+ GetEditEngine()->ParagraphInserted( nNode+1 );
+ GetEditEngine()->SetParaAttribs( nNode+1, aRightParaAttribs );
+ }
+
+ // Calling SetParaAttribs is effective only after ParagraphInserted
+ GetEditEngine()->SetParaAttribs( nNode, aLeftParaAttribs );
+
+ if (GetEditEngine()->GetStyleSheetPool())
+ {
+ if ( !aLeftStyleName.isEmpty() )
+ GetEditEngine()->SetStyleSheet( nNode, static_cast<SfxStyleSheet*>(GetEditEngine()->GetStyleSheetPool()->Find( aLeftStyleName, eLeftStyleFamily )) );
+ if ( !aRightStyleName.isEmpty() )
+ GetEditEngine()->SetStyleSheet( nNode+1, static_cast<SfxStyleSheet*>(GetEditEngine()->GetStyleSheetPool()->Find( aRightStyleName, eRightStyleFamily )) );
+ }
+
+ GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aPaM ) );
+}
+
+void EditUndoConnectParas::Redo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: Np Active View!" );
+ EditPaM aPaM = GetEditEngine()->ConnectContents( nNode, bBackward );
+
+ GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aPaM ) );
+}
+
+EditUndoSplitPara::EditUndoSplitPara(
+ EditEngine* pEE, sal_Int32 nN, sal_uInt16 nSP) :
+ EditUndo(EDITUNDO_SPLITPARA, pEE),
+ nNode(nN), nSepPos(nSP) {}
+
+EditUndoSplitPara::~EditUndoSplitPara() {}
+
+void EditUndoSplitPara::Undo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ EditPaM aPaM = GetEditEngine()->ConnectContents(nNode, false);
+ GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aPaM ) );
+}
+
+void EditUndoSplitPara::Redo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ EditPaM aPaM = GetEditEngine()->SplitContent(nNode, nSepPos);
+ GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aPaM ) );
+}
+
+EditUndoInsertChars::EditUndoInsertChars(
+ EditEngine* pEE, const EPaM& rEPaM, OUString aStr) :
+ EditUndo(EDITUNDO_INSERTCHARS, pEE),
+ aEPaM(rEPaM),
+ aText(std::move(aStr)) {}
+
+void EditUndoInsertChars::Undo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM);
+ EditSelection aSel( aPaM, aPaM );
+ aSel.Max().SetIndex( aSel.Max().GetIndex() + aText.getLength() );
+ EditPaM aNewPaM( GetEditEngine()->DeleteSelection(aSel) );
+ GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aNewPaM, aNewPaM ) );
+}
+
+void EditUndoInsertChars::Redo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM);
+ GetEditEngine()->InsertText(EditSelection(aPaM, aPaM), aText);
+ EditPaM aNewPaM( aPaM );
+ aNewPaM.SetIndex( aNewPaM.GetIndex() + aText.getLength() );
+ GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( EditSelection( aPaM, aNewPaM ) );
+}
+
+bool EditUndoInsertChars::Merge( SfxUndoAction* pNextAction )
+{
+ EditUndoInsertChars* pNext = dynamic_cast<EditUndoInsertChars*>(pNextAction);
+ if (!pNext)
+ return false;
+
+ if ( aEPaM.nPara != pNext->aEPaM.nPara )
+ return false;
+
+ if ( ( aEPaM.nIndex + aText.getLength() ) == pNext->aEPaM.nIndex )
+ {
+ aText += pNext->aText;
+ return true;
+ }
+ return false;
+}
+
+EditUndoRemoveChars::EditUndoRemoveChars(
+ EditEngine* pEE, const EPaM& rEPaM, OUString aStr) :
+ EditUndo(EDITUNDO_REMOVECHARS, pEE),
+ aEPaM(rEPaM), aText(std::move(aStr)) {}
+
+void EditUndoRemoveChars::Undo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM);
+ EditSelection aSel( aPaM, aPaM );
+ GetEditEngine()->InsertText(aSel, aText);
+ aSel.Max().SetIndex( aSel.Max().GetIndex() + aText.getLength() );
+ GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection(aSel);
+}
+
+void EditUndoRemoveChars::Redo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM);
+ EditSelection aSel( aPaM, aPaM );
+ aSel.Max().SetIndex( aSel.Max().GetIndex() + aText.getLength() );
+ EditPaM aNewPaM = GetEditEngine()->DeleteSelection(aSel);
+ GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection(aNewPaM);
+}
+
+EditUndoInsertFeature::EditUndoInsertFeature(
+ EditEngine* pEE, const EPaM& rEPaM, const SfxPoolItem& rFeature) :
+ EditUndo(EDITUNDO_INSERTFEATURE, pEE),
+ aEPaM(rEPaM),
+ pFeature(rFeature.Clone())
+{
+ DBG_ASSERT( pFeature, "Feature could not be duplicated: EditUndoInsertFeature" );
+}
+
+EditUndoInsertFeature::~EditUndoInsertFeature()
+{
+}
+
+void EditUndoInsertFeature::Undo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM);
+ EditSelection aSel( aPaM, aPaM );
+ // Attributes are then corrected implicitly by the document ...
+ aSel.Max().SetIndex( aSel.Max().GetIndex()+1 );
+ GetEditEngine()->DeleteSelection(aSel);
+ aSel.Max().SetIndex( aSel.Max().GetIndex()-1 ); // For Selection
+ GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection(aSel);
+}
+
+void EditUndoInsertFeature::Redo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ EditPaM aPaM = GetEditEngine()->CreateEditPaM(aEPaM);
+ EditSelection aSel( aPaM, aPaM );
+ GetEditEngine()->InsertFeature(aSel, *pFeature);
+ if ( pFeature->Which() == EE_FEATURE_FIELD )
+ GetEditEngine()->UpdateFieldsOnly();
+ aSel.Max().SetIndex( aSel.Max().GetIndex()+1 );
+ GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection(aSel);
+}
+
+EditUndoMoveParagraphs::EditUndoMoveParagraphs(
+ EditEngine* pEE, const Range& rParas, sal_Int32 n) :
+ EditUndo(EDITUNDO_MOVEPARAGRAPHS, pEE), nParagraphs(rParas), nDest(n) {}
+
+EditUndoMoveParagraphs::~EditUndoMoveParagraphs() {}
+
+void EditUndoMoveParagraphs::Undo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ Range aTmpRange( nParagraphs );
+ tools::Long nTmpDest = aTmpRange.Min();
+
+ tools::Long nDiff = nDest - aTmpRange.Min();
+ aTmpRange.Min() += nDiff;
+ aTmpRange.Max() += nDiff;
+
+ if ( nParagraphs.Min() < static_cast<tools::Long>(nDest) )
+ {
+ tools::Long nLen = aTmpRange.Len();
+ aTmpRange.Min() -= nLen;
+ aTmpRange.Max() -= nLen;
+ }
+ else
+ nTmpDest += aTmpRange.Len();
+
+ EditSelection aNewSel = GetEditEngine()->MoveParagraphs(aTmpRange, nTmpDest);
+ GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel );
+}
+
+void EditUndoMoveParagraphs::Redo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ EditSelection aNewSel = GetEditEngine()->MoveParagraphs(nParagraphs, nDest);
+ GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel );
+}
+
+EditUndoSetStyleSheet::EditUndoSetStyleSheet(
+ EditEngine* pEE, sal_Int32 nP, OUString _aPrevName, SfxStyleFamily ePrevFam,
+ OUString _aNewName, SfxStyleFamily eNewFam, SfxItemSet _aPrevParaAttribs) :
+ EditUndo(EDITUNDO_STYLESHEET, pEE),
+ nPara(nP),
+ aPrevName(std::move(_aPrevName)),
+ aNewName(std::move(_aNewName)),
+ ePrevFamily(ePrevFam),
+ eNewFamily(eNewFam),
+ aPrevParaAttribs(std::move(_aPrevParaAttribs))
+{
+}
+
+EditUndoSetStyleSheet::~EditUndoSetStyleSheet()
+{
+}
+
+void EditUndoSetStyleSheet::Undo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ GetEditEngine()->SetStyleSheet( nPara, static_cast<SfxStyleSheet*>(GetEditEngine()->GetStyleSheetPool()->Find( aPrevName, ePrevFamily )) );
+ GetEditEngine()->SetParaAttribsOnly( nPara, aPrevParaAttribs );
+ lcl_DoSetSelection( GetEditEngine()->GetActiveView(), nPara );
+}
+
+void EditUndoSetStyleSheet::Redo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ GetEditEngine()->SetStyleSheet( nPara, static_cast<SfxStyleSheet*>(GetEditEngine()->GetStyleSheetPool()->Find( aNewName, eNewFamily )) );
+ lcl_DoSetSelection( GetEditEngine()->GetActiveView(), nPara );
+}
+
+EditUndoSetParaAttribs::EditUndoSetParaAttribs(
+ EditEngine* pEE, sal_Int32 nP, SfxItemSet _aPrevItems, SfxItemSet _aNewItems) :
+ EditUndo(EDITUNDO_PARAATTRIBS, pEE),
+ nPara(nP),
+ aPrevItems(std::move(_aPrevItems)),
+ aNewItems(std::move(_aNewItems)) {}
+
+EditUndoSetParaAttribs::~EditUndoSetParaAttribs() {}
+
+void EditUndoSetParaAttribs::Undo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ GetEditEngine()->SetParaAttribsOnly( nPara, aPrevItems );
+ lcl_DoSetSelection( GetEditEngine()->GetActiveView(), nPara );
+}
+
+void EditUndoSetParaAttribs::Redo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ GetEditEngine()->SetParaAttribsOnly( nPara, aNewItems );
+ lcl_DoSetSelection( GetEditEngine()->GetActiveView(), nPara );
+}
+
+EditUndoSetAttribs::EditUndoSetAttribs(EditEngine* pEE, const ESelection& rESel, SfxItemSet aNewItems) :
+ EditUndo(EDITUNDO_ATTRIBS, pEE),
+ aESel(rESel),
+ aNewAttribs(std::move(aNewItems)),
+ nSpecial(SetAttribsMode::NONE),
+ m_bSetSelection(true),
+ // When EditUndoSetAttribs actually is a RemoveAttribs this could be
+ // recognize by the empty itemset, but then it would have to be caught in
+ // its own place, which possible a setAttribs does with an empty itemset.
+ bSetIsRemove(false),
+ bRemoveParaAttribs(false),
+ nRemoveWhich(0)
+{
+}
+
+EditUndoSetAttribs::~EditUndoSetAttribs()
+{
+}
+
+void EditUndoSetAttribs::Undo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ EditEngine* pEE = GetEditEngine();
+ bool bFields = false;
+ for ( sal_Int32 nPara = aESel.nStartPara; nPara <= aESel.nEndPara; nPara++ )
+ {
+ const ContentAttribsInfo& rInf = *aPrevAttribs[nPara-aESel.nStartPara];
+
+ // first the paragraph attributes ...
+ pEE->SetParaAttribsOnly(nPara, rInf.GetPrevParaAttribs());
+
+ // Then the character attributes ...
+ // Remove all attributes including features, are later re-established.
+ pEE->RemoveCharAttribs(nPara, 0, true);
+ DBG_ASSERT( pEE->GetEditDoc().GetObject( nPara ), "Undo (SetAttribs): pNode = NULL!" );
+ ContentNode* pNode = pEE->GetEditDoc().GetObject( nPara );
+ for (const auto & nAttr : rInf.GetPrevCharAttribs())
+ {
+ const EditCharAttrib& rX = *nAttr;
+ // is automatically "poolsized"
+ pEE->GetEditDoc().InsertAttrib(pNode, rX.GetStart(), rX.GetEnd(), *rX.GetItem());
+ if (rX.Which() == EE_FEATURE_FIELD)
+ bFields = true;
+ }
+ }
+ if ( bFields )
+ pEE->UpdateFieldsOnly();
+ if (m_bSetSelection)
+ {
+ ImpSetSelection();
+ }
+}
+
+void EditUndoSetAttribs::Redo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ EditEngine* pEE = GetEditEngine();
+
+ EditSelection aSel = pEE->CreateSelection(aESel);
+ if ( !bSetIsRemove )
+ pEE->SetAttribs( aSel, aNewAttribs, nSpecial );
+ else
+ pEE->RemoveCharAttribs( aSel, bRemoveParaAttribs, nRemoveWhich );
+
+ if (m_bSetSelection)
+ {
+ ImpSetSelection();
+ }
+}
+
+void EditUndoSetAttribs::AppendContentInfo(ContentAttribsInfo* pNew)
+{
+ aPrevAttribs.push_back(std::unique_ptr<ContentAttribsInfo>(pNew));
+}
+
+void EditUndoSetAttribs::ImpSetSelection()
+{
+ EditEngine* pEE = GetEditEngine();
+ EditSelection aSel = pEE->CreateSelection(aESel);
+ pEE->GetActiveView()->GetImpEditView()->SetEditSelection(aSel);
+}
+
+EditUndoTransliteration::EditUndoTransliteration(EditEngine* pEE, const ESelection& rESel, TransliterationFlags nM) :
+ EditUndo(EDITUNDO_TRANSLITERATE, pEE),
+ aOldESel(rESel), nMode(nM) {}
+
+EditUndoTransliteration::~EditUndoTransliteration()
+{
+}
+
+void EditUndoTransliteration::Undo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+
+ EditEngine* pEE = GetEditEngine();
+
+ EditSelection aSel = pEE->CreateSelection(aNewESel);
+
+ // Insert text, but don't expand Attribs at the current position:
+ aSel = pEE->DeleteSelected( aSel );
+ EditSelection aDelSel( aSel );
+ aSel = pEE->InsertParaBreak( aSel );
+ aDelSel.Max() = aSel.Min();
+ aDelSel.Max().GetNode()->GetCharAttribs().DeleteEmptyAttribs();
+ EditSelection aNewSel;
+ if ( pTxtObj )
+ {
+ aNewSel = pEE->InsertText( *pTxtObj, aSel );
+ }
+ else
+ {
+ aNewSel = pEE->InsertText( aSel, aText );
+ }
+ if ( aNewSel.Min().GetNode() == aDelSel.Max().GetNode() )
+ {
+ aNewSel.Min().SetNode( aDelSel.Min().GetNode() );
+ aNewSel.Min().SetIndex( aNewSel.Min().GetIndex() + aDelSel.Min().GetIndex() );
+ }
+ if ( aNewSel.Max().GetNode() == aDelSel.Max().GetNode() )
+ {
+ aNewSel.Max().SetNode( aDelSel.Min().GetNode() );
+ aNewSel.Max().SetIndex( aNewSel.Max().GetIndex() + aDelSel.Min().GetIndex() );
+ }
+ pEE->DeleteSelected( aDelSel );
+ pEE->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel );
+}
+
+void EditUndoTransliteration::Redo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ EditEngine* pEE = GetEditEngine();
+
+ EditSelection aSel = pEE->CreateSelection(aOldESel);
+ EditSelection aNewSel = pEE->TransliterateText( aSel, nMode );
+ pEE->GetActiveView()->GetImpEditView()->SetEditSelection( aNewSel );
+}
+
+EditUndoMarkSelection::EditUndoMarkSelection(EditEngine* pEE, const ESelection& rSel) :
+ EditUndo(EDITUNDO_MARKSELECTION, pEE), aSelection(rSel) {}
+
+EditUndoMarkSelection::~EditUndoMarkSelection() {}
+
+void EditUndoMarkSelection::Undo()
+{
+ DBG_ASSERT( GetEditEngine()->GetActiveView(), "Undo/Redo: No Active View!" );
+ if ( GetEditEngine()->GetActiveView() )
+ {
+ if ( GetEditEngine()->IsFormatted() )
+ GetEditEngine()->GetActiveView()->SetSelection( aSelection );
+ else
+ GetEditEngine()->GetActiveView()->GetImpEditView()->SetEditSelection( GetEditEngine()->CreateSelection(aSelection) );
+ }
+}
+
+void EditUndoMarkSelection::Redo()
+{
+ // For redo unimportant, because at the beginning of the undo parentheses
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/editundo.hxx b/editeng/source/editeng/editundo.hxx
new file mode 100644
index 0000000000..d08bc4810b
--- /dev/null
+++ b/editeng/source/editeng/editundo.hxx
@@ -0,0 +1,291 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <editdoc.hxx>
+#include <editeng/editund2.hxx>
+#include <editeng/editdata.hxx>
+#include <editeng/editobj.hxx>
+#include <vector>
+#include <memory>
+
+class EditTextObject;
+class EditEngine;
+enum class SetAttribsMode;
+enum class TransliterationFlags;
+
+// EditUndoDelContent
+
+class EditUndoDelContent : public EditUndo
+{
+private:
+ bool bDelObject;
+ sal_Int32 nNode;
+ ContentNode* pContentNode; // Points to the valid,
+ // undestroyed object!
+
+public:
+ EditUndoDelContent(EditEngine* pEE, ContentNode* pNode, sal_Int32 nPortion);
+ virtual ~EditUndoDelContent() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+};
+
+
+// EditUndoConnectParas
+
+class EditUndoConnectParas : public EditUndo
+{
+private:
+ sal_Int32 nNode;
+ sal_uInt16 nSepPos;
+ SfxItemSet aLeftParaAttribs;
+ SfxItemSet aRightParaAttribs;
+
+ // 2 Pointers would be nicer but then it would have to be a SfxListener.
+ OUString aLeftStyleName;
+ OUString aRightStyleName;
+ SfxStyleFamily eLeftStyleFamily;
+ SfxStyleFamily eRightStyleFamily;
+
+ bool bBackward;
+
+public:
+ EditUndoConnectParas(EditEngine* pEE, sal_Int32 nNode, sal_uInt16 nSepPos,
+ SfxItemSet aLeftParaAttribs, SfxItemSet aRightParaAttribs,
+ const SfxStyleSheet* pLeftStyle, const SfxStyleSheet* pRightStyle, bool bBackward);
+ virtual ~EditUndoConnectParas() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+};
+
+
+// EditUndoSplitPara
+
+class EditUndoSplitPara : public EditUndo
+{
+private:
+ sal_Int32 nNode;
+ sal_uInt16 nSepPos;
+
+public:
+ EditUndoSplitPara(EditEngine* pEE, sal_Int32 nNode, sal_uInt16 nSepPos);
+ virtual ~EditUndoSplitPara() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+};
+
+
+// EditUndoInsertChars
+
+class EditUndoInsertChars : public EditUndo
+{
+private:
+ EPaM aEPaM;
+ OUString aText;
+
+public:
+ EditUndoInsertChars(EditEngine* pEE, const EPaM& rEPaM, OUString aStr);
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ virtual bool Merge( SfxUndoAction *pNextAction ) override;
+};
+
+
+// EditUndoRemoveChars
+
+class EditUndoRemoveChars : public EditUndo
+{
+private:
+ EPaM aEPaM;
+ OUString aText;
+
+public:
+ EditUndoRemoveChars(EditEngine* pEE, const EPaM& rEPaM, OUString aStr);
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+};
+
+
+// EditUndoInsertFeature
+
+class EditUndoInsertFeature : public EditUndo
+{
+private:
+ EPaM aEPaM;
+ std::unique_ptr<SfxPoolItem> pFeature;
+
+public:
+ EditUndoInsertFeature(EditEngine* pEE, const EPaM& rEPaM, const SfxPoolItem& rFeature);
+ virtual ~EditUndoInsertFeature() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+};
+
+
+// EditUndoMoveParagraphs
+
+class EditUndoMoveParagraphs: public EditUndo
+{
+private:
+ Range nParagraphs;
+ sal_Int32 nDest;
+
+public:
+ EditUndoMoveParagraphs(EditEngine* pEE, const Range& rParas, sal_Int32 nDest);
+ virtual ~EditUndoMoveParagraphs() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+};
+
+
+// EditUndoSetStyleSheet
+
+class EditUndoSetStyleSheet: public EditUndo
+{
+private:
+ sal_Int32 nPara;
+ OUString aPrevName;
+ OUString aNewName;
+ SfxStyleFamily ePrevFamily;
+ SfxStyleFamily eNewFamily;
+ SfxItemSet aPrevParaAttribs;
+
+public:
+ EditUndoSetStyleSheet(EditEngine* pEE, sal_Int32 nPara,
+ OUString aPrevName, SfxStyleFamily ePrevFamily,
+ OUString aNewName, SfxStyleFamily eNewFamily,
+ SfxItemSet aPrevParaAttribs);
+ virtual ~EditUndoSetStyleSheet() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+};
+
+
+// EditUndoSetParaAttribs
+
+class EditUndoSetParaAttribs: public EditUndo
+{
+private:
+ sal_Int32 nPara;
+ SfxItemSet aPrevItems;
+ SfxItemSet aNewItems;
+
+public:
+ EditUndoSetParaAttribs(EditEngine* pEE, sal_Int32 nPara, SfxItemSet aPrevItems, SfxItemSet aNewItems);
+ virtual ~EditUndoSetParaAttribs() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+};
+
+
+// EditUndoSetAttribs
+
+class EditUndoSetAttribs: public EditUndo
+{
+private:
+ typedef std::vector<std::unique_ptr<ContentAttribsInfo> > InfoArrayType;
+
+ ESelection aESel;
+ SfxItemSet aNewAttribs;
+ InfoArrayType aPrevAttribs;
+
+ SetAttribsMode nSpecial;
+ /// Once the attributes are set / unset, set the selection to the end of the formatted range?
+ bool m_bSetSelection;
+ bool bSetIsRemove;
+ bool bRemoveParaAttribs;
+ sal_uInt16 nRemoveWhich;
+
+ void ImpSetSelection();
+
+
+public:
+ EditUndoSetAttribs(EditEngine* pEE, const ESelection& rESel, SfxItemSet aNewItems);
+ virtual ~EditUndoSetAttribs() override;
+
+ SfxItemSet& GetNewAttribs() { return aNewAttribs; }
+
+ void SetSpecial( SetAttribsMode n ) { nSpecial = n; }
+ void SetUpdateSelection( bool bSetSelection ) { m_bSetSelection = bSetSelection; }
+ void SetRemoveAttribs( bool b ) { bSetIsRemove = b; }
+ void SetRemoveParaAttribs( bool b ) { bRemoveParaAttribs = b; }
+ void SetRemoveWhich( sal_uInt16 n ) { nRemoveWhich = n; }
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+
+ void AppendContentInfo(ContentAttribsInfo* pNew);
+};
+
+
+// EditUndoTransliteration
+
+class EditUndoTransliteration: public EditUndo
+{
+private:
+ ESelection aOldESel;
+ ESelection aNewESel;
+
+ TransliterationFlags
+ nMode;
+ std::unique_ptr<EditTextObject>
+ pTxtObj;
+ OUString aText;
+
+public:
+ EditUndoTransliteration(EditEngine* pEE, const ESelection& rESel, TransliterationFlags nMode);
+ virtual ~EditUndoTransliteration() override;
+
+ void SetText( const OUString& rText ) { aText = rText; }
+ void SetText( std::unique_ptr<EditTextObject> pObj ) { pTxtObj = std::move( pObj ); }
+ void SetNewSelection( const ESelection& rSel ) { aNewESel = rSel; }
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+};
+
+
+// EditUndoMarkSelection
+
+class EditUndoMarkSelection: public EditUndo
+{
+private:
+ ESelection aSelection;
+
+public:
+ EditUndoMarkSelection(EditEngine* pEE, const ESelection& rSel);
+ virtual ~EditUndoMarkSelection() override;
+
+ virtual void Undo() override;
+ virtual void Redo() override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/editview.cxx b/editeng/source/editeng/editview.cxx
new file mode 100644
index 0000000000..e047e6a41f
--- /dev/null
+++ b/editeng/source/editeng/editview.cxx
@@ -0,0 +1,1800 @@
+/* -*- 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 <memory>
+#include <vcl/image.hxx>
+
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+
+#include <i18nlangtag/languagetag.hxx>
+#include <i18nlangtag/mslangid.hxx>
+#include <svl/languageoptions.hxx>
+#include <svtools/ctrltool.hxx>
+#include <svtools/langtab.hxx>
+#include <tools/stream.hxx>
+
+#include <svl/srchitem.hxx>
+
+#include "impedit.hxx"
+#include <comphelper/propertyvalue.hxx>
+#include <editeng/editeng.hxx>
+#include <editeng/editview.hxx>
+#include <editeng/flditem.hxx>
+#include <editeng/svxacorr.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/eerdll.hxx>
+#include <eerdll2.hxx>
+#include <editeng/editrids.hrc>
+#include <editeng.hxx>
+#include <i18nlangtag/lang.h>
+#include <vcl/window.hxx>
+#include <editeng/acorrcfg.hxx>
+#include <editeng/unolingu.hxx>
+#include <unotools/lingucfg.hxx>
+
+#include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/linguistic2/XDictionary.hpp>
+#include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
+#include <linguistic/lngprops.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <comphelper/lok.hxx>
+#include <sfx2/viewsh.hxx>
+#include <osl/diagnose.h>
+#include <boost/property_tree/json_parser.hpp>
+
+#include <com/sun/star/lang/XServiceInfo.hpp>
+
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::beans;
+
+
+// static
+LanguageType EditView::CheckLanguage(
+ const OUString &rText,
+ const Reference< linguistic2::XSpellChecker1 >& xSpell,
+ const Reference< linguistic2::XLanguageGuessing >& xLangGuess,
+ bool bIsParaText )
+{
+ LanguageType nLang = LANGUAGE_NONE;
+ if (bIsParaText) // check longer texts with language-guessing...
+ {
+ if (!xLangGuess.is())
+ return nLang;
+
+ LanguageTag aGuessTag( xLangGuess->guessPrimaryLanguage( rText, 0, rText.getLength()) );
+
+ // If the result from language guessing does not provide a 'Country'
+ // part, try to get it by looking up the locale setting of the office,
+ // "Tools/Options - Languages and Locales - General: Locale setting", if
+ // the language matches.
+ if ( aGuessTag.getCountry().isEmpty() )
+ {
+ const LanguageTag& rAppLocaleTag = Application::GetSettings().GetLanguageTag();
+ if (rAppLocaleTag.getLanguage() == aGuessTag.getLanguage())
+ nLang = rAppLocaleTag.getLanguageType();
+ }
+ if (nLang == LANGUAGE_NONE) // language not found by looking up the system language...
+ nLang = aGuessTag.makeFallback().getLanguageType(); // best known locale match
+ if (nLang == LANGUAGE_SYSTEM)
+ nLang = Application::GetSettings().GetLanguageTag().getLanguageType();
+ if (nLang == LANGUAGE_DONTKNOW)
+ nLang = LANGUAGE_NONE;
+ }
+ else // check single word
+ {
+ if (!xSpell.is())
+ return nLang;
+
+
+ // build list of languages to check
+
+ LanguageType aLangList[4];
+ const AllSettings& rSettings = Application::GetSettings();
+ SvtLinguOptions aLinguOpt;
+ SvtLinguConfig().GetOptions( aLinguOpt );
+ // The default document language from "Tools/Options - Languages and Locales - General: Western"
+ aLangList[0] = MsLangId::resolveSystemLanguageByScriptType( aLinguOpt.nDefaultLanguage,
+ css::i18n::ScriptType::LATIN);
+ // The one from "Tools/Options - Languages and Locales - General: User interface"
+ aLangList[1] = rSettings.GetUILanguageTag().getLanguageType();
+ // The one from "Tools/Options - Languages and Locales - General: Locale setting"
+ aLangList[2] = rSettings.GetLanguageTag().getLanguageType();
+ // en-US
+ aLangList[3] = LANGUAGE_ENGLISH_US;
+#ifdef DEBUG
+ lang::Locale a0( LanguageTag::convertToLocale( aLangList[0] ) );
+ lang::Locale a1( LanguageTag::convertToLocale( aLangList[1] ) );
+ lang::Locale a2( LanguageTag::convertToLocale( aLangList[2] ) );
+ lang::Locale a3( LanguageTag::convertToLocale( aLangList[3] ) );
+#endif
+
+ for (const LanguageType& nTmpLang : aLangList)
+ {
+ if (nTmpLang != LANGUAGE_NONE && nTmpLang != LANGUAGE_DONTKNOW)
+ {
+ if (xSpell->hasLanguage( static_cast<sal_uInt16>(nTmpLang) ) &&
+ xSpell->isValid( rText, static_cast<sal_uInt16>(nTmpLang), Sequence< PropertyValue >() ))
+ {
+ nLang = nTmpLang;
+ break;
+ }
+ }
+ }
+ }
+
+ return nLang;
+}
+
+EditViewCallbacks::~EditViewCallbacks()
+{
+}
+
+EditView::EditView( EditEngine* pEng, vcl::Window* pWindow )
+{
+ pImpEditView.reset( new ImpEditView( this, pEng, pWindow ) );
+}
+
+EditView::~EditView()
+{
+}
+
+void EditView::setEditViewCallbacks(EditViewCallbacks* pEditViewCallbacks)
+{
+ pImpEditView->setEditViewCallbacks(pEditViewCallbacks);
+}
+
+EditViewCallbacks* EditView::getEditViewCallbacks() const
+{
+ return pImpEditView->getEditViewCallbacks();
+}
+
+ImpEditEngine* EditView::GetImpEditEngine() const
+{
+ return pImpEditView->pEditEngine->pImpEditEngine.get();
+}
+
+EditEngine* EditView::GetEditEngine() const
+{
+ return pImpEditView->pEditEngine;
+}
+
+tools::Rectangle EditView::GetInvalidateRect() const
+{
+ if ( !pImpEditView->DoInvalidateMore() )
+ return pImpEditView->aOutArea;
+ else
+ {
+ tools::Rectangle aRect( pImpEditView->aOutArea );
+ tools::Long nMore = pImpEditView->GetOutputDevice().PixelToLogic( Size( pImpEditView->GetInvalidateMore(), 0 ) ).Width();
+ aRect.AdjustLeft( -nMore );
+ aRect.AdjustRight(nMore );
+ aRect.AdjustTop( -nMore );
+ aRect.AdjustBottom(nMore );
+ return aRect;
+ }
+}
+
+namespace {
+
+tools::Rectangle lcl_negateRectX(const tools::Rectangle& rRect)
+{
+ return tools::Rectangle(-rRect.Right(), rRect.Top(), -rRect.Left(), rRect.Bottom());
+}
+
+}
+
+void EditView::InvalidateWindow(const tools::Rectangle& rClipRect)
+{
+ bool bNegativeX = IsNegativeX();
+ if (EditViewCallbacks* pEditViewCallbacks = pImpEditView->getEditViewCallbacks())
+ {
+ // do not invalidate and trigger a global repaint, but forward
+ // the need for change to the applied EditViewCallback, can e.g.
+ // be used to visualize the active edit text in an OverlayObject
+ pEditViewCallbacks->EditViewInvalidate(bNegativeX ? lcl_negateRectX(rClipRect) : rClipRect);
+ }
+ else
+ {
+ // classic mode: invalidate and trigger full repaint
+ // of the changed area
+ GetWindow()->Invalidate(bNegativeX ? lcl_negateRectX(rClipRect) : rClipRect);
+ }
+}
+
+void EditView::InvalidateOtherViewWindows( const tools::Rectangle& rInvRect )
+{
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ bool bNegativeX = IsNegativeX();
+ for (auto& pWin : pImpEditView->aOutWindowSet)
+ {
+ if (pWin)
+ pWin->Invalidate( bNegativeX ? lcl_negateRectX(rInvRect) : rInvRect );
+ }
+ }
+}
+
+void EditView::Invalidate()
+{
+ const tools::Rectangle& rInvRect = GetInvalidateRect();
+ pImpEditView->InvalidateAtWindow(rInvRect);
+ InvalidateOtherViewWindows(rInvRect);
+}
+
+void EditView::SetReadOnly( bool bReadOnly )
+{
+ pImpEditView->bReadOnly = bReadOnly;
+}
+
+bool EditView::IsReadOnly() const
+{
+ return pImpEditView->bReadOnly;
+}
+
+void EditView::SetSelection( const ESelection& rESel )
+{
+ // If someone has just left an empty attribute, and then the outliner manipulates the
+ // selection, call the CursorMoved method so that empty attributes get cleaned up.
+ if ( !HasSelection() )
+ {
+ // tdf#113591 Get node from EditDoc, as the selection might have a pointer to an
+ // already deleted node.
+ const ContentNode* pNode(pImpEditView->pEditEngine->GetEditDoc().GetEndPaM().GetNode());
+ if (nullptr != pNode)
+ pNode->checkAndDeleteEmptyAttribs();
+ }
+ EditSelection aNewSelection( pImpEditView->pEditEngine->pImpEditEngine->ConvertSelection(
+ rESel.nStartPara, rESel.nStartPos, rESel.nEndPara, rESel.nEndPos ) );
+
+ // If the selection is manipulated after a KeyInput:
+ pImpEditView->pEditEngine->CheckIdleFormatter();
+
+ // Selection may not start/end at an invisible paragraph:
+ const ParaPortion* pPortion = pImpEditView->pEditEngine->FindParaPortion( aNewSelection.Min().GetNode() );
+ if ( !pPortion->IsVisible() )
+ {
+ pPortion = pImpEditView->pEditEngine->GetPrevVisPortion( pPortion );
+ ContentNode* pNode = pPortion ? pPortion->GetNode() : pImpEditView->pEditEngine->GetEditDoc().GetObject( 0 );
+ aNewSelection.Min() = EditPaM( pNode, pNode->Len() );
+ }
+ pPortion = pImpEditView->pEditEngine->FindParaPortion( aNewSelection.Max().GetNode() );
+ if ( !pPortion->IsVisible() )
+ {
+ pPortion = pImpEditView->pEditEngine->GetPrevVisPortion( pPortion );
+ ContentNode* pNode = pPortion ? pPortion->GetNode() : pImpEditView->pEditEngine->GetEditDoc().GetObject( 0 );
+ aNewSelection.Max() = EditPaM( pNode, pNode->Len() );
+ }
+
+ pImpEditView->DrawSelectionXOR();
+ pImpEditView->SetEditSelection( aNewSelection );
+ pImpEditView->DrawSelectionXOR();
+ bool bGotoCursor = pImpEditView->DoAutoScroll();
+
+ // comments section in Writer:
+ // don't scroll to the selection if it is
+ // out of visible area of comment canvas.
+ if (HasSelection())
+ ShowCursor( bGotoCursor );
+}
+
+ESelection EditView::GetSelection() const
+{
+ ESelection aSelection;
+
+ aSelection.nStartPara = pImpEditView->pEditEngine->GetEditDoc().GetPos( pImpEditView->GetEditSelection().Min().GetNode() );
+ aSelection.nEndPara = pImpEditView->pEditEngine->GetEditDoc().GetPos( pImpEditView->GetEditSelection().Max().GetNode() );
+
+ aSelection.nStartPos = pImpEditView->GetEditSelection().Min().GetIndex();
+ aSelection.nEndPos = pImpEditView->GetEditSelection().Max().GetIndex();
+
+ return aSelection;
+}
+
+bool EditView::HasSelection() const
+{
+ return pImpEditView->HasSelection();
+}
+
+bool EditView::IsSelectionFullPara() const
+{
+ return pImpEditView->IsSelectionFullPara();
+}
+
+bool EditView::IsSelectionWithinSinglePara() const
+{
+ return pImpEditView->IsSelectionInSinglePara();
+}
+
+bool EditView::IsSelectionAtPoint(const Point& rPointPixel)
+{
+ return pImpEditView->IsSelectionAtPoint(rPointPixel);
+}
+
+void EditView::DeleteSelected()
+{
+ pImpEditView->DeleteSelected();
+}
+
+SvtScriptType EditView::GetSelectedScriptType() const
+{
+ return pImpEditView->pEditEngine->GetScriptType( pImpEditView->GetEditSelection() );
+}
+
+void EditView::GetSelectionRectangles(std::vector<tools::Rectangle>& rLogicRects) const
+{
+ return pImpEditView->GetSelectionRectangles(pImpEditView->GetEditSelection(), rLogicRects);
+}
+
+void EditView::Paint( const tools::Rectangle& rRect, OutputDevice* pTargetDevice )
+{
+ pImpEditView->pEditEngine->pImpEditEngine->Paint( pImpEditView.get(), rRect, pTargetDevice );
+}
+
+void EditView::SetEditEngine( EditEngine* pEditEng )
+{
+ pImpEditView->pEditEngine = pEditEng;
+ EditSelection aStartSel = pImpEditView->pEditEngine->GetEditDoc().GetStartPaM();
+ pImpEditView->SetEditSelection( aStartSel );
+}
+
+void EditView::SetWindow( vcl::Window* pWin )
+{
+ pImpEditView->pOutWin = pWin;
+ pImpEditView->pEditEngine->pImpEditEngine->GetSelEngine().Reset();
+}
+
+vcl::Window* EditView::GetWindow() const
+{
+ return pImpEditView->pOutWin;
+}
+
+OutputDevice& EditView::GetOutputDevice() const
+{
+ return pImpEditView->GetOutputDevice();
+}
+
+LanguageType EditView::GetInputLanguage() const
+{
+ // it might make sense to add this to getEditViewCallbacks
+ if (const vcl::Window* pWindow = GetWindow())
+ return pWindow->GetInputLanguage();
+ return LANGUAGE_DONTKNOW;
+}
+
+bool EditView::HasOtherViewWindow( vcl::Window* pWin )
+{
+ OutWindowSet& rOutWindowSet = pImpEditView->aOutWindowSet;
+ auto found = std::find(rOutWindowSet.begin(), rOutWindowSet.end(), pWin);
+ return (found != rOutWindowSet.end());
+}
+
+bool EditView::AddOtherViewWindow( vcl::Window* pWin )
+{
+ if (HasOtherViewWindow(pWin))
+ return false;
+ pImpEditView->aOutWindowSet.emplace_back(pWin);
+ return true;
+}
+
+bool EditView::RemoveOtherViewWindow( vcl::Window* pWin )
+{
+ OutWindowSet& rOutWindowSet = pImpEditView->aOutWindowSet;
+ auto found = std::find(rOutWindowSet.begin(), rOutWindowSet.end(), pWin);
+ if (found == rOutWindowSet.end())
+ return false;
+ rOutWindowSet.erase(found);
+ return true;
+}
+
+void EditView::SetVisArea( const tools::Rectangle& rRect )
+{
+ pImpEditView->SetVisDocStartPos( rRect.TopLeft() );
+}
+
+tools::Rectangle EditView::GetVisArea() const
+{
+ return pImpEditView->GetVisDocArea();
+}
+
+void EditView::SetOutputArea( const tools::Rectangle& rRect )
+{
+ pImpEditView->SetOutputArea( rRect );
+
+ // the rest here only if it is an API call:
+ pImpEditView->CalcAnchorPoint();
+ if ( pImpEditView->pEditEngine->pImpEditEngine->GetStatus().AutoPageSize() )
+ pImpEditView->RecalcOutputArea();
+ pImpEditView->ShowCursor( false, false );
+}
+
+const tools::Rectangle& EditView::GetOutputArea() const
+{
+ return pImpEditView->GetOutputArea();
+}
+
+PointerStyle EditView::GetPointer() const
+{
+ return pImpEditView->GetPointer();
+}
+
+vcl::Cursor* EditView::GetCursor() const
+{
+ return pImpEditView->pCursor.get();
+}
+
+void EditView::InsertText( const OUString& rStr, bool bSelect, bool bLOKShowSelect )
+{
+
+ EditEngine* pEE = pImpEditView->pEditEngine;
+
+ if (bLOKShowSelect)
+ pImpEditView->DrawSelectionXOR();
+
+ EditPaM aPaM1;
+ if ( bSelect )
+ {
+ EditSelection aTmpSel( pImpEditView->GetEditSelection() );
+ aTmpSel.Adjust( pEE->GetEditDoc() );
+ aPaM1 = aTmpSel.Min();
+ }
+
+ pEE->UndoActionStart( EDITUNDO_INSERT );
+ EditPaM aPaM2( pEE->InsertText( pImpEditView->GetEditSelection(), rStr ) );
+ pEE->UndoActionEnd();
+
+ if ( bSelect )
+ {
+ DBG_ASSERT( !aPaM1.DbgIsBuggy( pEE->GetEditDoc() ), "Insert: PaM broken" );
+ pImpEditView->SetEditSelection( EditSelection( aPaM1, aPaM2 ) );
+ }
+ else
+ pImpEditView->SetEditSelection( EditSelection( aPaM2, aPaM2 ) );
+
+ if (bLOKShowSelect)
+ pEE->FormatAndLayout( this );
+}
+
+bool EditView::PostKeyEvent( const KeyEvent& rKeyEvent, vcl::Window const * pFrameWin )
+{
+ return pImpEditView->PostKeyEvent( rKeyEvent, pFrameWin );
+}
+
+bool EditView::MouseButtonUp( const MouseEvent& rMouseEvent )
+{
+ return pImpEditView->MouseButtonUp( rMouseEvent );
+}
+
+void EditView::ReleaseMouse()
+{
+ return pImpEditView->ReleaseMouse();
+}
+
+bool EditView::MouseButtonDown( const MouseEvent& rMouseEvent )
+{
+ return pImpEditView->MouseButtonDown( rMouseEvent );
+}
+
+bool EditView::MouseMove( const MouseEvent& rMouseEvent )
+{
+ return pImpEditView->MouseMove( rMouseEvent );
+}
+
+bool EditView::Command(const CommandEvent& rCEvt)
+{
+ return pImpEditView->Command(rCEvt);
+}
+
+void EditView::SetBroadcastLOKViewCursor(bool bSet)
+{
+ pImpEditView->SetBroadcastLOKViewCursor(bSet);
+}
+
+tools::Rectangle EditView::GetEditCursor() const
+{
+ return pImpEditView->GetEditCursor();
+}
+
+void EditView::ShowCursor( bool bGotoCursor, bool bForceVisCursor, bool bActivate )
+{
+ if ( !pImpEditView->pEditEngine->HasView( this ) )
+ return;
+
+ // The control word is more important:
+ if ( !pImpEditView->DoAutoScroll() )
+ bGotoCursor = false;
+ pImpEditView->ShowCursor( bGotoCursor, bForceVisCursor );
+
+ if (pImpEditView->mpViewShell && !bActivate)
+ {
+ if (!pImpEditView->pOutWin)
+ return;
+ VclPtr<vcl::Window> pParent = pImpEditView->pOutWin->GetParentWithLOKNotifier();
+ if (pParent && pParent->GetLOKWindowId() != 0)
+ return;
+
+ static const OString aPayload = OString::boolean(true);
+ pImpEditView->mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload);
+ pImpEditView->mpViewShell->NotifyOtherViews(LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible"_ostr, aPayload);
+ }
+}
+
+void EditView::HideCursor(bool bDeactivate)
+{
+ pImpEditView->GetCursor()->Hide();
+
+ if (pImpEditView->mpViewShell && !bDeactivate)
+ {
+ if (!pImpEditView->pOutWin)
+ return;
+ VclPtr<vcl::Window> pParent = pImpEditView->pOutWin->GetParentWithLOKNotifier();
+ if (pParent && pParent->GetLOKWindowId() != 0)
+ return;
+
+ OString aPayload = OString::boolean(false);
+ pImpEditView->mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CURSOR_VISIBLE, aPayload);
+ pImpEditView->mpViewShell->NotifyOtherViews(LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible"_ostr, aPayload);
+ }
+}
+
+Pair EditView::Scroll( tools::Long ndX, tools::Long ndY, ScrollRangeCheck nRangeCheck )
+{
+ return pImpEditView->Scroll( ndX, ndY, nRangeCheck );
+}
+
+const SfxItemSet& EditView::GetEmptyItemSet() const
+{
+ return pImpEditView->pEditEngine->GetEmptyItemSet();
+}
+
+void EditView::SetAttribs( const SfxItemSet& rSet )
+{
+ DBG_ASSERT( !pImpEditView->aEditSelection.IsInvalid(), "Blind Selection in..." );
+
+ pImpEditView->DrawSelectionXOR();
+ pImpEditView->pEditEngine->SetAttribs( pImpEditView->GetEditSelection(), rSet, SetAttribsMode::WholeWord );
+ if (pImpEditView->pEditEngine->IsUpdateLayout())
+ pImpEditView->pEditEngine->FormatAndLayout( this );
+}
+
+void EditView::RemoveAttribsKeepLanguages( bool bRemoveParaAttribs )
+{
+
+ pImpEditView->DrawSelectionXOR();
+ pImpEditView->pEditEngine->UndoActionStart( EDITUNDO_RESETATTRIBS );
+ EditSelection aSelection( pImpEditView->GetEditSelection() );
+
+ for (sal_uInt16 nWID = EE_ITEMS_START; nWID <= EE_ITEMS_END; ++nWID)
+ {
+ bool bIsLang = EE_CHAR_LANGUAGE == nWID ||
+ EE_CHAR_LANGUAGE_CJK == nWID ||
+ EE_CHAR_LANGUAGE_CTL == nWID;
+ if (!bIsLang)
+ pImpEditView->pEditEngine->RemoveCharAttribs( aSelection, bRemoveParaAttribs, nWID );
+ }
+
+ pImpEditView->pEditEngine->UndoActionEnd();
+ if (pImpEditView->pEditEngine->IsUpdateLayout())
+ pImpEditView->pEditEngine->FormatAndLayout( this );
+}
+
+void EditView::RemoveAttribs( bool bRemoveParaAttribs, sal_uInt16 nWhich )
+{
+ RemoveAttribs(bRemoveParaAttribs ? EERemoveParaAttribsMode::RemoveAll
+ : EERemoveParaAttribsMode::RemoveCharItems, nWhich);
+}
+
+void EditView::RemoveAttribs( EERemoveParaAttribsMode eMode, sal_uInt16 nWhich )
+{
+ pImpEditView->DrawSelectionXOR();
+ pImpEditView->pEditEngine->UndoActionStart( EDITUNDO_RESETATTRIBS );
+ pImpEditView->pEditEngine->RemoveCharAttribs( pImpEditView->GetEditSelection(), eMode, nWhich );
+ pImpEditView->pEditEngine->UndoActionEnd();
+ if (pImpEditView->pEditEngine->IsUpdateLayout())
+ pImpEditView->pEditEngine->FormatAndLayout( this );
+}
+
+void EditView::RemoveCharAttribs( sal_Int32 nPara, sal_uInt16 nWhich )
+{
+ pImpEditView->pEditEngine->UndoActionStart( EDITUNDO_RESETATTRIBS );
+ pImpEditView->pEditEngine->RemoveCharAttribs( nPara, nWhich );
+ pImpEditView->pEditEngine->UndoActionEnd();
+ if (pImpEditView->pEditEngine->IsUpdateLayout())
+ pImpEditView->pEditEngine->FormatAndLayout( this );
+}
+
+SfxItemSet EditView::GetAttribs()
+{
+ DBG_ASSERT( !pImpEditView->aEditSelection.IsInvalid(), "Blind Selection in..." );
+ return pImpEditView->pEditEngine->pImpEditEngine->GetAttribs( pImpEditView->GetEditSelection() );
+}
+
+void EditView::Undo()
+{
+ pImpEditView->pEditEngine->Undo( this );
+}
+
+void EditView::Redo()
+{
+ pImpEditView->pEditEngine->Redo( this );
+}
+
+ErrCode EditView::Read( SvStream& rInput, EETextFormat eFormat, SvKeyValueIterator* pHTTPHeaderAttrs )
+{
+ EditSelection aOldSel( pImpEditView->GetEditSelection() );
+ pImpEditView->DrawSelectionXOR();
+ pImpEditView->pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_READ );
+ EditPaM aEndPaM = pImpEditView->pEditEngine->pImpEditEngine->Read( rInput, "", eFormat, aOldSel, pHTTPHeaderAttrs );
+ pImpEditView->pEditEngine->pImpEditEngine->UndoActionEnd();
+ EditSelection aNewSel( aEndPaM, aEndPaM );
+
+ pImpEditView->SetEditSelection( aNewSel );
+ bool bGotoCursor = pImpEditView->DoAutoScroll();
+ ShowCursor( bGotoCursor );
+
+ return rInput.GetError();
+}
+
+void EditView::Cut()
+{
+ Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard());
+ pImpEditView->CutCopy( aClipBoard, true );
+}
+
+Reference<css::datatransfer::clipboard::XClipboard> EditView::GetClipboard() const
+{
+ return pImpEditView->GetClipboard();
+}
+
+css::uno::Reference< css::datatransfer::XTransferable > EditView::GetTransferable() const
+{
+ uno::Reference< datatransfer::XTransferable > xData =
+ GetEditEngine()->CreateTransferable( pImpEditView->GetEditSelection() );
+ return xData;
+}
+
+void EditView::Copy()
+{
+ Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard());
+ pImpEditView->CutCopy( aClipBoard, false );
+}
+
+void EditView::Paste()
+{
+ Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard());
+ pImpEditView->Paste( aClipBoard );
+}
+
+void EditView::PasteSpecial(SotClipboardFormatId format)
+{
+ Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard());
+ pImpEditView->Paste(aClipBoard, true, format );
+}
+
+Point EditView::GetWindowPosTopLeft( sal_Int32 nParagraph )
+{
+ Point aDocPos( pImpEditView->pEditEngine->GetDocPosTopLeft( nParagraph ) );
+ return pImpEditView->GetWindowPos( aDocPos );
+}
+
+void EditView::SetSelectionMode( EESelectionMode eMode )
+{
+ pImpEditView->SetSelectionMode( eMode );
+}
+
+OUString EditView::GetSelected() const
+{
+ return pImpEditView->pEditEngine->pImpEditEngine->GetSelected( pImpEditView->GetEditSelection() );
+}
+
+void EditView::MoveParagraphs( Range aParagraphs, sal_Int32 nNewPos )
+{
+ pImpEditView->pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_MOVEPARAS );
+ pImpEditView->pEditEngine->pImpEditEngine->MoveParagraphs( aParagraphs, nNewPos, this );
+ pImpEditView->pEditEngine->pImpEditEngine->UndoActionEnd();
+}
+
+void EditView::MoveParagraphs( tools::Long nDiff )
+{
+ ESelection aSel = GetSelection();
+ Range aRange( aSel.nStartPara, aSel.nEndPara );
+ aRange.Normalize();
+ tools::Long nDest = ( nDiff > 0 ? aRange.Max() : aRange.Min() ) + nDiff;
+ if ( nDiff > 0 )
+ nDest++;
+ DBG_ASSERT( ( nDest >= 0 ) && ( nDest <= pImpEditView->pEditEngine->GetParagraphCount() ), "MoveParagraphs - wrong Parameters!" );
+ MoveParagraphs( aRange, sal::static_int_cast< sal_Int32 >( nDest ) );
+}
+
+void EditView::SetBackgroundColor( const Color& rColor )
+{
+ pImpEditView->SetBackgroundColor( rColor );
+ pImpEditView->pEditEngine->SetBackgroundColor( rColor );
+}
+
+Color const & EditView::GetBackgroundColor() const
+{
+ return pImpEditView->GetBackgroundColor();
+}
+
+void EditView::RegisterViewShell(OutlinerViewShell* pViewShell)
+{
+ pImpEditView->RegisterViewShell(pViewShell);
+}
+
+void EditView::RegisterOtherShell(OutlinerViewShell* pOtherShell)
+{
+ pImpEditView->RegisterOtherShell(pOtherShell);
+}
+
+void EditView::SetControlWord( EVControlBits nWord )
+{
+ pImpEditView->nControl = nWord;
+}
+
+EVControlBits EditView::GetControlWord() const
+{
+ return pImpEditView->nControl;
+}
+
+std::unique_ptr<EditTextObject> EditView::CreateTextObject()
+{
+ return pImpEditView->pEditEngine->pImpEditEngine->CreateTextObject( pImpEditView->GetEditSelection() );
+}
+
+void EditView::InsertText( const EditTextObject& rTextObject )
+{
+ pImpEditView->DrawSelectionXOR();
+
+ pImpEditView->pEditEngine->UndoActionStart( EDITUNDO_INSERT );
+ EditSelection aTextSel( pImpEditView->pEditEngine->InsertText( rTextObject, pImpEditView->GetEditSelection() ) );
+ pImpEditView->pEditEngine->UndoActionEnd();
+
+ aTextSel.Min() = aTextSel.Max(); // Selection not retained.
+ pImpEditView->SetEditSelection( aTextSel );
+ if (pImpEditView->pEditEngine->IsUpdateLayout())
+ pImpEditView->pEditEngine->FormatAndLayout( this );
+}
+
+void EditView::InsertText( css::uno::Reference< css::datatransfer::XTransferable > const & xDataObj, const OUString& rBaseURL, bool bUseSpecial )
+{
+ pImpEditView->pEditEngine->UndoActionStart( EDITUNDO_INSERT );
+ pImpEditView->DeleteSelected();
+ EditSelection aTextSel =
+ pImpEditView->pEditEngine->InsertText(xDataObj, rBaseURL, pImpEditView->GetEditSelection().Max(), bUseSpecial);
+ pImpEditView->pEditEngine->UndoActionEnd();
+
+ aTextSel.Min() = aTextSel.Max(); // Selection not retained.
+ pImpEditView->SetEditSelection( aTextSel );
+ if (pImpEditView->pEditEngine->IsUpdateLayout())
+ pImpEditView->pEditEngine->FormatAndLayout( this );
+}
+
+bool EditView::SetEditEngineUpdateLayout( bool bUpdate )
+{
+ return pImpEditView->pEditEngine->pImpEditEngine->SetUpdateLayout( bUpdate, this );
+}
+
+void EditView::ForceLayoutCalculation()
+{
+ pImpEditView->pEditEngine->pImpEditEngine->SetUpdateLayout( true, this, true );
+}
+
+SfxStyleSheet* EditView::GetStyleSheet()
+{
+ EditSelection aSel( pImpEditView->GetEditSelection() );
+ aSel.Adjust( pImpEditView->pEditEngine->GetEditDoc() );
+ sal_Int32 nStartPara = pImpEditView->pEditEngine->GetEditDoc().GetPos( aSel.Min().GetNode() );
+ sal_Int32 nEndPara = pImpEditView->pEditEngine->GetEditDoc().GetPos( aSel.Max().GetNode() );
+
+ SfxStyleSheet* pStyle = nullptr;
+ for ( sal_Int32 n = nStartPara; n <= nEndPara; n++ )
+ {
+ SfxStyleSheet* pTmpStyle = pImpEditView->pEditEngine->GetStyleSheet( n );
+ if ( ( n != nStartPara ) && ( pStyle != pTmpStyle ) )
+ return nullptr; // Not unique.
+ pStyle = pTmpStyle;
+ }
+ return pStyle;
+}
+
+const SfxStyleSheet* EditView::GetStyleSheet() const
+{
+ return const_cast< EditView* >( this )->GetStyleSheet();
+}
+
+bool EditView::IsInsertMode() const
+{
+ return pImpEditView->IsInsertMode();
+}
+
+void EditView::SetInsertMode( bool bInsert )
+{
+ pImpEditView->SetInsertMode( bInsert );
+}
+
+void EditView::SetAnchorMode( EEAnchorMode eMode )
+{
+ pImpEditView->SetAnchorMode( eMode );
+}
+
+EEAnchorMode EditView::GetAnchorMode() const
+{
+ return pImpEditView->GetAnchorMode();
+}
+
+void EditView::TransliterateText( TransliterationFlags nTransliterationMode )
+{
+ EditSelection aOldSel( pImpEditView->GetEditSelection() );
+ EditSelection aNewSel = pImpEditView->pEditEngine->TransliterateText( pImpEditView->GetEditSelection(), nTransliterationMode );
+ if ( aNewSel != aOldSel )
+ {
+ pImpEditView->DrawSelectionXOR();
+ pImpEditView->SetEditSelection( aNewSel );
+ pImpEditView->DrawSelectionXOR();
+ }
+}
+
+void EditView::CompleteAutoCorrect( vcl::Window const * pFrameWin )
+{
+ if ( !HasSelection() && pImpEditView->pEditEngine->pImpEditEngine->GetStatus().DoAutoCorrect() )
+ {
+ pImpEditView->DrawSelectionXOR();
+ EditSelection aSel = pImpEditView->GetEditSelection();
+ aSel = pImpEditView->pEditEngine->EndOfWord( aSel.Max() );
+ aSel = pImpEditView->pEditEngine->pImpEditEngine->AutoCorrect( aSel, 0, !IsInsertMode(), pFrameWin );
+ pImpEditView->SetEditSelection( aSel );
+ if ( pImpEditView->pEditEngine->IsModified() )
+ pImpEditView->pEditEngine->FormatAndLayout( this );
+ }
+}
+
+EESpellState EditView::StartSpeller(weld::Widget* pDialogParent, bool bMultipleDoc)
+{
+ if ( !pImpEditView->pEditEngine->pImpEditEngine->GetSpeller().is() )
+ return EESpellState::NoSpeller;
+
+ return pImpEditView->pEditEngine->pImpEditEngine->Spell(this, pDialogParent, bMultipleDoc);
+}
+
+EESpellState EditView::StartThesaurus(weld::Widget* pDialogParent)
+{
+ if ( !pImpEditView->pEditEngine->pImpEditEngine->GetSpeller().is() )
+ return EESpellState::NoSpeller;
+
+ return pImpEditView->pEditEngine->pImpEditEngine->StartThesaurus(this, pDialogParent);
+}
+
+void EditView::StartTextConversion(weld::Widget* pDialogParent,
+ LanguageType nSrcLang, LanguageType nDestLang, const vcl::Font *pDestFont,
+ sal_Int32 nOptions, bool bIsInteractive, bool bMultipleDoc )
+{
+ pImpEditView->pEditEngine->pImpEditEngine->Convert(this, pDialogParent, nSrcLang, nDestLang, pDestFont, nOptions, bIsInteractive, bMultipleDoc);
+}
+
+sal_Int32 EditView::StartSearchAndReplace( const SvxSearchItem& rSearchItem )
+{
+ return pImpEditView->pEditEngine->pImpEditEngine->StartSearchAndReplace( this, rSearchItem );
+}
+
+bool EditView::IsCursorAtWrongSpelledWord()
+{
+ bool bIsWrong = false;
+ if ( !HasSelection() )
+ {
+ EditPaM aPaM = pImpEditView->GetEditSelection().Max();
+ bIsWrong = pImpEditView->IsWrongSpelledWord( aPaM, false/*bMarkIfWrong*/ );
+ }
+ return bIsWrong;
+}
+
+bool EditView::IsWrongSpelledWordAtPos( const Point& rPosPixel, bool bMarkIfWrong )
+{
+ Point aPos(pImpEditView->GetOutputDevice().PixelToLogic(rPosPixel));
+ aPos = pImpEditView->GetDocPos( aPos );
+ EditPaM aPaM = pImpEditView->pEditEngine->GetPaM(aPos, false);
+ return pImpEditView->IsWrongSpelledWord( aPaM , bMarkIfWrong );
+}
+
+static void LOKSendSpellPopupMenu(const weld::Menu& rMenu, LanguageType nGuessLangWord,
+ LanguageType nGuessLangPara, sal_uInt16 nSuggestions)
+{
+ if (!comphelper::LibreOfficeKit::isActive())
+ return;
+
+ // Generate the menu structure and send it to the client code.
+ SfxViewShell* pViewShell = SfxViewShell::Current();
+ if (!pViewShell)
+ return;
+
+ boost::property_tree::ptree aMenu;
+
+ boost::property_tree::ptree aItemTree;
+ if (nSuggestions)
+ {
+ for(int i = 0; i < nSuggestions; ++i)
+ {
+ OUString sItemId = OUString::number(MN_ALTSTART + i);
+ OUString sText = rMenu.get_label(sItemId);
+ aItemTree.put("text", sText.toUtf8().getStr());
+ aItemTree.put("type", "command");
+ OUString sCommandString = ".uno:SpellCheckApplySuggestion?ApplyRule:string=Spelling_" + sText;
+ aItemTree.put("command", sCommandString.toUtf8().getStr());
+ aItemTree.put("enabled", rMenu.get_sensitive(sItemId));
+ aMenu.push_back(std::make_pair("", aItemTree));
+ aItemTree.clear();
+ }
+
+ aItemTree.put("type", "separator");
+ aMenu.push_back(std::make_pair("", aItemTree));
+ aItemTree.clear();
+ }
+
+ // First we need to set item commands for the context menu.
+ OUString aTmpWord( SvtLanguageTable::GetLanguageString( nGuessLangWord ) );
+ OUString aTmpPara( SvtLanguageTable::GetLanguageString( nGuessLangPara ) );
+
+ aItemTree.put("text", rMenu.get_label("ignore").toUtf8().getStr());
+ aItemTree.put("type", "command");
+ aItemTree.put("command", ".uno:SpellCheckIgnoreAll?Type:string=Spelling");
+ aItemTree.put("enabled", rMenu.get_sensitive("ignore"));
+ aMenu.push_back(std::make_pair("", aItemTree));
+ aItemTree.clear();
+
+ aItemTree.put("type", "separator");
+ aMenu.push_back(std::make_pair("", aItemTree));
+ aItemTree.clear();
+
+ aItemTree.put("text", rMenu.get_label("wordlanguage").toUtf8().getStr());
+ aItemTree.put("type", "command");
+ OUString sCommandString = ".uno:LanguageStatus?Language:string=Current_" + aTmpWord;
+ aItemTree.put("command", sCommandString.toUtf8().getStr());
+ aItemTree.put("enabled", rMenu.get_sensitive("wordlanguage"));
+ aMenu.push_back(std::make_pair("", aItemTree));
+ aItemTree.clear();
+
+ aItemTree.put("text", rMenu.get_label("paralanguage").toUtf8().getStr());
+ aItemTree.put("type", "command");
+ sCommandString = ".uno:LanguageStatus?Language:string=Paragraph_" + aTmpPara;
+ aItemTree.put("command", sCommandString.toUtf8().getStr());
+ aItemTree.put("enabled", rMenu.get_sensitive("paralanguage"));
+ aMenu.push_back(std::make_pair("", aItemTree));
+ aItemTree.clear();
+
+ boost::property_tree::ptree aRoot;
+ aRoot.add_child("menu", aMenu);
+
+ std::stringstream aStream;
+ boost::property_tree::write_json(aStream, aRoot, true);
+ pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CONTEXT_MENU, OString(aStream.str()));
+}
+
+bool EditView::ExecuteSpellPopup(const Point& rPosPixel, const Link<SpellCallbackInfo&,void> &rCallBack)
+{
+ OutputDevice& rDevice = pImpEditView->GetOutputDevice();
+ Point aPos(rDevice.PixelToLogic(rPosPixel));
+ aPos = pImpEditView->GetDocPos( aPos );
+ EditPaM aPaM = pImpEditView->pEditEngine->GetPaM(aPos, false);
+ Reference< linguistic2::XSpellChecker1 > xSpeller( pImpEditView->pEditEngine->pImpEditEngine->GetSpeller() );
+ ESelection aOldSel = GetSelection();
+ if ( !(xSpeller.is() && pImpEditView->IsWrongSpelledWord( aPaM, true )) )
+ return false;
+
+ // PaMtoEditCursor returns Logical units
+ tools::Rectangle aTempRect = pImpEditView->pEditEngine->pImpEditEngine->PaMtoEditCursor( aPaM, GetCursorFlags::TextOnly );
+ // GetWindowPos works in Logical units
+ aTempRect = pImpEditView->GetWindowPos(aTempRect);
+ // Convert to pixels
+ aTempRect = rDevice.LogicToPixel(aTempRect);
+
+ weld::Widget* pPopupParent = pImpEditView->GetPopupParent(aTempRect);
+ std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pPopupParent, "editeng/ui/spellmenu.ui"));
+ std::unique_ptr<weld::Menu> xPopupMenu(xBuilder->weld_menu("editviewspellmenu"));
+ std::unique_ptr<weld::Menu> xInsertMenu(xBuilder->weld_menu("insertmenu")); // add word to user-dictionaries
+ std::unique_ptr<weld::Menu> xAutoMenu(xBuilder->weld_menu("automenu"));
+
+ EditPaM aPaM2( aPaM );
+ aPaM2.SetIndex( aPaM2.GetIndex()+1 );
+
+ // Are there any replace suggestions?
+ OUString aSelected( GetSelected() );
+
+ // restrict the maximal number of suggestions displayed
+ // in the context menu.
+ // Note: That could of course be done by clipping the
+ // resulting sequence but the current third party
+ // implementations result differs greatly if the number of
+ // suggestions to be returned gets changed. Statistically
+ // it gets much better if told to return e.g. only 7 strings
+ // than returning e.g. 16 suggestions and using only the
+ // first 7. Thus we hand down the value to use to that
+ // implementation here by providing an additional parameter.
+ Sequence< PropertyValue > aPropVals { comphelper::makePropertyValue(UPN_MAX_NUMBER_OF_SUGGESTIONS, sal_Int16(7)) };
+
+ // Are there any replace suggestions?
+ Reference< linguistic2::XSpellAlternatives > xSpellAlt =
+ xSpeller->spell( aSelected, static_cast<sal_uInt16>(pImpEditView->pEditEngine->pImpEditEngine->GetLanguage( aPaM2 ).nLang), aPropVals );
+
+ Reference< linguistic2::XLanguageGuessing > xLangGuesser( EditDLL::Get().GetGlobalData()->GetLanguageGuesser() );
+
+ // check if text might belong to a different language...
+ LanguageType nGuessLangWord = LANGUAGE_NONE;
+ LanguageType nGuessLangPara = LANGUAGE_NONE;
+ if (xSpellAlt.is() && xLangGuesser.is())
+ {
+ OUString aParaText;
+ ContentNode *pNode = aPaM.GetNode();
+ if (pNode)
+ {
+ aParaText = pNode->GetString();
+ }
+ else
+ {
+ OSL_FAIL( "content node is NULL" );
+ }
+
+ nGuessLangWord = CheckLanguage( xSpellAlt->getWord(), xSpeller, xLangGuesser, false );
+ nGuessLangPara = CheckLanguage( aParaText, xSpeller, xLangGuesser, true );
+ }
+ if (nGuessLangWord != LANGUAGE_NONE || nGuessLangPara != LANGUAGE_NONE)
+ {
+ // make sure LANGUAGE_NONE gets not used as menu entry
+ if (nGuessLangWord == LANGUAGE_NONE)
+ nGuessLangWord = nGuessLangPara;
+ if (nGuessLangPara == LANGUAGE_NONE)
+ nGuessLangPara = nGuessLangWord;
+
+ xPopupMenu->append_separator("separator1");
+ OUString aTmpWord( SvtLanguageTable::GetLanguageString( nGuessLangWord ) );
+ OUString aTmpPara( SvtLanguageTable::GetLanguageString( nGuessLangPara ) );
+ OUString aWordStr( EditResId( RID_STR_WORD ) );
+ aWordStr = aWordStr.replaceFirst( "%x", aTmpWord );
+ OUString aParaStr( EditResId( RID_STR_PARAGRAPH ) );
+ aParaStr = aParaStr.replaceFirst( "%x", aTmpPara );
+ xPopupMenu->append("wordlanguage", aWordStr);
+ xPopupMenu->append("paralanguage", aParaStr);
+ }
+
+ // Replace suggestions...
+ Sequence< OUString > aAlt;
+ if (xSpellAlt.is())
+ aAlt = xSpellAlt->getAlternatives();
+ const OUString *pAlt = aAlt.getConstArray();
+ sal_uInt16 nWords = static_cast<sal_uInt16>(aAlt.getLength());
+ if ( nWords )
+ {
+ for ( sal_uInt16 nW = 0; nW < nWords; nW++ )
+ {
+ OUString aAlternate( pAlt[nW] );
+ OUString sId(OUString::number(MN_ALTSTART + nW));
+ xPopupMenu->insert(nW, sId, aAlternate, nullptr, nullptr, nullptr, TRISTATE_INDET);
+ xAutoMenu->append(sId, aAlternate);
+ }
+ xPopupMenu->insert_separator(nWords, "separator2");
+ }
+ else
+ {
+ xAutoMenu.reset();
+ xPopupMenu->remove("autocorrect");
+ }
+
+ SvtLinguConfig aCfg;
+
+ Reference< linguistic2::XSearchableDictionaryList > xDicList( LinguMgr::GetDictionaryList() );
+ Sequence< Reference< linguistic2::XDictionary > > aDics;
+ if (xDicList.is())
+ {
+ const Reference< linguistic2::XDictionary > *pDic = nullptr;
+ // add the default positive dictionary to dic-list (if not already done).
+ // This is to ensure that there is at least one dictionary to which
+ // words could be added.
+ uno::Reference< linguistic2::XDictionary > xDic( LinguMgr::GetStandardDic() );
+ if (xDic.is())
+ xDic->setActive( true );
+
+ aDics = xDicList->getDictionaries();
+ pDic = aDics.getConstArray();
+ LanguageType nCheckedLanguage = pImpEditView->pEditEngine->pImpEditEngine->GetLanguage( aPaM2 ).nLang;
+ sal_uInt16 nDicCount = static_cast<sal_uInt16>(aDics.getLength());
+ for (sal_uInt16 i = 0; i < nDicCount; i++)
+ {
+ uno::Reference< linguistic2::XDictionary > xDicTmp = pDic[i];
+ if (!xDicTmp.is() || LinguMgr::GetIgnoreAllList() == xDicTmp)
+ continue;
+
+ uno::Reference< frame::XStorable > xStor( xDicTmp, uno::UNO_QUERY );
+ LanguageType nActLanguage = LanguageTag( xDicTmp->getLocale() ).getLanguageType();
+ if( xDicTmp->isActive()
+ && xDicTmp->getDictionaryType() != linguistic2::DictionaryType_NEGATIVE
+ && (nCheckedLanguage == nActLanguage || LANGUAGE_NONE == nActLanguage )
+ && (!xStor.is() || !xStor->isReadonly()) )
+ {
+ OUString sImage;
+
+ uno::Reference< lang::XServiceInfo > xSvcInfo( xDicTmp, uno::UNO_QUERY );
+ if (xSvcInfo.is())
+ {
+ OUString aDictionaryImageUrl( aCfg.GetSpellAndGrammarContextDictionaryImage(
+ xSvcInfo->getImplementationName()) );
+ if (!aDictionaryImageUrl.isEmpty() )
+ sImage = aDictionaryImageUrl;
+ }
+
+ if (sImage.isEmpty())
+ {
+ xInsertMenu->append(OUString::number(MN_DICTSTART + i), xDicTmp->getName());
+ }
+ else
+ {
+ Image aImage(sImage);
+ ScopedVclPtr<VirtualDevice> xVirDev(pPopupParent->create_virtual_device());
+ Size aSize(aImage.GetSizePixel());
+ xVirDev->SetOutputSizePixel(aSize);
+ xVirDev->DrawImage(Point(0, 0), aImage);
+ xInsertMenu->append(OUString::number(MN_DICTSTART + i), xDicTmp->getName(), *xVirDev);
+ }
+ aDicNameSingle = xDicTmp->getName();
+ }
+ }
+ }
+
+ if (xInsertMenu->n_children() != 1)
+ xPopupMenu->remove("add");
+ if (xInsertMenu->n_children() < 2)
+ {
+ xInsertMenu.reset();
+ xPopupMenu->remove("insert");
+ }
+
+ //tdf#106123 store and restore the EditPaM around the menu Execute
+ //because the loss of focus in the current editeng causes writer
+ //annotations to save their contents, making the pContent of the
+ //current EditPams invalid
+ EPaM aP = pImpEditView->pEditEngine->pImpEditEngine->CreateEPaM(aPaM);
+ EPaM aP2 = pImpEditView->pEditEngine->pImpEditEngine->CreateEPaM(aPaM2);
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ xPopupMenu->remove("autocorrect");
+ xPopupMenu->remove("autocorrectdlg");
+
+ LOKSendSpellPopupMenu(*xPopupMenu, nGuessLangWord, nGuessLangPara, nWords);
+ return true;
+ }
+
+ OUString sId = xPopupMenu->popup_at_rect(pPopupParent, aTempRect);
+
+ aPaM2 = pImpEditView->pEditEngine->pImpEditEngine->CreateEditPaM(aP2);
+ aPaM = pImpEditView->pEditEngine->pImpEditEngine->CreateEditPaM(aP);
+
+ if (sId == "ignore")
+ {
+ OUString aWord = pImpEditView->SpellIgnoreWord();
+ SpellCallbackInfo aInf( SpellCallbackCommand::IGNOREWORD, aWord );
+ rCallBack.Call(aInf);
+ SetSelection( aOldSel );
+ }
+ else if (sId == "wordlanguage" || sId == "paralanguage")
+ {
+ LanguageType nLangToUse = (sId == "wordlanguage") ? nGuessLangWord : nGuessLangPara;
+ SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( nLangToUse );
+
+ SfxItemSet aAttrs = GetEditEngine()->GetEmptyItemSet();
+ if (nScriptType == SvtScriptType::LATIN)
+ aAttrs.Put( SvxLanguageItem( nLangToUse, EE_CHAR_LANGUAGE ) );
+ if (nScriptType == SvtScriptType::COMPLEX)
+ aAttrs.Put( SvxLanguageItem( nLangToUse, EE_CHAR_LANGUAGE_CTL ) );
+ if (nScriptType == SvtScriptType::ASIAN)
+ aAttrs.Put( SvxLanguageItem( nLangToUse, EE_CHAR_LANGUAGE_CJK ) );
+ if (sId == "paralanguage")
+ {
+ ESelection aSel = GetSelection();
+ aSel.nStartPos = 0;
+ aSel.nEndPos = EE_TEXTPOS_ALL;
+ SetSelection( aSel );
+ }
+ SetAttribs( aAttrs );
+ pImpEditView->pEditEngine->pImpEditEngine->StartOnlineSpellTimer();
+
+ SpellCallbackInfo aInf((sId == "wordlanguage") ? SpellCallbackCommand::WORDLANGUAGE : SpellCallbackCommand::PARALANGUAGE);
+ rCallBack.Call(aInf);
+ SetSelection( aOldSel );
+ }
+ else if (sId == "check")
+ {
+ SpellCallbackInfo aInf( SpellCallbackCommand::STARTSPELLDLG, OUString() );
+ rCallBack.Call(aInf);
+ }
+ else if (sId == "autocorrectdlg")
+ {
+ SpellCallbackInfo aInf( SpellCallbackCommand::AUTOCORRECT_OPTIONS, OUString() );
+ rCallBack.Call(aInf);
+ }
+ else if ( sId.toInt32() >= MN_DICTSTART || sId == "add")
+ {
+ OUString aDicName;
+ if (sId.toInt32() >= MN_DICTSTART)
+ {
+ assert(xInsertMenu && "this case only occurs when xInsertMenu exists");
+ // strip_mnemonic is necessary to retrieve the correct dictionary name
+ aDicName = pPopupParent->strip_mnemonic(xInsertMenu->get_label(sId));
+ }
+ else
+ aDicName = aDicNameSingle;
+
+ uno::Reference< linguistic2::XDictionary > xDic;
+ if (xDicList.is())
+ xDic = xDicList->getDictionaryByName( aDicName );
+
+ if (xDic.is())
+ xDic->add( aSelected, false, OUString() );
+ // save modified user-dictionary if it is persistent
+ Reference< frame::XStorable > xSavDic( xDic, UNO_QUERY );
+ if (xSavDic.is())
+ xSavDic->store();
+
+ aPaM.GetNode()->GetWrongList()->ResetInvalidRange(0, aPaM.GetNode()->Len());
+ pImpEditView->pEditEngine->pImpEditEngine->StartOnlineSpellTimer();
+
+ SpellCallbackInfo aInf( SpellCallbackCommand::ADDTODICTIONARY, aSelected );
+ rCallBack.Call(aInf);
+ SetSelection( aOldSel );
+ }
+ else if ( sId.toInt32() >= MN_AUTOSTART )
+ {
+ DBG_ASSERT(sId.toInt32() - MN_AUTOSTART < aAlt.getLength(), "index out of range");
+ OUString aWord = pAlt[sId.toInt32() - MN_AUTOSTART];
+ SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
+ if ( pAutoCorrect )
+ pAutoCorrect->PutText( aSelected, aWord, pImpEditView->pEditEngine->pImpEditEngine->GetLanguage( aPaM2 ).nLang );
+ InsertText( aWord );
+ }
+ else if ( sId.toInt32() >= MN_ALTSTART ) // Replace
+ {
+ DBG_ASSERT(sId.toInt32() - MN_ALTSTART < aAlt.getLength(), "index out of range");
+ OUString aWord = pAlt[sId.toInt32() - MN_ALTSTART];
+ InsertText( aWord );
+ }
+ else
+ {
+ SetSelection( aOldSel );
+ }
+ return true;
+}
+
+OUString EditView::SpellIgnoreWord()
+{
+ return pImpEditView->SpellIgnoreWord();
+}
+
+void EditView::SelectCurrentWord( sal_Int16 nWordType )
+{
+ EditSelection aCurSel( pImpEditView->GetEditSelection() );
+ pImpEditView->DrawSelectionXOR();
+ aCurSel = pImpEditView->pEditEngine->SelectWord(aCurSel.Max(), nWordType);
+ pImpEditView->SetEditSelection( aCurSel );
+ pImpEditView->DrawSelectionXOR();
+ ShowCursor( true, false );
+}
+
+void EditView::InsertParaBreak()
+{
+ pImpEditView->pEditEngine->UndoActionStart(EDITUNDO_INSERT);
+ pImpEditView->DeleteSelected();
+ EditPaM aPaM(pImpEditView->pEditEngine->InsertParaBreak(pImpEditView->GetEditSelection()));
+ pImpEditView->pEditEngine->UndoActionEnd();
+ pImpEditView->SetEditSelection(EditSelection(aPaM, aPaM));
+ if (pImpEditView->pEditEngine->IsUpdateLayout())
+ pImpEditView->pEditEngine->FormatAndLayout(this);
+}
+
+void EditView::InsertField( const SvxFieldItem& rFld )
+{
+ EditEngine* pEE = pImpEditView->pEditEngine;
+ pImpEditView->DrawSelectionXOR();
+ pEE->UndoActionStart( EDITUNDO_INSERT );
+ EditPaM aPaM( pEE->InsertField( pImpEditView->GetEditSelection(), rFld ) );
+ pEE->UndoActionEnd();
+ pImpEditView->SetEditSelection( EditSelection( aPaM, aPaM ) );
+ pEE->UpdateFields();
+ if (pImpEditView->pEditEngine->IsUpdateLayout())
+ pEE->FormatAndLayout( this );
+}
+
+const SvxFieldItem* EditView::GetFieldUnderMousePointer() const
+{
+ sal_Int32 nPara;
+ sal_Int32 nPos;
+ return GetFieldUnderMousePointer( nPara, nPos );
+}
+
+const SvxFieldItem* EditView::GetField( const Point& rPos, sal_Int32* pPara, sal_Int32* pPos ) const
+{
+ return pImpEditView->GetField( rPos, pPara, pPos );
+}
+
+const SvxFieldItem* EditView::GetFieldUnderMousePointer( sal_Int32& nPara, sal_Int32& nPos ) const
+{
+ Point aPos;
+ if (EditViewCallbacks* pEditViewCallbacks = pImpEditView->getEditViewCallbacks())
+ aPos = pEditViewCallbacks->EditViewPointerPosPixel();
+ else
+ aPos = pImpEditView->GetWindow()->GetPointerPosPixel();
+ OutputDevice& rDevice = pImpEditView->GetOutputDevice();
+ aPos = rDevice.PixelToLogic(aPos);
+ return GetField( aPos, &nPara, &nPos );
+}
+
+const SvxFieldItem* EditView::GetFieldAtSelection(bool bAlsoCheckBeforeCursor) const
+{
+ bool* pIsBeforeCursor = bAlsoCheckBeforeCursor ? &bAlsoCheckBeforeCursor : nullptr;
+ return GetFieldAtSelection(pIsBeforeCursor);
+}
+
+// If pIsBeforeCursor != nullptr, the position before the cursor will also be checked for a field
+// and pIsBeforeCursor will return true if that fallback field is returned.
+// If no field is returned, the value in pIsBeforeCursor is meaningless.
+const SvxFieldItem* EditView::GetFieldAtSelection(bool* pIsBeforeCursor) const
+{
+ // a field is a dummy character - so it cannot span nodes or be a selection larger than 1
+ EditSelection aSel( pImpEditView->GetEditSelection() );
+ if (aSel.Min().GetNode() != aSel.Max().GetNode())
+ return nullptr;
+
+ // normalize: min < max
+ aSel.Adjust( pImpEditView->pEditEngine->GetEditDoc() );
+
+ const sal_Int32 nMinIndex = aSel.Min().GetIndex();
+ const sal_Int32 nMaxIndex = aSel.Max().GetIndex();
+ if (nMaxIndex > nMinIndex + 1)
+ return nullptr;
+
+ // Only when cursor is in font of field, no selection,
+ // or only selecting field
+ bool bAlsoCheckBeforeCursor = false;
+ if (pIsBeforeCursor)
+ {
+ *pIsBeforeCursor = false;
+ bAlsoCheckBeforeCursor = nMaxIndex == nMinIndex;
+ }
+ const SvxFieldItem* pFoundBeforeCursor = nullptr;
+ const CharAttribList::AttribsType& rAttrs = aSel.Min().GetNode()->GetCharAttribs().GetAttribs();
+ for (const auto& rAttr: rAttrs)
+ {
+ if (rAttr->Which() == EE_FEATURE_FIELD)
+ {
+ DBG_ASSERT(dynamic_cast<const SvxFieldItem*>(rAttr->GetItem()), "No FieldItem...");
+ if (rAttr->GetStart() == nMinIndex)
+ return static_cast<const SvxFieldItem*>(rAttr->GetItem());
+
+ // perhaps the cursor is behind the field?
+ if (nMinIndex && rAttr->GetStart() == nMinIndex - 1)
+ pFoundBeforeCursor = static_cast<const SvxFieldItem*>(rAttr->GetItem());
+ }
+ }
+ if (bAlsoCheckBeforeCursor)
+ {
+ *pIsBeforeCursor = /*(bool)*/pFoundBeforeCursor;
+ return pFoundBeforeCursor;
+ }
+ return nullptr;
+}
+
+void EditView::SelectFieldAtCursor()
+{
+ bool bIsBeforeCursor = false;
+ const SvxFieldItem* pFieldItem = GetFieldAtSelection(&bIsBeforeCursor);
+ if (!pFieldItem)
+ return;
+
+ // Make sure the whole field is selected
+ // A field is represented by a dummy character - so it cannot be a selection larger than 1
+ ESelection aSel = GetSelection();
+ if (aSel.nStartPos == aSel.nEndPos) // not yet selected
+ {
+ if (bIsBeforeCursor)
+ {
+ assert (aSel.nStartPos);
+ --aSel.nStartPos;
+ }
+ else
+ aSel.nEndPos++;
+ SetSelection(aSel);
+ }
+ else
+ assert(std::abs(aSel.nStartPos - aSel.nEndPos) == 1);
+}
+
+const SvxFieldData* EditView::GetFieldUnderMouseOrInSelectionOrAtCursor(bool bAlsoCheckBeforeCursor) const
+{
+ const SvxFieldItem* pFieldItem = GetFieldUnderMousePointer();
+ if (!pFieldItem)
+ pFieldItem = GetFieldAtSelection(bAlsoCheckBeforeCursor);
+
+ return pFieldItem ? pFieldItem->GetField() : nullptr;
+}
+
+sal_Int32 EditView::countFieldsOffsetSum(sal_Int32 nPara, sal_Int32 nPos, bool bCanOverflow) const
+{
+ if (!pImpEditView || !pImpEditView->pEditEngine)
+ return 0;
+
+ int nOffset = 0;
+
+ for (int nCurrentPara = 0; nCurrentPara <= nPara; nCurrentPara++)
+ {
+ int nFields = pImpEditView->pEditEngine->GetFieldCount( nCurrentPara );
+ for (int nField = 0; nField < nFields; nField++)
+ {
+ EFieldInfo aFieldInfo
+ = pImpEditView->pEditEngine->GetFieldInfo( nCurrentPara, nField );
+
+ bool bLastPara = nCurrentPara == nPara;
+ sal_Int32 nFieldPos = aFieldInfo.aPosition.nIndex;
+
+ if (bLastPara && nFieldPos >= nPos)
+ break;
+
+ sal_Int32 nFieldLen = aFieldInfo.aCurrentText.getLength();
+
+ // position in the middle of a field
+ if (!bCanOverflow && bLastPara && nFieldPos + nFieldLen > nPos)
+ nFieldLen = nPos - nFieldPos;
+
+ nOffset += nFieldLen - 1;
+ }
+ }
+
+ return nOffset;
+}
+
+sal_Int32 EditView::GetPosNoField(sal_Int32 nPara, sal_Int32 nPos) const
+{
+ sal_Int32 nOffset = countFieldsOffsetSum(nPara, nPos, false);
+ assert(nPos >= nOffset);
+ return nPos - nOffset;
+}
+
+sal_Int32 EditView::GetPosWithField(sal_Int32 nPara, sal_Int32 nPos) const
+{
+ sal_Int32 nOffset = countFieldsOffsetSum(nPara, nPos, true);
+ return nPos + nOffset;
+}
+
+void EditView::SetInvalidateMore( sal_uInt16 nPixel )
+{
+ pImpEditView->SetInvalidateMore( nPixel );
+}
+
+sal_uInt16 EditView::GetInvalidateMore() const
+{
+ return pImpEditView->GetInvalidateMore();
+}
+
+static void ChangeFontSizeImpl( EditView* pEditView, bool bGrow, const ESelection& rSel, const FontList* pFontList )
+{
+ pEditView->SetSelection( rSel );
+
+ SfxItemSet aSet( pEditView->GetAttribs() );
+ if( EditView::ChangeFontSize( bGrow, aSet, pFontList ) )
+ {
+ SfxItemSet aNewSet( pEditView->GetEmptyItemSet() );
+ aNewSet.Put( aSet.Get( EE_CHAR_FONTHEIGHT ) );
+ aNewSet.Put( aSet.Get( EE_CHAR_FONTHEIGHT_CJK ) );
+ aNewSet.Put( aSet.Get( EE_CHAR_FONTHEIGHT_CTL ) );
+ pEditView->SetAttribs( aNewSet );
+ }
+}
+
+void EditView::ChangeFontSize( bool bGrow, const FontList* pFontList )
+{
+
+ EditEngine& rEditEngine = *pImpEditView->pEditEngine;
+
+ ESelection aSel( GetSelection() );
+ ESelection aOldSelection( aSel );
+ aSel.Adjust();
+
+ if( !aSel.HasRange() )
+ {
+ aSel = rEditEngine.GetWord( aSel, css::i18n::WordType::DICTIONARY_WORD );
+ }
+
+ if( aSel.HasRange() )
+ {
+ for( sal_Int32 nPara = aSel.nStartPara; nPara <= aSel.nEndPara; nPara++ )
+ {
+ std::vector<sal_Int32> aPortions;
+ rEditEngine.GetPortions( nPara, aPortions );
+
+ if( aPortions.empty() )
+ aPortions.push_back( rEditEngine.GetTextLen(nPara) );
+
+ const sal_Int32 nBeginPos = (nPara == aSel.nStartPara) ? aSel.nStartPos : 0;
+ const sal_Int32 nEndPos = (nPara == aSel.nEndPara) ? aSel.nEndPos : EE_TEXTPOS_ALL;
+
+ for ( size_t nPos = 0; nPos < aPortions.size(); ++nPos )
+ {
+ sal_Int32 nPortionEnd = aPortions[ nPos ];
+ sal_Int32 nPortionStart = nPos > 0 ? aPortions[ nPos - 1 ] : 0;
+
+ if( (nPortionEnd < nBeginPos) || (nPortionStart > nEndPos) )
+ continue;
+
+ if( nPortionStart < nBeginPos )
+ nPortionStart = nBeginPos;
+ if( nPortionEnd > nEndPos )
+ nPortionEnd = nEndPos;
+
+ if( nPortionStart == nPortionEnd )
+ continue;
+
+ ESelection aPortionSel( nPara, nPortionStart, nPara, nPortionEnd );
+ ChangeFontSizeImpl( this, bGrow, aPortionSel, pFontList );
+ }
+ }
+ }
+ else
+ {
+ ChangeFontSizeImpl( this, bGrow, aSel, pFontList );
+ }
+
+ SetSelection( aOldSelection );
+}
+
+bool EditView::ChangeFontSize( bool bGrow, SfxItemSet& rSet, const FontList* pFontList )
+{
+ if (!pFontList)
+ return false;
+
+ static const sal_uInt16 gFontSizeWichMap[] = { EE_CHAR_FONTHEIGHT, EE_CHAR_FONTHEIGHT_CJK, EE_CHAR_FONTHEIGHT_CTL, 0 };
+ bool bRet = false;
+
+ const sal_uInt16* pWhich = gFontSizeWichMap;
+ while( *pWhich )
+ {
+ SvxFontHeightItem aFontHeightItem( static_cast<const SvxFontHeightItem&>(rSet.Get( *pWhich )) );
+ tools::Long nHeight = aFontHeightItem.GetHeight();
+ const MapUnit eUnit = rSet.GetPool()->GetMetric( *pWhich );
+ nHeight = OutputDevice::LogicToLogic(nHeight * 10, eUnit, MapUnit::MapPoint);
+
+ const int* pAry = FontList::GetStdSizeAry();
+
+ if( bGrow )
+ {
+ while( *pAry )
+ {
+ if( *pAry > nHeight )
+ {
+ nHeight = *pAry;
+ break;
+ }
+ pAry++;
+ }
+
+ if( *pAry == 0 )
+ {
+ nHeight += (nHeight + 5) / 10;
+ if( nHeight > 9999 )
+ nHeight = 9999;
+ }
+
+ }
+ else if( *pAry )
+ {
+ bool bFound = false;
+ if( *pAry < nHeight )
+ {
+ pAry++;
+ while( *pAry )
+ {
+ if( *pAry >= nHeight )
+ {
+ nHeight = pAry[-1];
+ bFound = true;
+ break;
+ }
+ pAry++;
+ }
+ }
+
+ if( !bFound )
+ {
+ nHeight -= (nHeight + 5) / 10;
+ if( nHeight < 2 )
+ nHeight = 2;
+ }
+ }
+
+ if( (nHeight >= 2) && (nHeight <= 9999 ) )
+ {
+ nHeight = OutputDevice::LogicToLogic( nHeight, MapUnit::MapPoint, eUnit ) / 10;
+
+ if( nHeight != static_cast<tools::Long>(aFontHeightItem.GetHeight()) )
+ {
+ aFontHeightItem.SetHeight( nHeight );
+ rSet.Put( aFontHeightItem.CloneSetWhich(*pWhich) );
+ bRet = true;
+ }
+ }
+ pWhich++;
+ }
+ return bRet;
+}
+
+OUString EditView::GetSurroundingText() const
+{
+ EditSelection aSel( pImpEditView->GetEditSelection() );
+ aSel.Adjust( pImpEditView->pEditEngine->GetEditDoc() );
+
+ if( HasSelection() )
+ {
+ OUString aStr = pImpEditView->pEditEngine->GetSelected(aSel);
+
+ // Stop reconversion if the selected text includes a line break.
+ if ( aStr.indexOf( 0x0A ) == -1 )
+ return aStr;
+ else
+ return OUString();
+ }
+ else
+ {
+ aSel.Min().SetIndex( 0 );
+ aSel.Max().SetIndex( aSel.Max().GetNode()->Len() );
+ return pImpEditView->pEditEngine->GetSelected(aSel);
+ }
+}
+
+Selection EditView::GetSurroundingTextSelection() const
+{
+ ESelection aSelection( GetSelection() );
+ aSelection.Adjust();
+
+ if( HasSelection() )
+ {
+ EditSelection aSel( pImpEditView->GetEditSelection() );
+ aSel.Adjust( pImpEditView->pEditEngine->GetEditDoc() );
+ OUString aStr = pImpEditView->pEditEngine->GetSelected(aSel);
+
+ // Stop reconversion if the selected text includes a line break.
+ if ( aStr.indexOf( 0x0A ) == -1 )
+ return Selection( 0, aSelection.nEndPos - aSelection.nStartPos );
+ else
+ return Selection( 0, 0 );
+ }
+ else
+ {
+ return Selection( aSelection.nStartPos, aSelection.nEndPos );
+ }
+}
+
+bool EditView::DeleteSurroundingText(const Selection& rRange)
+{
+ ESelection aSel(GetSelection());
+ aSel.nEndPara = aSel.nStartPara;
+ aSel.nStartPos = rRange.Min();
+ aSel.nEndPos = rRange.Max();
+ SetSelection(aSel);
+ DeleteSelected();
+ return true;
+}
+
+void EditView::SetCursorLogicPosition(const Point& rPosition, bool bPoint, bool bClearMark)
+{
+ Point aDocPos(pImpEditView->GetDocPos(rPosition));
+ EditPaM aPaM = pImpEditView->pEditEngine->GetPaM(aDocPos);
+ EditSelection aSelection(pImpEditView->GetEditSelection());
+
+ // Explicitly create or delete the selection.
+ if (bClearMark)
+ {
+ pImpEditView->DeselectAll();
+ aSelection = pImpEditView->GetEditSelection();
+ }
+ else
+ pImpEditView->CreateAnchor();
+
+ if (bPoint)
+ aSelection.Max() = aPaM;
+ else
+ aSelection.Min() = aPaM;
+
+ if (pImpEditView->GetEditSelection().Min() != aSelection.Min())
+ {
+ const ContentNode* pNode(pImpEditView->GetEditSelection().Min().GetNode());
+ if (nullptr != pNode)
+ pNode->checkAndDeleteEmptyAttribs();
+ }
+
+ pImpEditView->DrawSelectionXOR(aSelection);
+ if (pImpEditView->GetEditSelection() != aSelection)
+ pImpEditView->SetEditSelection(aSelection);
+ ShowCursor(/*bGotoCursor=*/false);
+}
+
+void EditView::DrawSelectionXOR(OutlinerViewShell* pOtherShell)
+{
+ pImpEditView->RegisterOtherShell(pOtherShell);
+ pImpEditView->DrawSelectionXOR();
+ pImpEditView->RegisterOtherShell(nullptr);
+}
+
+void EditView::InitLOKSpecialPositioning(MapUnit eUnit,
+ const tools::Rectangle& rOutputArea,
+ const Point& rVisDocStartPos)
+{
+ pImpEditView->InitLOKSpecialPositioning(eUnit, rOutputArea, rVisDocStartPos);
+}
+
+void EditView::SetLOKSpecialOutputArea(const tools::Rectangle& rOutputArea)
+{
+ pImpEditView->SetLOKSpecialOutputArea(rOutputArea);
+}
+
+const tools::Rectangle & EditView::GetLOKSpecialOutputArea() const
+{
+ return pImpEditView->GetLOKSpecialOutputArea();
+}
+
+void EditView::SetLOKSpecialVisArea(const tools::Rectangle& rVisArea)
+{
+ pImpEditView->SetLOKSpecialVisArea(rVisArea);
+}
+
+tools::Rectangle EditView::GetLOKSpecialVisArea() const
+{
+ return pImpEditView->GetLOKSpecialVisArea();
+}
+
+bool EditView::HasLOKSpecialPositioning() const
+{
+ return pImpEditView->HasLOKSpecialPositioning();
+}
+
+void EditView::SetLOKSpecialFlags(LOKSpecialFlags eFlags)
+{
+ pImpEditView->SetLOKSpecialFlags(eFlags);
+}
+
+void EditView::SuppressLOKMessages(bool bSet)
+{
+ pImpEditView->SuppressLOKMessages(bSet);
+}
+
+bool EditView::IsSuppressLOKMessages() const
+{
+ return pImpEditView->IsSuppressLOKMessages();
+}
+
+void EditView::SetNegativeX(bool bSet)
+{
+ pImpEditView->SetNegativeX(bSet);
+}
+
+bool EditView::IsNegativeX() const
+{
+ return pImpEditView->IsNegativeX();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/edtspell.cxx b/editeng/source/editeng/edtspell.cxx
new file mode 100644
index 0000000000..c07361bd19
--- /dev/null
+++ b/editeng/source/editeng/edtspell.cxx
@@ -0,0 +1,708 @@
+/* -*- 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 "impedit.hxx"
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <editeng/editview.hxx>
+#include <editeng/editeng.hxx>
+#include <edtspell.hxx>
+#include <editeng/flditem.hxx>
+#include <svl/intitem.hxx>
+#include <svl/eitem.hxx>
+#include <editeng/unolingu.hxx>
+#include <com/sun/star/linguistic2/XDictionary.hpp>
+
+using namespace com::sun::star::uno;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::linguistic2;
+
+
+EditSpellWrapper::EditSpellWrapper(weld::Widget* pWindow,
+ bool bIsStart, EditView* pView )
+ : SvxSpellWrapper(pWindow, bIsStart, false/*bIsAllRight*/)
+{
+ SAL_WARN_IF( !pView, "editeng", "One view has to be abandoned!" );
+ // Keep IgnoreList, delete ReplaceList...
+ if (LinguMgr::GetChangeAllList().is())
+ LinguMgr::GetChangeAllList()->clear();
+ pEditView = pView;
+}
+
+void EditSpellWrapper::SpellStart( SvxSpellArea eArea )
+{
+ EditEngine* pEE = pEditView->GetEditEngine();
+ ImpEditEngine* pImpEE = pEditView->GetImpEditEngine();
+ SpellInfo* pSpellInfo = pImpEE->GetSpellInfo();
+
+ if ( eArea == SvxSpellArea::BodyStart )
+ {
+ // Is called when
+ // a) Spell-Forward has arrived at the end and should restart at the top
+ // IsEndDone() returns also true, when backward-spelling is started at the end!
+ if ( IsEndDone() )
+ {
+ pSpellInfo->bSpellToEnd = false;
+ pSpellInfo->aSpellTo = pSpellInfo->aSpellStart;
+ pEditView->GetImpEditView()->SetEditSelection(
+ pEE->GetEditDoc().GetStartPaM() );
+ }
+ else
+ {
+ pSpellInfo->bSpellToEnd = true;
+ pSpellInfo->aSpellTo = pImpEE->CreateEPaM(
+ pEE->GetEditDoc().GetStartPaM() );
+ }
+ }
+ else if ( eArea == SvxSpellArea::BodyEnd )
+ {
+ // Is called when
+ // a) Spell-Forward is launched
+ // IsStartDone() return also true, when forward-spelling is started at the beginning!
+ if ( !IsStartDone() )
+ {
+ pSpellInfo->bSpellToEnd = true;
+ pSpellInfo->aSpellTo = pImpEE->CreateEPaM(
+ pEE->GetEditDoc().GetEndPaM() );
+ }
+ else
+ {
+ pSpellInfo->bSpellToEnd = false;
+ pSpellInfo->aSpellTo = pSpellInfo->aSpellStart;
+ pEditView->GetImpEditView()->SetEditSelection(
+ pEE->GetEditDoc().GetEndPaM() );
+ }
+ }
+ else if ( eArea == SvxSpellArea::Body )
+ {
+ ; // Is handled by the App through SpellNextDocument
+ }
+ else
+ {
+ OSL_FAIL( "SpellStart: Unknown Area!" );
+ }
+}
+
+void EditSpellWrapper::SpellContinue()
+{
+ SetLast( pEditView->GetImpEditEngine()->ImpSpell( pEditView ) );
+}
+
+bool EditSpellWrapper::SpellMore()
+{
+ EditEngine* pEE = pEditView->GetEditEngine();
+ ImpEditEngine* pImpEE = pEditView->GetImpEditEngine();
+ SpellInfo* pSpellInfo = pImpEE->GetSpellInfo();
+ bool bMore = false;
+ if ( pSpellInfo->bMultipleDoc )
+ {
+ bMore = pEE->SpellNextDocument();
+ if ( bMore )
+ {
+ // The text has been entered into the engine, when backwards then
+ // it must be behind the selection.
+ pEditView->GetImpEditView()->SetEditSelection(
+ pEE->GetEditDoc().GetStartPaM() );
+ }
+ }
+ return bMore;
+}
+
+void EditSpellWrapper::ReplaceAll( const OUString &rNewText )
+{
+ // Is called when the word is in ReplaceList of the spell checker
+ pEditView->InsertText( rNewText );
+ CheckSpellTo();
+}
+
+void EditSpellWrapper::CheckSpellTo()
+{
+ ImpEditEngine* pImpEE = pEditView->GetImpEditEngine();
+ SpellInfo* pSpellInfo = pImpEE->GetSpellInfo();
+ EditPaM aPaM( pEditView->GetImpEditView()->GetEditSelection().Max() );
+ EPaM aEPaM = pImpEE->CreateEPaM( aPaM );
+ if ( aEPaM.nPara == pSpellInfo->aSpellTo.nPara )
+ {
+ // Check if SpellToEnd still has a valid Index, if replace has been
+ // performed in the paragraph.
+ if ( pSpellInfo->aSpellTo.nIndex > aPaM.GetNode()->Len() )
+ pSpellInfo->aSpellTo.nIndex = aPaM.GetNode()->Len();
+ }
+}
+
+WrongList::WrongList() : mnInvalidStart(0), mnInvalidEnd(Valid) {}
+
+void WrongList::SetRanges( std::vector<editeng::MisspellRange>&&rRanges )
+{
+ maRanges = std::move(rRanges);
+}
+
+bool WrongList::IsValid() const
+{
+ return mnInvalidStart == Valid;
+}
+
+void WrongList::SetValid()
+{
+ mnInvalidStart = Valid;
+ mnInvalidEnd = 0;
+}
+
+void WrongList::SetInvalidRange( size_t nStart, size_t nEnd )
+{
+ if (mnInvalidStart == Valid || nStart < mnInvalidStart)
+ mnInvalidStart = nStart;
+
+ if (mnInvalidEnd < nEnd)
+ mnInvalidEnd = nEnd;
+}
+
+void WrongList::ResetInvalidRange( size_t nStart, size_t nEnd )
+{
+ mnInvalidStart = nStart;
+ mnInvalidEnd = nEnd;
+}
+
+void WrongList::TextInserted( size_t nPos, size_t nLength, bool bPosIsSep )
+{
+ if (IsValid())
+ {
+ mnInvalidStart = nPos;
+ mnInvalidEnd = nPos + nLength;
+ }
+ else
+ {
+ if ( mnInvalidStart > nPos )
+ mnInvalidStart = nPos;
+ if ( mnInvalidEnd >= nPos )
+ mnInvalidEnd = mnInvalidEnd + nLength;
+ else
+ mnInvalidEnd = nPos + nLength;
+ }
+
+ for (size_t i = 0, n = maRanges.size(); i < n; ++i)
+ {
+ editeng::MisspellRange& rWrong = maRanges[i];
+ bool bRefIsValid = true;
+ if (rWrong.mnEnd >= nPos)
+ {
+ // Move all Wrongs after the insert position...
+ if (rWrong.mnStart > nPos)
+ {
+ rWrong.mnStart += nLength;
+ rWrong.mnEnd += nLength;
+ }
+ // 1: Starts before and goes until nPos...
+ else if (rWrong.mnEnd == nPos)
+ {
+ // Should be halted at a blank!
+ if ( !bPosIsSep )
+ rWrong.mnEnd += nLength;
+ }
+ // 2: Starts before and goes until after nPos...
+ else if ((rWrong.mnStart < nPos) && (rWrong.mnEnd > nPos))
+ {
+ rWrong.mnEnd += nLength;
+ // When a separator remove and re-examine the Wrong
+ if ( bPosIsSep )
+ {
+ // Split Wrong...
+ editeng::MisspellRange aNewWrong(rWrong.mnStart, nPos);
+ rWrong.mnStart = nPos + 1;
+ maRanges.insert(maRanges.begin() + i, aNewWrong);
+ // Reference no longer valid after Insert, the other
+ // was inserted in front of this position
+ bRefIsValid = false;
+ ++i; // Not this again...
+ }
+ }
+ // 3: Attribute starts at position ..
+ else if (rWrong.mnStart == nPos)
+ {
+ rWrong.mnEnd += nLength;
+ if ( bPosIsSep )
+ ++(rWrong.mnStart);
+ }
+ }
+ SAL_WARN_IF(bRefIsValid && rWrong.mnStart >= rWrong.mnEnd, "editeng",
+ "TextInserted, editeng::MisspellRange: Start >= End?!");
+ }
+
+ SAL_WARN_IF(DbgIsBuggy(), "editeng", "InsertWrong: WrongList broken!");
+}
+
+void WrongList::TextDeleted( size_t nPos, size_t nLength )
+{
+ size_t nEndPos = nPos + nLength;
+ if (IsValid())
+ {
+ const size_t nNewInvalidStart = nPos ? nPos - 1 : 0;
+ mnInvalidStart = nNewInvalidStart;
+ mnInvalidEnd = nNewInvalidStart + 1;
+ }
+ else
+ {
+ if ( mnInvalidStart > nPos )
+ mnInvalidStart = nPos;
+ if ( mnInvalidEnd > nPos )
+ {
+ if (mnInvalidEnd > nEndPos)
+ mnInvalidEnd = mnInvalidEnd - nLength;
+ else
+ mnInvalidEnd = nPos+1;
+ }
+ }
+
+ for (WrongList::iterator i = maRanges.begin(); i != maRanges.end(); )
+ {
+ bool bDelWrong = false;
+ if (i->mnEnd >= nPos)
+ {
+ // Move all Wrongs after the insert position...
+ if (i->mnStart >= nEndPos)
+ {
+ i->mnStart -= nLength;
+ i->mnEnd -= nLength;
+ }
+ // 1. Delete Internal Wrongs ...
+ else if (i->mnStart >= nPos && i->mnEnd <= nEndPos)
+ {
+ bDelWrong = true;
+ }
+ // 2. Wrong begins before, ends inside or behind it ...
+ else if (i->mnStart <= nPos && i->mnEnd > nPos)
+ {
+ if (i->mnEnd <= nEndPos) // ends inside
+ i->mnEnd = nPos;
+ else
+ i->mnEnd -= nLength; // ends after
+ }
+ // 3. Wrong begins inside, ending after ...
+ else if (i->mnStart >= nPos && i->mnEnd > nEndPos)
+ {
+ i->mnStart = nEndPos - nLength;
+ i->mnEnd -= nLength;
+ }
+ }
+ SAL_WARN_IF(i->mnStart >= i->mnEnd, "editeng",
+ "TextDeleted, editeng::MisspellRange: Start >= End?!");
+ if ( bDelWrong )
+ {
+ i = maRanges.erase(i);
+ }
+ else
+ {
+ ++i;
+ }
+ }
+
+ SAL_WARN_IF(DbgIsBuggy(), "editeng", "TextDeleted: WrongList broken!");
+}
+
+bool WrongList::NextWrong( size_t& rnStart, size_t& rnEnd ) const
+{
+ /*
+ rnStart get the start position, is possibly adjusted wrt. Wrong start
+ rnEnd does not have to be initialized.
+ */
+ for (auto const& range : maRanges)
+ {
+ if (range.mnEnd > rnStart)
+ {
+ rnStart = range.mnStart;
+ rnEnd = range.mnEnd;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool WrongList::HasWrong( size_t nStart, size_t nEnd ) const
+{
+ for (auto const& range : maRanges)
+ {
+ if (range.mnStart == nStart && range.mnEnd == nEnd)
+ return true;
+ else if (range.mnStart >= nStart)
+ break;
+ }
+ return false;
+}
+
+bool WrongList::HasAnyWrong( size_t nStart, size_t nEnd ) const
+{
+ for (auto const& range : maRanges)
+ {
+ if (range.mnEnd >= nStart && range.mnStart < nEnd)
+ return true;
+ else if (range.mnStart >= nEnd)
+ break;
+ }
+ return false;
+}
+
+void WrongList::ClearWrongs( size_t nStart, size_t nEnd,
+ const ContentNode* pNode )
+{
+ for (WrongList::iterator i = maRanges.begin(); i != maRanges.end(); )
+ {
+ if (i->mnEnd > nStart && i->mnStart < nEnd)
+ {
+ if (i->mnEnd > nEnd) // Runs out
+ {
+ i->mnStart = nEnd;
+ // Blanks?
+ while (i->mnStart < o3tl::make_unsigned(pNode->Len()) &&
+ (pNode->GetChar(i->mnStart) == ' ' ||
+ pNode->IsFeature(i->mnStart)))
+ {
+ ++i->mnStart;
+ }
+ ++i;
+ }
+ else
+ {
+ i = maRanges.erase(i);
+ // no increment here
+ }
+ }
+ else
+ {
+ ++i;
+ }
+ }
+
+ SAL_WARN_IF(DbgIsBuggy(), "editeng", "ClearWrongs: WrongList broken!");
+}
+
+void WrongList::InsertWrong( size_t nStart, size_t nEnd )
+{
+ WrongList::iterator nPos = std::find_if(maRanges.begin(), maRanges.end(),
+ [&nStart](const editeng::MisspellRange& rRange) { return rRange.mnStart >= nStart; });
+
+ if (nPos != maRanges.end())
+ {
+ {
+ // It can really only happen that the Wrong starts exactly here
+ // and runs along, but not that there are several ranges ...
+ // Exactly in the range is no one allowed to be, otherwise this
+ // Method can not be called!
+ SAL_WARN_IF((nPos->mnStart != nStart || nPos->mnEnd <= nEnd) && nPos->mnStart <= nEnd, "editeng", "InsertWrong: RangeMismatch!");
+ if (nPos->mnStart == nStart && nPos->mnEnd > nEnd)
+ nPos->mnStart = nEnd + 1;
+ }
+ maRanges.insert(nPos, editeng::MisspellRange(nStart, nEnd));
+ }
+ else
+ maRanges.emplace_back(nStart, nEnd);
+
+ SAL_WARN_IF(DbgIsBuggy(), "editeng", "InsertWrong: WrongList broken!");
+}
+
+void WrongList::MarkWrongsInvalid()
+{
+ if (!maRanges.empty())
+ SetInvalidRange(maRanges.front().mnStart, maRanges.back().mnEnd);
+}
+
+WrongList* WrongList::Clone() const
+{
+ return new WrongList(*this);
+}
+
+// #i102062#
+bool WrongList::operator==(const WrongList& rCompare) const
+{
+ // check direct members
+ if(GetInvalidStart() != rCompare.GetInvalidStart()
+ || GetInvalidEnd() != rCompare.GetInvalidEnd())
+ return false;
+
+ return std::equal(maRanges.begin(), maRanges.end(), rCompare.maRanges.begin(), rCompare.maRanges.end(),
+ [](const editeng::MisspellRange& a, const editeng::MisspellRange& b) {
+ return a.mnStart == b.mnStart && a.mnEnd == b.mnEnd; });
+}
+
+bool WrongList::empty() const
+{
+ return maRanges.empty();
+}
+
+void WrongList::push_back(const editeng::MisspellRange& rRange)
+{
+ maRanges.push_back(rRange);
+}
+
+editeng::MisspellRange& WrongList::back()
+{
+ return maRanges.back();
+}
+
+const editeng::MisspellRange& WrongList::back() const
+{
+ return maRanges.back();
+}
+
+WrongList::iterator WrongList::begin()
+{
+ return maRanges.begin();
+}
+
+WrongList::iterator WrongList::end()
+{
+ return maRanges.end();
+}
+
+WrongList::const_iterator WrongList::begin() const
+{
+ return maRanges.begin();
+}
+
+WrongList::const_iterator WrongList::end() const
+{
+ return maRanges.end();
+}
+
+bool WrongList::DbgIsBuggy() const
+{
+ // Check if the ranges overlap.
+ bool bError = false;
+ for (WrongList::const_iterator i = maRanges.begin(); !bError && (i != maRanges.end()); ++i)
+ {
+ bError = std::any_of(i + 1, maRanges.end(), [&i](const editeng::MisspellRange& rRange) {
+ return i->mnStart <= rRange.mnEnd && rRange.mnStart <= i->mnEnd; });
+ }
+ return bError;
+}
+
+
+EdtAutoCorrDoc::EdtAutoCorrDoc(
+ EditEngine* pE, ContentNode* pN, sal_Int32 nCrsr, sal_Unicode cIns) :
+ mpEditEngine(pE),
+ pCurNode(pN),
+ nCursor(nCrsr),
+ bAllowUndoAction(cIns != 0),
+ bUndoAction(false) {}
+
+EdtAutoCorrDoc::~EdtAutoCorrDoc()
+{
+ if ( bUndoAction )
+ mpEditEngine->UndoActionEnd();
+}
+
+bool EdtAutoCorrDoc::Delete(sal_Int32 nStt, sal_Int32 nEnd)
+{
+ EditSelection aSel( EditPaM( pCurNode, nStt ), EditPaM( pCurNode, nEnd ) );
+ mpEditEngine->DeleteSelection(aSel);
+ SAL_WARN_IF(nCursor < nEnd, "editeng",
+ "Cursor in the heart of the action?!");
+ nCursor -= ( nEnd-nStt );
+ bAllowUndoAction = false;
+ return true;
+}
+
+bool EdtAutoCorrDoc::Insert(sal_Int32 nPos, const OUString& rTxt)
+{
+ EditSelection aSel = EditPaM( pCurNode, nPos );
+ mpEditEngine->InsertText(aSel, rTxt);
+ SAL_WARN_IF(nCursor < nPos, "editeng",
+ "Cursor in the heart of the action?!");
+ nCursor = nCursor + rTxt.getLength();
+
+ if ( bAllowUndoAction && ( rTxt.getLength() == 1 ) )
+ ImplStartUndoAction();
+ bAllowUndoAction = false;
+
+ return true;
+}
+
+bool EdtAutoCorrDoc::Replace(sal_Int32 nPos, const OUString& rTxt)
+{
+ return ReplaceRange( nPos, rTxt.getLength(), rTxt );
+}
+
+bool EdtAutoCorrDoc::ReplaceRange(sal_Int32 nPos, sal_Int32 nSourceLength, const OUString& rTxt)
+{
+ // Actually a Replace introduce => corresponds to UNDO
+ sal_Int32 nEnd = nPos+nSourceLength;
+ if ( nEnd > pCurNode->Len() )
+ nEnd = pCurNode->Len();
+
+ // #i5925# First insert new text behind to be deleted text, for keeping attributes.
+ mpEditEngine->InsertText(EditSelection(EditPaM(pCurNode, nEnd)), rTxt);
+ mpEditEngine->DeleteSelection(
+ EditSelection(EditPaM(pCurNode, nPos), EditPaM(pCurNode, nEnd)));
+
+ if ( nPos == nCursor )
+ nCursor = nCursor + rTxt.getLength();
+
+ if ( bAllowUndoAction && ( rTxt.getLength() == 1 ) )
+ ImplStartUndoAction();
+
+ bAllowUndoAction = false;
+
+ return true;
+}
+
+void EdtAutoCorrDoc::SetAttr(sal_Int32 nStt, sal_Int32 nEnd,
+ sal_uInt16 nSlotId, SfxPoolItem& rItem)
+{
+ SfxItemPool* pPool = &mpEditEngine->GetEditDoc().GetItemPool();
+ while ( pPool->GetSecondaryPool() &&
+ pPool->GetName() != "EditEngineItemPool" )
+ {
+ pPool = pPool->GetSecondaryPool();
+
+ }
+ sal_uInt16 nWhich = pPool->GetWhich( nSlotId );
+ if ( nWhich )
+ {
+ rItem.SetWhich( nWhich );
+
+ SfxItemSet aSet = mpEditEngine->GetEmptyItemSet();
+ aSet.Put( rItem );
+
+ EditSelection aSel( EditPaM( pCurNode, nStt ), EditPaM( pCurNode, nEnd ) );
+ aSel.Max().SetIndex( nEnd ); // ???
+ mpEditEngine->SetAttribs( aSel, aSet, SetAttribsMode::Edge );
+ bAllowUndoAction = false;
+ }
+}
+
+bool EdtAutoCorrDoc::SetINetAttr(sal_Int32 nStt, sal_Int32 nEnd,
+ const OUString& rURL)
+{
+ // Turn the Text into a command field ...
+ EditSelection aSel( EditPaM( pCurNode, nStt ), EditPaM( pCurNode, nEnd ) );
+ OUString aText = mpEditEngine->GetSelected(aSel);
+ aSel = mpEditEngine->DeleteSelection(aSel);
+ SAL_WARN_IF(nCursor < nEnd, "editeng",
+ "Cursor in the heart of the action?!");
+ nCursor -= ( nEnd-nStt );
+ SvxFieldItem aField( SvxURLField( rURL, aText, SvxURLFormat::Repr ),
+ EE_FEATURE_FIELD );
+ mpEditEngine->InsertField(aSel, aField);
+ nCursor++;
+ mpEditEngine->UpdateFieldsOnly();
+ bAllowUndoAction = false;
+ return true;
+}
+
+OUString const* EdtAutoCorrDoc::GetPrevPara(bool const)
+{
+ // Return previous paragraph, so that it can be determined,
+ // whether the current word is at the beginning of a sentence.
+
+ bAllowUndoAction = false; // Not anymore ...
+
+ EditDoc& rNodes = mpEditEngine->GetEditDoc();
+ sal_Int32 nPos = rNodes.GetPos( pCurNode );
+
+ // Special case: Bullet => Paragraph start => simply return NULL...
+ const SfxBoolItem& rBulletState = mpEditEngine->GetParaAttrib( nPos, EE_PARA_BULLETSTATE );
+ bool bBullet = rBulletState.GetValue();
+ if ( !bBullet && (mpEditEngine->GetControlWord() & EEControlBits::OUTLINER) )
+ {
+ // The Outliner has still a Bullet at Level 0.
+ const SfxInt16Item& rLevel = mpEditEngine->GetParaAttrib( nPos, EE_PARA_OUTLLEVEL );
+ if ( rLevel.GetValue() == 0 )
+ bBullet = true;
+ }
+ if ( bBullet )
+ return nullptr;
+
+ for ( sal_Int32 n = nPos; n; )
+ {
+ n--;
+ ContentNode* pNode = rNodes[n];
+ if ( pNode->Len() )
+ return & pNode->GetString();
+ }
+ return nullptr;
+
+}
+
+bool EdtAutoCorrDoc::ChgAutoCorrWord( sal_Int32& rSttPos,
+ sal_Int32 nEndPos, SvxAutoCorrect& rACorrect,
+ OUString* pPara )
+{
+ // Paragraph-start or a blank found, search for the word
+ // shortcut in Auto
+ bAllowUndoAction = false; // Not anymore ...
+
+ OUString aShort( pCurNode->Copy( rSttPos, nEndPos - rSttPos ) );
+ bool bRet = false;
+
+ if( aShort.isEmpty() )
+ return bRet;
+
+ LanguageTag aLanguageTag( mpEditEngine->GetLanguage( EditPaM( pCurNode, rSttPos+1 ) ).nLang );
+ const SvxAutocorrWord* pFnd = rACorrect.SearchWordsInList(
+ pCurNode->GetString(), rSttPos, nEndPos, *this, aLanguageTag);
+ if( pFnd && pFnd->IsTextOnly() )
+ {
+
+ // replace also last colon of keywords surrounded by colons (for example, ":name:")
+ bool replaceLastChar = pFnd->GetShort()[0] == ':' && pFnd->GetShort().endsWith(":");
+
+ // then replace
+ EditSelection aSel( EditPaM( pCurNode, rSttPos ),
+ EditPaM( pCurNode, nEndPos + (replaceLastChar ? 1 : 0) ));
+ aSel = mpEditEngine->DeleteSelection(aSel);
+ SAL_WARN_IF(nCursor < nEndPos, "editeng",
+ "Cursor in the heart of the action?!");
+ nCursor -= ( nEndPos-rSttPos );
+ mpEditEngine->InsertText(aSel, pFnd->GetLong());
+ nCursor = nCursor + pFnd->GetLong().getLength();
+ if( pPara )
+ *pPara = pCurNode->GetString();
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+bool EdtAutoCorrDoc::TransliterateRTLWord( sal_Int32& /*rSttPos*/,
+ sal_Int32 /*nEndPos*/, bool /*bApply*/ )
+{
+ // Paragraph-start or a blank found, search for the word
+ // shortcut in Auto
+ bool bRet = false;
+
+ return bRet;
+}
+
+
+LanguageType EdtAutoCorrDoc::GetLanguage( sal_Int32 nPos ) const
+{
+ return mpEditEngine->GetLanguage( EditPaM( pCurNode, nPos+1 ) ).nLang;
+}
+
+void EdtAutoCorrDoc::ImplStartUndoAction()
+{
+ sal_Int32 nPara = mpEditEngine->GetEditDoc().GetPos( pCurNode );
+ ESelection aSel( nPara, nCursor, nPara, nCursor );
+ mpEditEngine->UndoActionStart( EDITUNDO_INSERT, aSel );
+ bUndoAction = true;
+ bAllowUndoAction = false;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/eehtml.cxx b/editeng/source/editeng/eehtml.cxx
new file mode 100644
index 0000000000..b3ed283955
--- /dev/null
+++ b/editeng/source/editeng/eehtml.cxx
@@ -0,0 +1,813 @@
+/* -*- 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 "eehtml.hxx"
+#include <editeng/adjustitem.hxx>
+#include <editeng/flditem.hxx>
+#include <tools/urlobj.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/ulspitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <svtools/htmltokn.h>
+#include <svtools/htmlkywd.hxx>
+#include <tools/tenccvt.hxx>
+
+#include <editeng/editeng.hxx>
+#include <utility>
+
+#define STYLE_PRE 101
+
+EditHTMLParser::EditHTMLParser( SvStream& rIn, OUString _aBaseURL, SvKeyValueIterator* pHTTPHeaderAttrs )
+ : HTMLParser( rIn, true ),
+ aBaseURL(std::move( _aBaseURL )),
+ mpEditEngine(nullptr),
+ bInPara(false),
+ bWasInPara(false),
+ bFieldsInserted(false),
+ bInTitle(false),
+ nInTable(0),
+ nInCell(0),
+ nDefListLevel(0)
+{
+ DBG_ASSERT( !IsSwitchToUCS2(), "EditHTMLParser::::EditHTMLParser: Switch to UCS2?" );
+
+ // Although the real default encoding is ISO8859-1, we use MS-1252
+ // as default encoding.
+ SetSrcEncoding( GetExtendedCompatibilityTextEncoding( RTL_TEXTENCODING_ISO_8859_1 ) );
+
+ // If the file starts with a BOM, switch to UCS2.
+ SetSwitchToUCS2( true );
+
+ if ( pHTTPHeaderAttrs )
+ SetEncodingByHTTPHeader( pHTTPHeaderAttrs );
+}
+
+EditHTMLParser::~EditHTMLParser()
+{
+}
+
+SvParserState EditHTMLParser::CallParser(EditEngine* pEE, const EditPaM& rPaM)
+{
+ DBG_ASSERT(pEE, "CallParser: ImpEditEngine ?!");
+ mpEditEngine = pEE;
+ SvParserState _eState = SvParserState::NotStarted;
+ if ( mpEditEngine )
+ {
+ // Build in wrap mimic in RTF import?
+ aCurSel = EditSelection( rPaM, rPaM );
+
+ if (mpEditEngine->IsHtmlImportHandlerSet())
+ {
+ HtmlImportInfo aImportInfo(HtmlImportState::Start, this, mpEditEngine->CreateESelection(aCurSel));
+ mpEditEngine->CallHtmlImportHandler(aImportInfo);
+ }
+
+ ImpSetStyleSheet( 0 );
+ _eState = HTMLParser::CallParser();
+
+ if (mpEditEngine->IsHtmlImportHandlerSet())
+ {
+ HtmlImportInfo aImportInfo(HtmlImportState::End, this, mpEditEngine->CreateESelection(aCurSel));
+ mpEditEngine->CallHtmlImportHandler(aImportInfo);
+ }
+
+ if ( bFieldsInserted )
+ mpEditEngine->UpdateFieldsOnly();
+ }
+ return _eState;
+}
+
+void EditHTMLParser::NextToken( HtmlTokenId nToken )
+{
+ switch( nToken )
+ {
+ case HtmlTokenId::META:
+ {
+ const HTMLOptions& aOptions = GetOptions();
+ size_t nArrLen = aOptions.size();
+ bool bEquiv = false;
+ for ( size_t i = 0; i < nArrLen; i++ )
+ {
+ const HTMLOption& aOption = aOptions[i];
+ switch( aOption.GetToken() )
+ {
+ case HtmlOptionId::HTTPEQUIV:
+ {
+ bEquiv = true;
+ }
+ break;
+ case HtmlOptionId::CONTENT:
+ {
+ if ( bEquiv )
+ {
+ rtl_TextEncoding eEnc = GetEncodingByMIME( aOption.GetString() );
+ if ( eEnc != RTL_TEXTENCODING_DONTKNOW )
+ SetSrcEncoding( eEnc );
+ }
+ }
+ break;
+ default: break;
+ }
+ }
+
+ }
+ break;
+ case HtmlTokenId::PLAINTEXT_ON:
+ case HtmlTokenId::PLAINTEXT2_ON:
+ bInPara = true;
+ break;
+ case HtmlTokenId::PLAINTEXT_OFF:
+ case HtmlTokenId::PLAINTEXT2_OFF:
+ bInPara = false;
+ break;
+
+ case HtmlTokenId::LINEBREAK:
+ case HtmlTokenId::NEWPARA:
+ {
+ if ( ( bInPara || nInTable ) &&
+ ( ( nToken == HtmlTokenId::LINEBREAK ) || HasTextInCurrentPara() ) )
+ {
+ ImpInsertParaBreak();
+ }
+ }
+ break;
+ case HtmlTokenId::HORZRULE:
+ {
+ if ( HasTextInCurrentPara() )
+ ImpInsertParaBreak();
+ ImpInsertParaBreak();
+ }
+ break;
+ case HtmlTokenId::NONBREAKSPACE:
+ {
+ if ( bInPara )
+ {
+ ImpInsertText( " " );
+ }
+ }
+ break;
+ case HtmlTokenId::RAWDATA:
+ if (IsReadStyle() && !aToken.isEmpty())
+ {
+ // Each token represents a single line.
+ maStyleSource.append(aToken);
+ maStyleSource.append('\n');
+ }
+ break;
+ case HtmlTokenId::TEXTTOKEN:
+ {
+ // #i110937# for <title> content, call aImportHdl (no SkipGroup), but don't insert the text into the EditEngine
+ if (!bInTitle)
+ {
+ if ( !bInPara )
+ StartPara( false );
+
+ OUString aText = aToken.toString();
+ if ( aText.startsWith(" ") && ThrowAwayBlank() && !IsReadPRE() )
+ aText = aText.copy( 1 );
+
+ if ( moCurAnchor )
+ {
+ moCurAnchor->aText += aText;
+ }
+ else
+ {
+ // Only written until HTML with 319?
+ if ( IsReadPRE() )
+ aText = aText.replaceAll(u"\t", u" ");
+ ImpInsertText( aText );
+ }
+ }
+ }
+ break;
+
+ case HtmlTokenId::CENTER_ON:
+ case HtmlTokenId::CENTER_OFF:
+ {
+ sal_Int32 nNode = mpEditEngine->GetEditDoc().GetPos( aCurSel.Max().GetNode() );
+ SfxItemSet aItems( aCurSel.Max().GetNode()->GetContentAttribs().GetItems() );
+ aItems.ClearItem( EE_PARA_JUST );
+ if ( nToken == HtmlTokenId::CENTER_ON )
+ aItems.Put( SvxAdjustItem( SvxAdjust::Center, EE_PARA_JUST ) );
+ mpEditEngine->SetParaAttribsOnly(nNode, aItems);
+ }
+ break;
+
+ case HtmlTokenId::ANCHOR_ON: AnchorStart();
+ break;
+ case HtmlTokenId::ANCHOR_OFF: AnchorEnd();
+ break;
+
+ case HtmlTokenId::PARABREAK_ON:
+ if( bInPara && HasTextInCurrentPara() )
+ EndPara();
+ StartPara( true );
+ break;
+
+ case HtmlTokenId::PARABREAK_OFF:
+ if( bInPara )
+ EndPara();
+ break;
+
+ case HtmlTokenId::HEAD1_ON:
+ case HtmlTokenId::HEAD2_ON:
+ case HtmlTokenId::HEAD3_ON:
+ case HtmlTokenId::HEAD4_ON:
+ case HtmlTokenId::HEAD5_ON:
+ case HtmlTokenId::HEAD6_ON:
+ {
+ HeadingStart( nToken );
+ }
+ break;
+
+ case HtmlTokenId::HEAD1_OFF:
+ case HtmlTokenId::HEAD2_OFF:
+ case HtmlTokenId::HEAD3_OFF:
+ case HtmlTokenId::HEAD4_OFF:
+ case HtmlTokenId::HEAD5_OFF:
+ case HtmlTokenId::HEAD6_OFF:
+ {
+ HeadingEnd();
+ }
+ break;
+
+ case HtmlTokenId::PREFORMTXT_ON:
+ case HtmlTokenId::XMP_ON:
+ case HtmlTokenId::LISTING_ON:
+ {
+ StartPara( true );
+ ImpSetStyleSheet( STYLE_PRE );
+ }
+ break;
+
+ case HtmlTokenId::DEFLIST_ON:
+ {
+ nDefListLevel++;
+ }
+ break;
+
+ case HtmlTokenId::DEFLIST_OFF:
+ {
+ if( nDefListLevel )
+ nDefListLevel--;
+ }
+ break;
+
+ case HtmlTokenId::TABLE_ON: nInTable++;
+ break;
+ case HtmlTokenId::TABLE_OFF: DBG_ASSERT( nInTable, "Not in Table, but TABLE_OFF?" );
+ nInTable--;
+ break;
+
+ case HtmlTokenId::TABLEHEADER_ON:
+ case HtmlTokenId::TABLEDATA_ON:
+ nInCell++;
+ [[fallthrough]];
+ case HtmlTokenId::BLOCKQUOTE_ON:
+ case HtmlTokenId::BLOCKQUOTE_OFF:
+ case HtmlTokenId::BLOCKQUOTE30_ON:
+ case HtmlTokenId::BLOCKQUOTE30_OFF:
+ case HtmlTokenId::LISTHEADER_ON:
+ case HtmlTokenId::LI_ON:
+ case HtmlTokenId::DD_ON:
+ case HtmlTokenId::DT_ON:
+ case HtmlTokenId::ORDERLIST_ON:
+ case HtmlTokenId::UNORDERLIST_ON:
+ {
+ bool bHasText = HasTextInCurrentPara();
+ if ( bHasText )
+ ImpInsertParaBreak();
+ StartPara( false );
+ }
+ break;
+
+ case HtmlTokenId::TABLEHEADER_OFF:
+ case HtmlTokenId::TABLEDATA_OFF:
+ {
+ if ( nInCell )
+ nInCell--;
+ [[fallthrough]];
+ }
+ case HtmlTokenId::LISTHEADER_OFF:
+ case HtmlTokenId::LI_OFF:
+ case HtmlTokenId::DD_OFF:
+ case HtmlTokenId::DT_OFF:
+ case HtmlTokenId::ORDERLIST_OFF:
+ case HtmlTokenId::UNORDERLIST_OFF: EndPara();
+ break;
+
+ case HtmlTokenId::TABLEROW_ON:
+ case HtmlTokenId::TABLEROW_OFF: // A RETURN only after a CELL, for Calc
+
+ case HtmlTokenId::COL_ON:
+ case HtmlTokenId::COLGROUP_ON:
+ case HtmlTokenId::COLGROUP_OFF: break;
+
+ case HtmlTokenId::FONT_ON:
+ break;
+ case HtmlTokenId::FONT_OFF:
+ break;
+
+ case HtmlTokenId::TITLE_ON:
+ bInTitle = true;
+ break;
+ case HtmlTokenId::TITLE_OFF:
+ bInTitle = false;
+ break;
+
+ // globals
+ case HtmlTokenId::HTML_ON:
+ case HtmlTokenId::HTML_OFF:
+ case HtmlTokenId::STYLE_ON:
+ case HtmlTokenId::STYLE_OFF:
+ case HtmlTokenId::BODY_ON:
+ case HtmlTokenId::BODY_OFF:
+ case HtmlTokenId::HEAD_ON:
+ case HtmlTokenId::HEAD_OFF:
+ case HtmlTokenId::FORM_ON:
+ case HtmlTokenId::FORM_OFF:
+ case HtmlTokenId::THEAD_ON:
+ case HtmlTokenId::THEAD_OFF:
+ case HtmlTokenId::TBODY_ON:
+ case HtmlTokenId::TBODY_OFF:
+ // inline elements, structural markup
+ // HTML 3.0
+ case HtmlTokenId::BANNER_ON:
+ case HtmlTokenId::BANNER_OFF:
+ case HtmlTokenId::DIVISION_ON:
+ case HtmlTokenId::DIVISION_OFF:
+// case HtmlTokenId::LISTHEADER_ON: //! special handling
+// case HtmlTokenId::LISTHEADER_OFF:
+ case HtmlTokenId::NOTE_ON:
+ case HtmlTokenId::NOTE_OFF:
+ // inline elements, logical markup
+ // HTML 2.0
+ case HtmlTokenId::ADDRESS_ON:
+ case HtmlTokenId::ADDRESS_OFF:
+// case HtmlTokenId::BLOCKQUOTE_ON: //! special handling
+// case HtmlTokenId::BLOCKQUOTE_OFF:
+ case HtmlTokenId::CITATION_ON:
+ case HtmlTokenId::CITATION_OFF:
+ case HtmlTokenId::CODE_ON:
+ case HtmlTokenId::CODE_OFF:
+ case HtmlTokenId::DEFINSTANCE_ON:
+ case HtmlTokenId::DEFINSTANCE_OFF:
+ case HtmlTokenId::EMPHASIS_ON:
+ case HtmlTokenId::EMPHASIS_OFF:
+ case HtmlTokenId::KEYBOARD_ON:
+ case HtmlTokenId::KEYBOARD_OFF:
+ case HtmlTokenId::SAMPLE_ON:
+ case HtmlTokenId::SAMPLE_OFF:
+ case HtmlTokenId::STRIKE_ON:
+ case HtmlTokenId::STRIKE_OFF:
+ case HtmlTokenId::STRONG_ON:
+ case HtmlTokenId::STRONG_OFF:
+ case HtmlTokenId::VARIABLE_ON:
+ case HtmlTokenId::VARIABLE_OFF:
+ // HTML 3.0
+ case HtmlTokenId::ABBREVIATION_ON:
+ case HtmlTokenId::ABBREVIATION_OFF:
+ case HtmlTokenId::ACRONYM_ON:
+ case HtmlTokenId::ACRONYM_OFF:
+ case HtmlTokenId::AUTHOR_ON:
+ case HtmlTokenId::AUTHOR_OFF:
+// case HtmlTokenId::BLOCKQUOTE30_ON: //! special handling
+// case HtmlTokenId::BLOCKQUOTE30_OFF:
+ case HtmlTokenId::DELETEDTEXT_ON:
+ case HtmlTokenId::DELETEDTEXT_OFF:
+ case HtmlTokenId::INSERTEDTEXT_ON:
+ case HtmlTokenId::INSERTEDTEXT_OFF:
+ case HtmlTokenId::LANGUAGE_ON:
+ case HtmlTokenId::LANGUAGE_OFF:
+ case HtmlTokenId::PERSON_ON:
+ case HtmlTokenId::PERSON_OFF:
+ case HtmlTokenId::SHORTQUOTE_ON:
+ case HtmlTokenId::SHORTQUOTE_OFF:
+ case HtmlTokenId::SUBSCRIPT_ON:
+ case HtmlTokenId::SUBSCRIPT_OFF:
+ case HtmlTokenId::SUPERSCRIPT_ON:
+ case HtmlTokenId::SUPERSCRIPT_OFF:
+ // inline elements, visual markup
+ // HTML 2.0
+ case HtmlTokenId::BOLD_ON:
+ case HtmlTokenId::BOLD_OFF:
+ case HtmlTokenId::ITALIC_ON:
+ case HtmlTokenId::ITALIC_OFF:
+ case HtmlTokenId::TELETYPE_ON:
+ case HtmlTokenId::TELETYPE_OFF:
+ case HtmlTokenId::UNDERLINE_ON:
+ case HtmlTokenId::UNDERLINE_OFF:
+ // HTML 3.0
+ case HtmlTokenId::BIGPRINT_ON:
+ case HtmlTokenId::BIGPRINT_OFF:
+ case HtmlTokenId::STRIKETHROUGH_ON:
+ case HtmlTokenId::STRIKETHROUGH_OFF:
+ case HtmlTokenId::SMALLPRINT_ON:
+ case HtmlTokenId::SMALLPRINT_OFF:
+ // figures
+ case HtmlTokenId::FIGURE_ON:
+ case HtmlTokenId::FIGURE_OFF:
+ case HtmlTokenId::CAPTION_ON:
+ case HtmlTokenId::CAPTION_OFF:
+ case HtmlTokenId::CREDIT_ON:
+ case HtmlTokenId::CREDIT_OFF:
+ // misc
+ case HtmlTokenId::DIRLIST_ON:
+ case HtmlTokenId::DIRLIST_OFF:
+ case HtmlTokenId::FOOTNOTE_ON: //! they land so in the text
+ case HtmlTokenId::FOOTNOTE_OFF:
+ case HtmlTokenId::MENULIST_ON:
+ case HtmlTokenId::MENULIST_OFF:
+// case HtmlTokenId::PLAINTEXT_ON: //! special handling
+// case HtmlTokenId::PLAINTEXT_OFF:
+// case HtmlTokenId::PREFORMTXT_ON: //! special handling
+// case HtmlTokenId::PREFORMTXT_OFF:
+ case HtmlTokenId::SPAN_ON:
+ case HtmlTokenId::SPAN_OFF:
+ // obsolete
+// case HtmlTokenId::XMP_ON: //! special handling
+// case HtmlTokenId::XMP_OFF:
+// case HtmlTokenId::LISTING_ON: //! special handling
+// case HtmlTokenId::LISTING_OFF:
+ // Netscape
+ case HtmlTokenId::BLINK_ON:
+ case HtmlTokenId::BLINK_OFF:
+ case HtmlTokenId::NOBR_ON:
+ case HtmlTokenId::NOBR_OFF:
+ case HtmlTokenId::NOEMBED_ON:
+ case HtmlTokenId::NOEMBED_OFF:
+ case HtmlTokenId::NOFRAMES_ON:
+ case HtmlTokenId::NOFRAMES_OFF:
+ // Internet Explorer
+ case HtmlTokenId::MARQUEE_ON:
+ case HtmlTokenId::MARQUEE_OFF:
+// case HtmlTokenId::PLAINTEXT2_ON: //! special handling
+// case HtmlTokenId::PLAINTEXT2_OFF:
+ break;
+
+ default:
+ {
+ if ( nToken >= HtmlTokenId::ONOFF_START )
+ {
+ if ( ( nToken == HtmlTokenId::UNKNOWNCONTROL_ON ) || ( nToken == HtmlTokenId::UNKNOWNCONTROL_OFF ) )
+ {
+ ;
+ }
+ else if ( !isOffToken(nToken) )
+ {
+ DBG_ASSERT( !isOffToken( nToken ), "No Start-Token ?!" );
+ SkipGroup( static_cast<HtmlTokenId>(static_cast<int>(nToken) + 1) );
+ }
+ }
+ }
+ } // SWITCH
+
+ if (mpEditEngine->IsHtmlImportHandlerSet())
+ {
+ HtmlImportInfo aImportInfo(HtmlImportState::NextToken, this, mpEditEngine->CreateESelection(aCurSel));
+ aImportInfo.nToken = nToken;
+ if ( nToken == HtmlTokenId::TEXTTOKEN )
+ aImportInfo.aText = aToken;
+ else if (nToken == HtmlTokenId::STYLE_OFF)
+ aImportInfo.aText = maStyleSource.makeStringAndClear();
+ mpEditEngine->CallHtmlImportHandler(aImportInfo);
+ }
+
+}
+
+void EditHTMLParser::ImpInsertParaBreak()
+{
+ if (mpEditEngine->IsHtmlImportHandlerSet())
+ {
+ HtmlImportInfo aImportInfo(HtmlImportState::InsertPara, this, mpEditEngine->CreateESelection(aCurSel));
+ mpEditEngine->CallHtmlImportHandler(aImportInfo);
+ }
+ aCurSel = mpEditEngine->InsertParaBreak(aCurSel);
+}
+
+void EditHTMLParser::ImpSetAttribs( const SfxItemSet& rItems )
+{
+ // pSel, when character attributes, otherwise paragraph attributes for
+ // the current paragraph.
+ DBG_ASSERT( aCurSel.Min().GetNode() == aCurSel.Max().GetNode(), "ImpInsertAttribs: Selection?" );
+
+ EditPaM aStartPaM( aCurSel.Min() );
+ EditPaM aEndPaM( aCurSel.Max() );
+
+ aStartPaM.SetIndex( 0 );
+ aEndPaM.SetIndex( aEndPaM.GetNode()->Len() );
+
+ if (mpEditEngine->IsHtmlImportHandlerSet())
+ {
+ EditSelection aSel( aStartPaM, aEndPaM );
+ HtmlImportInfo aImportInfo(HtmlImportState::SetAttr, this, mpEditEngine->CreateESelection(aSel));
+ mpEditEngine->CallHtmlImportHandler(aImportInfo);
+ }
+
+ ContentNode* pSN = aStartPaM.GetNode();
+ sal_Int32 nStartNode = mpEditEngine->GetEditDoc().GetPos( pSN );
+
+ // If an attribute goes from 0 to current Paragraph length,
+ // then it should be a paragraph attribute!
+
+ // Note: Selection can reach over several Paragraphs.
+ // All complete paragraphs are paragraph attributes ...
+
+ // not really HTML:
+#ifdef DBG_UTIL
+ ContentNode* pEN = aEndPaM.GetNode();
+ sal_Int32 nEndNode = mpEditEngine->GetEditDoc().GetPos( pEN );
+ DBG_ASSERT( nStartNode == nEndNode, "ImpSetAttribs: Several paragraphs?" );
+#endif
+
+ if ( ( aStartPaM.GetIndex() == 0 ) && ( aEndPaM.GetIndex() == aEndPaM.GetNode()->Len() ) )
+ {
+ // Has to be merged:
+ SfxItemSet aItems = mpEditEngine->GetBaseParaAttribs(nStartNode);
+ aItems.Put( rItems );
+ mpEditEngine->SetParaAttribsOnly(nStartNode, aItems);
+ }
+ else
+ mpEditEngine->SetAttribs( EditSelection( aStartPaM, aEndPaM ), rItems );
+}
+
+void EditHTMLParser::ImpSetStyleSheet( sal_uInt16 nHLevel )
+{
+ /*
+ nHLevel: 0: Turn off
+ 1-6: Heading
+ STYLE_PRE: Preformatted
+ */
+ // Create hard attributes ...
+ // Enough for Calc, would have to be clarified with StyleSheets
+ // that they should also be in the app so that when they are feed
+ // in a different engine still are here ...
+ sal_Int32 nNode = mpEditEngine->GetEditDoc().GetPos( aCurSel.Max().GetNode() );
+
+ SfxItemSet aItems( aCurSel.Max().GetNode()->GetContentAttribs().GetItems() );
+
+ aItems.ClearItem( EE_PARA_ULSPACE );
+
+ aItems.ClearItem( EE_CHAR_FONTHEIGHT );
+ aItems.ClearItem( EE_CHAR_FONTINFO );
+ aItems.ClearItem( EE_CHAR_WEIGHT );
+
+ aItems.ClearItem( EE_CHAR_FONTHEIGHT_CJK );
+ aItems.ClearItem( EE_CHAR_FONTINFO_CJK );
+ aItems.ClearItem( EE_CHAR_WEIGHT_CJK );
+
+ aItems.ClearItem( EE_CHAR_FONTHEIGHT_CTL );
+ aItems.ClearItem( EE_CHAR_FONTINFO_CTL );
+ aItems.ClearItem( EE_CHAR_WEIGHT_CTL );
+
+ // Bold in the first 3 Headings
+ if ( ( nHLevel >= 1 ) && ( nHLevel <= 3 ) )
+ {
+ SvxWeightItem aWeightItem( WEIGHT_BOLD, EE_CHAR_WEIGHT );
+ aItems.Put( aWeightItem );
+
+ SvxWeightItem aWeightItemCJK( WEIGHT_BOLD, EE_CHAR_WEIGHT_CJK );
+ aItems.Put( aWeightItemCJK );
+
+ SvxWeightItem aWeightItemCTL( WEIGHT_BOLD, EE_CHAR_WEIGHT_CTL );
+ aItems.Put( aWeightItemCTL );
+ }
+
+ // Font height and margins, when LogicToLogic is possible:
+ MapUnit eUnit = mpEditEngine->GetRefMapMode().GetMapUnit();
+ if ( ( eUnit != MapUnit::MapPixel ) && ( eUnit != MapUnit::MapSysFont ) &&
+ ( eUnit != MapUnit::MapAppFont ) && ( eUnit != MapUnit::MapRelative ) )
+ {
+ tools::Long nPoints = 10;
+ if ( nHLevel == 1 )
+ nPoints = 22;
+ else if ( nHLevel == 2 )
+ nPoints = 16;
+ else if ( nHLevel == 3 )
+ nPoints = 12;
+ else if ( nHLevel == 4 )
+ nPoints = 11;
+
+ nPoints = OutputDevice::LogicToLogic( nPoints, MapUnit::MapPoint, eUnit );
+
+ SvxFontHeightItem aHeightItem( nPoints, 100, EE_CHAR_FONTHEIGHT );
+ aItems.Put( aHeightItem );
+
+ SvxFontHeightItem aHeightItemCJK( nPoints, 100, EE_CHAR_FONTHEIGHT_CJK );
+ aItems.Put( aHeightItemCJK );
+
+ SvxFontHeightItem aHeightItemCTL( nPoints, 100, EE_CHAR_FONTHEIGHT_CTL );
+ aItems.Put( aHeightItemCTL );
+
+ // Paragraph margins, when Heading:
+ if (nHLevel <= 6)
+ {
+ SvxULSpaceItem aULSpaceItem( EE_PARA_ULSPACE );
+ aULSpaceItem.SetUpper( static_cast<sal_uInt16>(OutputDevice::LogicToLogic( 42, MapUnit::Map10thMM, eUnit )) );
+ aULSpaceItem.SetLower( static_cast<sal_uInt16>(OutputDevice::LogicToLogic( 35, MapUnit::Map10thMM, eUnit )) );
+ aItems.Put( aULSpaceItem );
+ }
+ }
+
+ // Choose a proportional Font for Pre
+ if ( nHLevel == STYLE_PRE )
+ {
+ vcl::Font aFont = OutputDevice::GetDefaultFont( DefaultFontType::FIXED, LANGUAGE_SYSTEM, GetDefaultFontFlags::NONE );
+ SvxFontItem aFontItem( aFont.GetFamilyType(), aFont.GetFamilyName(), OUString(), aFont.GetPitch(), aFont.GetCharSet(), EE_CHAR_FONTINFO );
+ aItems.Put( aFontItem );
+
+ SvxFontItem aFontItemCJK( aFont.GetFamilyType(), aFont.GetFamilyName(), OUString(), aFont.GetPitch(), aFont.GetCharSet(), EE_CHAR_FONTINFO_CJK );
+ aItems.Put( aFontItemCJK );
+
+ SvxFontItem aFontItemCTL( aFont.GetFamilyType(), aFont.GetFamilyName(), OUString(), aFont.GetPitch(), aFont.GetCharSet(), EE_CHAR_FONTINFO_CTL );
+ aItems.Put( aFontItemCTL );
+ }
+
+ mpEditEngine->SetParaAttribsOnly(nNode, aItems);
+}
+
+void EditHTMLParser::ImpInsertText( const OUString& rText )
+{
+ if (mpEditEngine->IsHtmlImportHandlerSet())
+ {
+ HtmlImportInfo aImportInfo(HtmlImportState::InsertText, this, mpEditEngine->CreateESelection(aCurSel));
+ aImportInfo.aText = rText;
+ mpEditEngine->CallHtmlImportHandler(aImportInfo);
+ }
+
+ aCurSel = mpEditEngine->InsertText(aCurSel, rText);
+}
+
+void EditHTMLParser::SkipGroup( HtmlTokenId nEndToken )
+{
+ // groups in cells are closed upon leaving the cell, because those
+ // ******* web authors don't know their job
+ // for example: <td><form></td> lacks a closing </form>
+ sal_uInt8 nCellLevel = nInCell;
+ HtmlTokenId nToken;
+ while( nCellLevel <= nInCell )
+ {
+ nToken = GetNextToken();
+ if (nToken == nEndToken || nToken == HtmlTokenId::NONE)
+ break;
+ switch ( nToken )
+ {
+ case HtmlTokenId::TABLEHEADER_ON:
+ case HtmlTokenId::TABLEDATA_ON:
+ nInCell++;
+ break;
+ case HtmlTokenId::TABLEHEADER_OFF:
+ case HtmlTokenId::TABLEDATA_OFF:
+ if ( nInCell )
+ nInCell--;
+ break;
+ default: break;
+ }
+ }
+}
+
+void EditHTMLParser::StartPara( bool bReal )
+{
+ if ( bReal )
+ {
+ const HTMLOptions& aOptions = GetOptions();
+ SvxAdjust eAdjust = SvxAdjust::Left;
+ for (const auto & aOption : aOptions)
+ {
+ if( aOption.GetToken() == HtmlOptionId::ALIGN )
+ {
+ OUString const& rTmp(aOption.GetString());
+ if (rTmp.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_AL_right))
+ eAdjust = SvxAdjust::Right;
+ else if (rTmp.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_AL_middle))
+ eAdjust = SvxAdjust::Center;
+ else if (rTmp.equalsIgnoreAsciiCase(OOO_STRING_SVTOOLS_HTML_AL_center))
+ eAdjust = SvxAdjust::Center;
+ else
+ eAdjust = SvxAdjust::Left;
+ }
+ }
+ SfxItemSet aItemSet = mpEditEngine->GetEmptyItemSet();
+ aItemSet.Put( SvxAdjustItem( eAdjust, EE_PARA_JUST ) );
+ ImpSetAttribs( aItemSet );
+ }
+ bInPara = true;
+}
+
+void EditHTMLParser::EndPara()
+{
+ if ( bInPara )
+ {
+ bool bHasText = HasTextInCurrentPara();
+ if ( bHasText )
+ ImpInsertParaBreak();
+ }
+ bInPara = false;
+}
+
+bool EditHTMLParser::ThrowAwayBlank()
+{
+ // A blank must be thrown away if the new text begins with a Blank and
+ // if the current paragraph is empty or ends with a Blank...
+ ContentNode* pNode = aCurSel.Max().GetNode();
+ return !(pNode->Len() && ( pNode->GetChar( pNode->Len()-1 ) != ' ' ));
+}
+
+bool EditHTMLParser::HasTextInCurrentPara()
+{
+ return aCurSel.Max().GetNode()->Len() != 0;
+}
+
+void EditHTMLParser::AnchorStart()
+{
+ // ignore anchor in anchor
+ if ( moCurAnchor )
+ return;
+
+ const HTMLOptions& aOptions = GetOptions();
+ OUString aRef;
+
+ for (const auto & aOption : aOptions)
+ {
+ if( aOption.GetToken() == HtmlOptionId::HREF)
+ aRef = aOption.GetString();
+ }
+
+ if ( aRef.isEmpty() )
+ return;
+
+ OUString aURL = aRef;
+ if ( !aURL.isEmpty() && ( aURL[ 0 ] != '#' ) )
+ {
+ INetURLObject aTargetURL;
+ INetURLObject aRootURL( aBaseURL );
+ aRootURL.GetNewAbsURL( aRef, &aTargetURL );
+ aURL = aTargetURL.GetMainURL( INetURLObject::DecodeMechanism::ToIUri );
+ }
+ moCurAnchor.emplace();
+ moCurAnchor->aHRef = aURL;
+}
+
+void EditHTMLParser::AnchorEnd()
+{
+ if ( !moCurAnchor )
+ return;
+
+ // Insert as URL-Field...
+ SvxFieldItem aFld( SvxURLField( moCurAnchor->aHRef, moCurAnchor->aText, SvxURLFormat::Repr ), EE_FEATURE_FIELD );
+ aCurSel = mpEditEngine->InsertField(aCurSel, aFld);
+ bFieldsInserted = true;
+ moCurAnchor.reset();
+
+ if (mpEditEngine->IsHtmlImportHandlerSet())
+ {
+ HtmlImportInfo aImportInfo(HtmlImportState::InsertField, this, mpEditEngine->CreateESelection(aCurSel));
+ mpEditEngine->CallHtmlImportHandler(aImportInfo);
+ }
+}
+
+void EditHTMLParser::HeadingStart( HtmlTokenId nToken )
+{
+ bWasInPara = bInPara;
+ StartPara( false );
+
+ if ( bWasInPara && HasTextInCurrentPara() )
+ ImpInsertParaBreak();
+
+ sal_uInt16 nId = sal::static_int_cast< sal_uInt16 >(
+ 1 + ( ( static_cast<int>(nToken) - int(HtmlTokenId::HEAD1_ON) ) / 2 ) );
+ DBG_ASSERT( (nId >= 1) && (nId <= 9), "HeadingStart: ID can not be correct!" );
+ ImpSetStyleSheet( nId );
+}
+
+void EditHTMLParser::HeadingEnd()
+{
+ EndPara();
+ ImpSetStyleSheet( 0 );
+
+ if ( bWasInPara )
+ {
+ bInPara = true;
+ bWasInPara = false;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/eehtml.hxx b/editeng/source/editeng/eehtml.hxx
new file mode 100644
index 0000000000..fddd567ac6
--- /dev/null
+++ b/editeng/source/editeng/eehtml.hxx
@@ -0,0 +1,84 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <editdoc.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <svtools/parhtml.hxx>
+
+class EditEngine;
+
+struct AnchorInfo
+{
+ OUString aHRef;
+ OUString aText;
+};
+
+class EditHTMLParser : public HTMLParser
+{
+ using HTMLParser::CallParser;
+private:
+ OUStringBuffer maStyleSource;
+ EditSelection aCurSel;
+ OUString aBaseURL;
+ EditEngine* mpEditEngine;
+ std::optional<AnchorInfo> moCurAnchor;
+
+ bool bInPara:1;
+ bool bWasInPara:1; // Remember bInPara before HeadingStart, because afterwards it will be gone.
+ bool bFieldsInserted:1;
+ bool bInTitle:1;
+
+ sal_uInt8 nInTable;
+ sal_uInt8 nInCell;
+ sal_uInt8 nDefListLevel;
+
+ void StartPara( bool bReal );
+ void EndPara();
+ void AnchorStart();
+ void AnchorEnd();
+ void HeadingStart( HtmlTokenId nToken );
+ void HeadingEnd();
+ void SkipGroup( HtmlTokenId nEndToken );
+ bool ThrowAwayBlank();
+ bool HasTextInCurrentPara();
+
+ void ImpInsertParaBreak();
+ void ImpInsertText( const OUString& rText );
+ void ImpSetAttribs( const SfxItemSet& rItems );
+ void ImpSetStyleSheet( sal_uInt16 nHeadingLevel );
+
+protected:
+ virtual void NextToken( HtmlTokenId nToken ) override;
+
+public:
+ EditHTMLParser(SvStream& rIn, OUString aBaseURL, SvKeyValueIterator* pHTTPHeaderAttrs);
+ virtual ~EditHTMLParser() override;
+
+ SvParserState CallParser(EditEngine* pEE, const EditPaM& rPaM);
+
+ const EditSelection& GetCurSelection() const { return aCurSel; }
+};
+
+typedef tools::SvRef<EditHTMLParser> EditHTMLParserRef;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/eeobj.cxx b/editeng/source/editeng/eeobj.cxx
new file mode 100644
index 0000000000..bfd81c40c3
--- /dev/null
+++ b/editeng/source/editeng/eeobj.cxx
@@ -0,0 +1,92 @@
+/* -*- 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 <sal/config.h>
+
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include "eeobj.hxx"
+#include <sot/exchange.hxx>
+#include <sot/formats.hxx>
+
+using namespace ::com::sun::star;
+
+
+EditDataObject::EditDataObject()
+{
+}
+
+EditDataObject::~EditDataObject()
+{
+}
+
+// datatransfer::XTransferable
+uno::Any EditDataObject::getTransferData( const datatransfer::DataFlavor& rFlavor )
+{
+ uno::Any aAny;
+
+ SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
+ if ( nT == SotClipboardFormatId::STRING )
+ {
+ aAny <<= GetString();
+ }
+ else if ( ( nT == SotClipboardFormatId::RTF ) || ( nT == SotClipboardFormatId::RICHTEXT ) || ( nT == SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT ) )
+ {
+ // No RTF on demand any more:
+ // 1) Was not working, because I had to flush() the clipboard immediately anyway
+ // 2) Don't have the old pool defaults and the StyleSheetPool here.
+
+ SvMemoryStream* pStream = (nT == SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT ) ? &GetODFStream() : &GetRTFStream();
+ sal_Int32 nLen = pStream->TellEnd();
+ if (nLen < 0) { abort(); }
+
+ aAny <<= uno::Sequence< sal_Int8 >( static_cast< const sal_Int8* >(pStream->GetData()), pStream->TellEnd() );
+ }
+ else
+ {
+ datatransfer::UnsupportedFlavorException aException;
+ throw aException;
+ }
+
+ return aAny;
+}
+
+uno::Sequence< datatransfer::DataFlavor > EditDataObject::getTransferDataFlavors( )
+{
+ uno::Sequence< datatransfer::DataFlavor > aDataFlavors(4);
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT, aDataFlavors.getArray()[0] );
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aDataFlavors.getArray()[1] );
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RTF, aDataFlavors.getArray()[2] );
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RICHTEXT, aDataFlavors.getArray()[3] );
+
+ return aDataFlavors;
+}
+
+sal_Bool EditDataObject::isDataFlavorSupported( const datatransfer::DataFlavor& rFlavor )
+{
+ bool bSupported = false;
+
+ SotClipboardFormatId nT = SotExchange::GetFormat( rFlavor );
+ if ( ( nT == SotClipboardFormatId::STRING ) || ( nT == SotClipboardFormatId::RTF ) || ( nT == SotClipboardFormatId::RICHTEXT )
+ || ( nT == SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT ) )
+ bSupported = true;
+
+ return bSupported;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/eeobj.hxx b/editeng/source/editeng/eeobj.hxx
new file mode 100644
index 0000000000..de06f1b703
--- /dev/null
+++ b/editeng/source/editeng/eeobj.hxx
@@ -0,0 +1,50 @@
+/* -*- 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 .
+ */
+
+#pragma once
+
+#include <cppuhelper/implbase.hxx>
+#include <com/sun/star/datatransfer/XTransferable.hpp>
+
+#include <tools/stream.hxx>
+
+class EditDataObject : public ::cppu::WeakImplHelper<css::datatransfer::XTransferable>
+{
+private:
+ SvMemoryStream maRTFData;
+ SvMemoryStream maODFData;
+ OUString maText;
+ OUString maOfficeBookmark;
+
+public:
+ EditDataObject();
+ virtual ~EditDataObject() override;
+
+ SvMemoryStream& GetRTFStream() { return maRTFData; }
+ SvMemoryStream& GetODFStream() { return maODFData; }
+ OUString& GetString() { return maText; }
+ OUString& GetURL() { return maOfficeBookmark; }
+
+ // css::datatransfer::XTransferable
+ css::uno::Any SAL_CALL getTransferData( const css::datatransfer::DataFlavor& aFlavor ) override;
+ css::uno::Sequence< css::datatransfer::DataFlavor > SAL_CALL getTransferDataFlavors( ) override;
+ sal_Bool SAL_CALL isDataFlavorSupported( const css::datatransfer::DataFlavor& aFlavor ) override;
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/eerdll.cxx b/editeng/source/editeng/eerdll.cxx
new file mode 100644
index 0000000000..9e3e8c4cf8
--- /dev/null
+++ b/editeng/source/editeng/eerdll.cxx
@@ -0,0 +1,228 @@
+/* -*- 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 <unotools/resmgr.hxx>
+#include <com/sun/star/linguistic2/LanguageGuessing.hpp>
+
+#include <comphelper/processfactory.hxx>
+
+#include <editeng/eeitem.hxx>
+#include <editeng/eerdll.hxx>
+#include <eerdll2.hxx>
+#include <editeng/lspcitem.hxx>
+#include <editeng/adjustitem.hxx>
+#include <editeng/tstpitem.hxx>
+#include <editeng/bulletitem.hxx>
+#include <editeng/flditem.hxx>
+#include <editeng/emphasismarkitem.hxx>
+#include <editeng/scriptspaceitem.hxx>
+#include <editeng/hngpnctitem.hxx>
+#include <editeng/forbiddenruleitem.hxx>
+#include <svl/grabbagitem.hxx>
+#include <svl/voiditem.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/virdev.hxx>
+
+#include <editeng/autokernitem.hxx>
+#include <editeng/contouritem.hxx>
+#include <editeng/colritem.hxx>
+#include <editeng/crossedoutitem.hxx>
+#include <editeng/escapementitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/kernitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <editeng/postitem.hxx>
+#include <editeng/shdditem.hxx>
+#include <editeng/udlnitem.hxx>
+#include <editeng/ulspitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <editeng/wrlmitem.hxx>
+#include <editeng/numitem.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/cmapitem.hxx>
+#include <editeng/charscaleitem.hxx>
+#include <editeng/charreliefitem.hxx>
+#include <editeng/frmdiritem.hxx>
+#include <editeng/xmlcnitm.hxx>
+#include <editeng/forbiddencharacterstable.hxx>
+#include <editeng/justifyitem.hxx>
+#include <tools/mapunit.hxx>
+#include <vcl/lazydelete.hxx>
+
+using namespace ::com::sun::star;
+
+EditDLL& EditDLL::Get()
+{
+ /**
+ Prevent use-after-free errors during application shutdown.
+ Previously this data was function-static, but then data in i18npool would
+ be torn down before the destructor here ran, causing a crash.
+ */
+ static vcl::DeleteOnDeinit< EditDLL > gaEditDll;
+ return *gaEditDll.get();
+}
+
+DefItems::DefItems()
+ : mvDefItems(EDITITEMCOUNT)
+{
+ std::vector<SfxPoolItem*>& rDefItems = mvDefItems;
+
+ // Paragraph attributes:
+ SvxNumRule aDefaultNumRule( SvxNumRuleFlags::NONE, 0, false );
+
+ rDefItems[0] = new SvxFrameDirectionItem( SvxFrameDirection::Horizontal_LR_TB, EE_PARA_WRITINGDIR );
+ rDefItems[1] = new SvXMLAttrContainerItem( EE_PARA_XMLATTRIBS );
+ rDefItems[2] = new SvxHangingPunctuationItem(false, EE_PARA_HANGINGPUNCTUATION);
+ rDefItems[3] = new SvxForbiddenRuleItem(true, EE_PARA_FORBIDDENRULES);
+ rDefItems[4] = new SvxScriptSpaceItem( true, EE_PARA_ASIANCJKSPACING );
+ rDefItems[5] = new SvxNumBulletItem( aDefaultNumRule, EE_PARA_NUMBULLET );
+ rDefItems[6] = new SfxBoolItem( EE_PARA_HYPHENATE, false );
+ rDefItems[7] = new SfxBoolItem( EE_PARA_HYPHENATE_NO_CAPS, false );
+ rDefItems[8] = new SfxBoolItem( EE_PARA_HYPHENATE_NO_LAST_WORD, false );
+ rDefItems[9] = new SfxBoolItem( EE_PARA_BULLETSTATE, true );
+ rDefItems[10] = new SvxLRSpaceItem( EE_PARA_OUTLLRSPACE );
+ rDefItems[11] = new SfxInt16Item( EE_PARA_OUTLLEVEL, -1 );
+ rDefItems[12] = new SvxBulletItem( EE_PARA_BULLET );
+ rDefItems[13] = new SvxLRSpaceItem( EE_PARA_LRSPACE );
+ rDefItems[14] = new SvxULSpaceItem( EE_PARA_ULSPACE );
+ rDefItems[15] = new SvxLineSpacingItem( 0, EE_PARA_SBL );
+ rDefItems[16] = new SvxAdjustItem( SvxAdjust::Left, EE_PARA_JUST );
+ rDefItems[17] = new SvxTabStopItem( 0, 0, SvxTabAdjust::Left, EE_PARA_TABS );
+ rDefItems[18] = new SvxJustifyMethodItem( SvxCellJustifyMethod::Auto, EE_PARA_JUST_METHOD );
+ rDefItems[19] = new SvxVerJustifyItem( SvxCellVerJustify::Standard, EE_PARA_VER_JUST );
+
+ // Character attributes:
+ rDefItems[20] = new SvxColorItem( COL_AUTO, EE_CHAR_COLOR );
+ rDefItems[21] = new SvxFontItem( EE_CHAR_FONTINFO );
+ rDefItems[22] = new SvxFontHeightItem( 240, 100, EE_CHAR_FONTHEIGHT );
+ rDefItems[23] = new SvxCharScaleWidthItem( 100, EE_CHAR_FONTWIDTH );
+ rDefItems[24] = new SvxWeightItem( WEIGHT_NORMAL, EE_CHAR_WEIGHT );
+ rDefItems[25] = new SvxUnderlineItem( LINESTYLE_NONE, EE_CHAR_UNDERLINE );
+ rDefItems[26] = new SvxCrossedOutItem( STRIKEOUT_NONE, EE_CHAR_STRIKEOUT );
+ rDefItems[27] = new SvxPostureItem( ITALIC_NONE, EE_CHAR_ITALIC );
+ rDefItems[28] = new SvxContourItem( false, EE_CHAR_OUTLINE );
+ rDefItems[29] = new SvxShadowedItem( false, EE_CHAR_SHADOW );
+ rDefItems[30] = new SvxEscapementItem( 0, 100, EE_CHAR_ESCAPEMENT );
+ rDefItems[31] = new SvxAutoKernItem( false, EE_CHAR_PAIRKERNING );
+ rDefItems[32] = new SvxKerningItem( 0, EE_CHAR_KERNING );
+ rDefItems[33] = new SvxWordLineModeItem( false, EE_CHAR_WLM );
+ rDefItems[34] = new SvxLanguageItem( LANGUAGE_DONTKNOW, EE_CHAR_LANGUAGE );
+ rDefItems[35] = new SvxLanguageItem( LANGUAGE_DONTKNOW, EE_CHAR_LANGUAGE_CJK );
+ rDefItems[36] = new SvxLanguageItem( LANGUAGE_DONTKNOW, EE_CHAR_LANGUAGE_CTL );
+ rDefItems[37] = new SvxFontItem( EE_CHAR_FONTINFO_CJK );
+ rDefItems[38] = new SvxFontItem( EE_CHAR_FONTINFO_CTL );
+ rDefItems[39] = new SvxFontHeightItem( 240, 100, EE_CHAR_FONTHEIGHT_CJK );
+ rDefItems[40] = new SvxFontHeightItem( 240, 100, EE_CHAR_FONTHEIGHT_CTL );
+ rDefItems[41] = new SvxWeightItem( WEIGHT_NORMAL, EE_CHAR_WEIGHT_CJK );
+ rDefItems[42] = new SvxWeightItem( WEIGHT_NORMAL, EE_CHAR_WEIGHT_CTL );
+ rDefItems[43] = new SvxPostureItem( ITALIC_NONE, EE_CHAR_ITALIC_CJK );
+ rDefItems[44] = new SvxPostureItem( ITALIC_NONE, EE_CHAR_ITALIC_CTL );
+ rDefItems[45] = new SvxEmphasisMarkItem( FontEmphasisMark::NONE, EE_CHAR_EMPHASISMARK );
+ rDefItems[46] = new SvxCharReliefItem( FontRelief::NONE, EE_CHAR_RELIEF );
+ rDefItems[47] = new SfxVoidItem( EE_CHAR_RUBI_DUMMY );
+ rDefItems[48] = new SvXMLAttrContainerItem( EE_CHAR_XMLATTRIBS );
+ rDefItems[49] = new SvxOverlineItem( LINESTYLE_NONE, EE_CHAR_OVERLINE );
+ rDefItems[50] = new SvxCaseMapItem( SvxCaseMap::NotMapped, EE_CHAR_CASEMAP );
+ rDefItems[51] = new SfxGrabBagItem( EE_CHAR_GRABBAG );
+ rDefItems[52] = new SvxColorItem( COL_AUTO, EE_CHAR_BKGCOLOR );
+ // Features
+ rDefItems[53] = new SfxVoidItem( EE_FEATURE_TAB );
+ rDefItems[54] = new SfxVoidItem( EE_FEATURE_LINEBR );
+ rDefItems[55] = new SvxColorItem( COL_RED, EE_FEATURE_NOTCONV );
+ rDefItems[56] = new SvxFieldItem( SvxFieldData(), EE_FEATURE_FIELD );
+
+ assert(EDITITEMCOUNT == 57 && "ITEMCOUNT changed, adjust DefItems!");
+
+ // Init DefFonts:
+ GetDefaultFonts( *static_cast<SvxFontItem*>(rDefItems[EE_CHAR_FONTINFO - EE_ITEMS_START]),
+ *static_cast<SvxFontItem*>(rDefItems[EE_CHAR_FONTINFO_CJK - EE_ITEMS_START]),
+ *static_cast<SvxFontItem*>(rDefItems[EE_CHAR_FONTINFO_CTL - EE_ITEMS_START]) );
+}
+
+DefItems::~DefItems()
+{
+ for (const auto& rItem : mvDefItems)
+ delete rItem;
+}
+
+std::shared_ptr<DefItems> GlobalEditData::GetDefItems()
+{
+ auto xDefItems = m_xDefItems.lock();
+ if (!xDefItems)
+ {
+ xDefItems = std::make_shared<DefItems>();
+ m_xDefItems = xDefItems;
+ }
+ return xDefItems;
+}
+
+std::shared_ptr<SvxForbiddenCharactersTable> const & GlobalEditData::GetForbiddenCharsTable()
+{
+ if (!xForbiddenCharsTable)
+ xForbiddenCharsTable = SvxForbiddenCharactersTable::makeForbiddenCharactersTable(::comphelper::getProcessComponentContext());
+ return xForbiddenCharsTable;
+}
+
+uno::Reference< linguistic2::XLanguageGuessing > const & GlobalEditData::GetLanguageGuesser()
+{
+ if (!xLanguageGuesser.is())
+ {
+ xLanguageGuesser = linguistic2::LanguageGuessing::create( comphelper::getProcessComponentContext() );
+ }
+ return xLanguageGuesser;
+}
+
+OUString EditResId(TranslateId aId)
+{
+ return Translate::get(aId, Translate::Create("editeng"));
+}
+
+EditDLL::EditDLL()
+ : pGlobalData( new GlobalEditData )
+{
+}
+
+EditDLL::~EditDLL()
+{
+}
+
+editeng::SharedVclResources::SharedVclResources()
+ : m_pVirDev(VclPtr<VirtualDevice>::Create())
+{
+ m_pVirDev->SetMapMode(MapMode(MapUnit::MapTwip));
+}
+
+editeng::SharedVclResources::~SharedVclResources()
+ { m_pVirDev.disposeAndClear(); }
+
+VclPtr<VirtualDevice> const & editeng::SharedVclResources::GetVirtualDevice() const
+ { return m_pVirDev; }
+
+std::shared_ptr<editeng::SharedVclResources> EditDLL::GetSharedVclResources()
+{
+ SolarMutexGuard g;
+ auto pLocked(pSharedVcl.lock());
+ if(!pLocked)
+ pSharedVcl = pLocked = std::make_shared<editeng::SharedVclResources>();
+ return pLocked;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/eertfpar.cxx b/editeng/source/editeng/eertfpar.cxx
new file mode 100644
index 0000000000..948216f33d
--- /dev/null
+++ b/editeng/source/editeng/eertfpar.cxx
@@ -0,0 +1,633 @@
+/* -*- 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 <comphelper/string.hxx>
+#include <utility>
+
+#include "eertfpar.hxx"
+#include "impedit.hxx"
+#include <svl/intitem.hxx>
+#include <editeng/escapementitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/flditem.hxx>
+#include <editeng/editeng.hxx>
+
+#include <svtools/rtftoken.h>
+#include <svtools/htmltokn.h>
+#include <unotools/configmgr.hxx>
+
+using namespace com::sun::star;
+
+HtmlImportInfo::HtmlImportInfo( HtmlImportState eSt, SvParser<HtmlTokenId>* pPrsrs, const ESelection& rSel )
+ : aSelection( rSel )
+{
+ pParser = pPrsrs;
+ eState = eSt;
+ nToken = HtmlTokenId::NONE;
+}
+
+HtmlImportInfo::~HtmlImportInfo()
+{
+}
+
+RtfImportInfo::RtfImportInfo( RtfImportState eSt, SvParser<int>* pPrsrs, const ESelection& rSel )
+ : aSelection( rSel )
+{
+ pParser = pPrsrs;
+ eState = eSt;
+ nToken = 0;
+ nTokenValue = 0;
+}
+
+constexpr MapUnit gRTFMapUnit = MapUnit::MapTwip;
+
+EditRTFParser::EditRTFParser(
+ SvStream& rIn, EditSelection aSel, SfxItemPool& rAttrPool, EditEngine* pEditEngine) :
+ SvxRTFParser(rAttrPool, rIn),
+ aCurSel(std::move(aSel)),
+ mpEditEngine(pEditEngine),
+ nDefFont(0),
+ bLastActionInsertParaBreak(false)
+{
+ SetInsPos(EditPosition(mpEditEngine, &aCurSel));
+
+ // Convert the twips values ...
+ SetCalcValue(true);
+ SetChkStyleAttr(mpEditEngine->IsImportRTFStyleSheetsSet());
+ SetNewDoc(false); // So that the Pool-Defaults are not overwritten...
+ aEditMapMode = MapMode(mpEditEngine->GetRefDevice()->GetMapMode().GetMapUnit());
+}
+
+EditRTFParser::~EditRTFParser()
+{
+}
+
+SvParserState EditRTFParser::CallParser()
+{
+ DBG_ASSERT( !aCurSel.HasRange(), "Selection for CallParser!" );
+ // Separate the part that is imported from the rest.
+ // This expression should be used for all imports.
+ // aStart1PaM: Last position before the imported content
+ // aEnd1PaM: First position after the imported content
+ // aStart2PaM: First position of the imported content
+ // aEnd2PaM: Last position of the imported content
+ EditPaM aStart1PaM( aCurSel.Min().GetNode(), aCurSel.Min().GetIndex() );
+ aCurSel = mpEditEngine->InsertParaBreak(aCurSel);
+ EditPaM aStart2PaM = aCurSel.Min();
+ // Useful or not?
+ aStart2PaM.GetNode()->GetContentAttribs().GetItems().ClearItem();
+ AddRTFDefaultValues( aStart2PaM, aStart2PaM );
+ EditPaM aEnd1PaM = mpEditEngine->InsertParaBreak(aCurSel.Max());
+ // aCurCel now points to the gap
+
+ if (mpEditEngine->IsRtfImportHandlerSet())
+ {
+ RtfImportInfo aImportInfo(RtfImportState::Start, this, mpEditEngine->CreateESelection(aCurSel));
+ mpEditEngine->CallRtfImportHandler(aImportInfo);
+ }
+
+ SvParserState _eState = SvxRTFParser::CallParser();
+
+ if (mpEditEngine->IsRtfImportHandlerSet())
+ {
+ RtfImportInfo aImportInfo(RtfImportState::End, this, mpEditEngine->CreateESelection(aCurSel));
+ mpEditEngine->CallRtfImportHandler(aImportInfo);
+ }
+
+ if (bLastActionInsertParaBreak)
+ {
+ ContentNode* pCurNode = aCurSel.Max().GetNode();
+ sal_Int32 nPara = mpEditEngine->GetEditDoc().GetPos(pCurNode);
+ ContentNode* pPrevNode = mpEditEngine->GetEditDoc().GetObject(nPara-1);
+ DBG_ASSERT( pPrevNode, "Invalid RTF-Document?!" );
+ EditSelection aSel;
+ aSel.Min() = EditPaM( pPrevNode, pPrevNode->Len() );
+ aSel.Max() = EditPaM( pCurNode, 0 );
+ aCurSel.Max() = mpEditEngine->DeleteSelection(aSel);
+ }
+ EditPaM aEnd2PaM( aCurSel.Max() );
+ //AddRTFDefaultValues( aStart2PaM, aEnd2PaM );
+ bool bOnlyOnePara = ( aEnd2PaM.GetNode() == aStart2PaM.GetNode() );
+ // Paste the chunk again ...
+ // Problem: Paragraph attributes may not possibly be taken over
+ // => Do Character attributes.
+
+ bool bSpecialBackward = aStart1PaM.GetNode()->Len() == 0;
+ if ( bOnlyOnePara || aStart1PaM.GetNode()->Len() )
+ mpEditEngine->ParaAttribsToCharAttribs( aStart2PaM.GetNode() );
+ aCurSel.Min() = mpEditEngine->ConnectParagraphs(
+ aStart1PaM.GetNode(), aStart2PaM.GetNode(), bSpecialBackward );
+ bSpecialBackward = aEnd1PaM.GetNode()->Len() != 0;
+ // when bOnlyOnePara, then the node is gone on Connect.
+ if ( !bOnlyOnePara && aEnd1PaM.GetNode()->Len() )
+ mpEditEngine->ParaAttribsToCharAttribs( aEnd2PaM.GetNode() );
+ aCurSel.Max() = mpEditEngine->ConnectParagraphs(
+ ( bOnlyOnePara ? aStart1PaM.GetNode() : aEnd2PaM.GetNode() ),
+ aEnd1PaM.GetNode(), bSpecialBackward );
+
+ return _eState;
+}
+
+void EditRTFParser::AddRTFDefaultValues( const EditPaM& rStart, const EditPaM& rEnd )
+{
+ // Problem: DefFont and DefFontHeight
+ Size aSz( 12, 0 );
+ MapMode aPntMode( MapUnit::MapPoint );
+ MapMode _aEditMapMode(mpEditEngine->GetRefDevice()->GetMapMode().GetMapUnit());
+ aSz = mpEditEngine->GetRefDevice()->LogicToLogic(aSz, &aPntMode, &_aEditMapMode);
+ SvxFontHeightItem aFontHeightItem( aSz.Width(), 100, EE_CHAR_FONTHEIGHT );
+ vcl::Font aDefFont( GetFont( nDefFont ) );
+ SvxFontItem aFontItem( aDefFont.GetFamilyType(), aDefFont.GetFamilyName(),
+ aDefFont.GetStyleName(), aDefFont.GetPitch(), aDefFont.GetCharSet(), EE_CHAR_FONTINFO );
+
+ sal_Int32 nStartPara = mpEditEngine->GetEditDoc().GetPos( rStart.GetNode() );
+ sal_Int32 nEndPara = mpEditEngine->GetEditDoc().GetPos( rEnd.GetNode() );
+ for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; nPara++ )
+ {
+ ContentNode* pNode = mpEditEngine->GetEditDoc().GetObject( nPara );
+ assert(pNode && "AddRTFDefaultValues - No paragraph?!");
+ if ( !pNode->GetContentAttribs().HasItem( EE_CHAR_FONTINFO ) )
+ pNode->GetContentAttribs().GetItems().Put( aFontItem );
+ if ( !pNode->GetContentAttribs().HasItem( EE_CHAR_FONTHEIGHT ) )
+ pNode->GetContentAttribs().GetItems().Put( aFontHeightItem );
+ }
+}
+
+void EditRTFParser::NextToken( int nToken )
+{
+ switch( nToken )
+ {
+ case RTF_DEFF:
+ {
+ nDefFont = sal_uInt16(nTokenValue);
+ }
+ break;
+ case RTF_DEFTAB:
+ break;
+ case RTF_CELL:
+ {
+ aCurSel = mpEditEngine->InsertParaBreak(aCurSel);
+ }
+ break;
+ case RTF_LINE:
+ {
+ aCurSel = mpEditEngine->InsertLineBreak(aCurSel);
+ }
+ break;
+ case RTF_FIELD:
+ {
+ ReadField();
+ }
+ break;
+ case RTF_SHPINST: // fdo#76776 process contents of shpinst
+ break;
+ case RTF_SP: // fdo#76776 but skip SP groups
+ {
+ SkipGroup();
+ }
+ break;
+ case RTF_LISTTEXT:
+ {
+ SkipGroup();
+ }
+ break;
+ default:
+ {
+ SvxRTFParser::NextToken( nToken );
+ if ( nToken == RTF_STYLESHEET )
+ CreateStyleSheets();
+ }
+ break;
+ }
+ if (mpEditEngine->IsRtfImportHandlerSet())
+ {
+ RtfImportInfo aImportInfo(RtfImportState::NextToken, this, mpEditEngine->CreateESelection(aCurSel));
+ aImportInfo.nToken = nToken;
+ aImportInfo.nTokenValue = short(nTokenValue);
+ mpEditEngine->CallRtfImportHandler(aImportInfo);
+ }
+}
+
+void EditRTFParser::UnknownAttrToken( int nToken )
+{
+ // for Tokens which are not evaluated in ReadAttr
+ // Actually, only for Calc (RTFTokenHdl), so that RTF_INTBL
+ if (mpEditEngine->IsRtfImportHandlerSet())
+ {
+ RtfImportInfo aImportInfo(RtfImportState::UnknownAttr, this, mpEditEngine->CreateESelection(aCurSel));
+ aImportInfo.nToken = nToken;
+ aImportInfo.nTokenValue = short(nTokenValue);
+ mpEditEngine->CallRtfImportHandler(aImportInfo);
+ }
+}
+
+void EditRTFParser::InsertText()
+{
+ OUString aText( aToken );
+ if (mpEditEngine->IsRtfImportHandlerSet())
+ {
+ RtfImportInfo aImportInfo(RtfImportState::InsertText, this, mpEditEngine->CreateESelection(aCurSel));
+ mpEditEngine->CallRtfImportHandler(aImportInfo);
+ }
+ aCurSel = mpEditEngine->InsertText(aCurSel, aText);
+ bLastActionInsertParaBreak = false;
+}
+
+void EditRTFParser::InsertPara()
+{
+ if (mpEditEngine->IsRtfImportHandlerSet())
+ {
+ RtfImportInfo aImportInfo(RtfImportState::InsertPara, this, mpEditEngine->CreateESelection(aCurSel));
+ mpEditEngine->CallRtfImportHandler(aImportInfo);
+ }
+ aCurSel = mpEditEngine->InsertParaBreak(aCurSel);
+ bLastActionInsertParaBreak = true;
+}
+
+void EditRTFParser::MovePos( bool const bForward )
+{
+ if( bForward )
+ aCurSel = mpEditEngine->CursorRight(
+ aCurSel.Max(), i18n::CharacterIteratorMode::SKIPCHARACTER);
+ else
+ aCurSel = mpEditEngine->CursorLeft(
+ aCurSel.Max(), i18n::CharacterIteratorMode::SKIPCHARACTER);
+}
+
+void EditRTFParser::SetEndPrevPara( std::optional<EditNodeIdx>& rpNodePos,
+ sal_Int32& rCntPos )
+{
+ // The Intention is to: determine the current insert position of the
+ // previous paragraph and set the end from this.
+ // This "\pard" always apply on the right paragraph.
+
+ ContentNode* pN = aCurSel.Max().GetNode();
+ sal_Int32 nCurPara = mpEditEngine->GetEditDoc().GetPos( pN );
+ DBG_ASSERT( nCurPara != 0, "Paragraph equal to 0: SetEnfPrevPara" );
+ if ( nCurPara )
+ nCurPara--;
+ ContentNode* pPrevNode = mpEditEngine->GetEditDoc().GetObject( nCurPara );
+ assert(pPrevNode && "pPrevNode = 0!");
+ rpNodePos = EditNodeIdx(mpEditEngine, pPrevNode);
+ rCntPos = pPrevNode->Len();
+}
+
+bool EditRTFParser::IsEndPara( EditNodeIdx* pNd, sal_Int32 nCnt ) const
+{
+ return nCnt == pNd->GetNode()->Len();
+}
+
+void EditRTFParser::SetAttrInDoc( SvxRTFItemStackType &rSet )
+{
+ ContentNode* pSttNode = const_cast<EditNodeIdx&>(rSet.GetSttNode()).GetNode();
+ ContentNode* pEndNode = const_cast<EditNodeIdx&>(rSet.GetEndNode()).GetNode();
+
+ EditPaM aStartPaM( pSttNode, rSet.GetSttCnt() );
+ EditPaM aEndPaM( pEndNode, rSet.GetEndCnt() );
+
+ // If possible adjust the Escapement-Item:
+
+ // #i66167# adapt font heights to destination MapUnit if necessary
+ const MapUnit eDestUnit = mpEditEngine->GetEditDoc().GetItemPool().GetMetric(0);
+ if (eDestUnit != gRTFMapUnit)
+ {
+ sal_uInt16 const aFntHeightIems[3] = { EE_CHAR_FONTHEIGHT, EE_CHAR_FONTHEIGHT_CJK, EE_CHAR_FONTHEIGHT_CTL };
+ for (unsigned short aFntHeightIem : aFntHeightIems)
+ {
+ const SfxPoolItem* pItem;
+ if (SfxItemState::SET == rSet.GetAttrSet().GetItemState( aFntHeightIem, false, &pItem ))
+ {
+ sal_uInt32 nHeight = static_cast<const SvxFontHeightItem*>(pItem)->GetHeight();
+ tools::Long nNewHeight;
+ nNewHeight = OutputDevice::LogicToLogic( static_cast<tools::Long>(nHeight), gRTFMapUnit, eDestUnit );
+
+ SvxFontHeightItem aFntHeightItem( nNewHeight, 100, aFntHeightIem );
+ aFntHeightItem.SetProp(
+ static_cast<const SvxFontHeightItem*>(pItem)->GetProp(),
+ static_cast<const SvxFontHeightItem*>(pItem)->GetPropUnit());
+ rSet.GetAttrSet().Put( aFntHeightItem );
+ }
+ }
+ }
+
+ if( const SvxEscapementItem* pItem = rSet.GetAttrSet().GetItemIfSet( EE_CHAR_ESCAPEMENT, false ) )
+ {
+ // the correct one
+ tools::Long nEsc = pItem->GetEsc();
+ tools::Long nEscFontHeight = 0;
+ if( ( DFLT_ESC_AUTO_SUPER != nEsc ) && ( DFLT_ESC_AUTO_SUB != nEsc ) )
+ {
+ nEsc *= 10; //HalfPoints => Twips was embezzled in RTFITEM.CXX!
+ SvxFont aFont;
+ if (utl::ConfigManager::IsFuzzing())
+ {
+ // ofz#24932 detecting RTL vs LTR is slow
+ aFont = aStartPaM.GetNode()->GetCharAttribs().GetDefFont();
+ }
+ else
+ mpEditEngine->SeekCursor(aStartPaM.GetNode(), aStartPaM.GetIndex()+1, aFont);
+ nEscFontHeight = aFont.GetFontSize().Height();
+ }
+ if (nEscFontHeight)
+ {
+ nEsc = nEsc * 100 / nEscFontHeight;
+
+ SvxEscapementItem aEscItem( static_cast<short>(nEsc), pItem->GetProportionalHeight(), EE_CHAR_ESCAPEMENT );
+ rSet.GetAttrSet().Put( aEscItem );
+ }
+ }
+
+ if (mpEditEngine->IsRtfImportHandlerSet())
+ {
+ EditSelection aSel( aStartPaM, aEndPaM );
+ RtfImportInfo aImportInfo(RtfImportState::SetAttr, this, mpEditEngine->CreateESelection(aSel));
+ mpEditEngine->CallRtfImportHandler(aImportInfo);
+ }
+
+ ContentNode* pSN = aStartPaM.GetNode();
+ ContentNode* pEN = aEndPaM.GetNode();
+ sal_Int32 nStartNode = mpEditEngine->GetEditDoc().GetPos( pSN );
+ sal_Int32 nEndNode = mpEditEngine->GetEditDoc().GetPos( pEN );
+ sal_Int16 nOutlLevel = 0xff;
+
+ if (rSet.StyleNo() && mpEditEngine->GetStyleSheetPool() && mpEditEngine->IsImportRTFStyleSheetsSet())
+ {
+ SvxRTFStyleTbl::iterator it = GetStyleTbl().find( rSet.StyleNo() );
+ DBG_ASSERT( it != GetStyleTbl().end(), "Template not defined in RTF!" );
+ if ( it != GetStyleTbl().end() )
+ {
+ auto const& pS = it->second;
+ mpEditEngine->SetStyleSheet(
+ EditSelection(aStartPaM, aEndPaM),
+ static_cast<SfxStyleSheet*>(mpEditEngine->GetStyleSheetPool()->Find(pS.sName, SfxStyleFamily::All)));
+ nOutlLevel = pS.nOutlineNo;
+ }
+ }
+
+ // When an Attribute goes from 0 to the current paragraph length,
+ // it should be a paragraph attribute!
+
+ // Note: Selection can reach over several paragraphs.
+ // All Complete paragraphs are paragraph attributes ...
+ for ( sal_Int32 z = nStartNode+1; z < nEndNode; z++ )
+ {
+ DBG_ASSERT(mpEditEngine->GetEditDoc().GetObject(z), "Node does not exist yet(RTF)");
+ mpEditEngine->SetParaAttribsOnly(z, rSet.GetAttrSet());
+ }
+
+ if ( aStartPaM.GetNode() != aEndPaM.GetNode() )
+ {
+ // The rest of the StartNodes...
+ if ( aStartPaM.GetIndex() == 0 )
+ mpEditEngine->SetParaAttribsOnly(nStartNode, rSet.GetAttrSet());
+ else
+ mpEditEngine->SetAttribs(
+ EditSelection(aStartPaM, EditPaM(aStartPaM.GetNode(), aStartPaM.GetNode()->Len())), rSet.GetAttrSet());
+
+ // the beginning of the EndNodes...
+ if ( aEndPaM.GetIndex() == aEndPaM.GetNode()->Len() )
+ mpEditEngine->SetParaAttribsOnly(nEndNode, rSet.GetAttrSet());
+ else
+ mpEditEngine->SetAttribs(
+ EditSelection(EditPaM(aEndPaM.GetNode(), 0), aEndPaM), rSet.GetAttrSet());
+ }
+ else
+ {
+ if ( ( aStartPaM.GetIndex() == 0 ) && ( aEndPaM.GetIndex() == aEndPaM.GetNode()->Len() ) )
+ {
+ // When settings char attribs as para attribs, we must merge with existing attribs, not overwrite the ItemSet!
+ SfxItemSet aAttrs = mpEditEngine->GetBaseParaAttribs(nStartNode);
+ aAttrs.Put( rSet.GetAttrSet() );
+ mpEditEngine->SetParaAttribsOnly(nStartNode, aAttrs);
+ }
+ else
+ {
+ mpEditEngine->SetAttribs(
+ EditSelection(aStartPaM, aEndPaM), rSet.GetAttrSet());
+ }
+ }
+
+ // OutlLevel...
+ if ( nOutlLevel != 0xff )
+ {
+ for ( sal_Int32 n = nStartNode; n <= nEndNode; n++ )
+ {
+ ContentNode* pNode = mpEditEngine->GetEditDoc().GetObject( n );
+ pNode->GetContentAttribs().GetItems().Put( SfxInt16Item( EE_PARA_OUTLLEVEL, nOutlLevel ) );
+ }
+ }
+}
+
+SvxRTFStyleType* EditRTFParser::FindStyleSheet( std::u16string_view rName )
+{
+ SvxRTFStyleTbl& rTable = GetStyleTbl();
+ for (auto & iter : rTable)
+ {
+ if (iter.second.sName == rName)
+ return &iter.second;
+ }
+ return nullptr;
+}
+
+SfxStyleSheet* EditRTFParser::CreateStyleSheet( SvxRTFStyleType const * pRTFStyle )
+{
+ // Check if a template exists, then it will not be changed!
+ SfxStyleSheet* pStyle = static_cast<SfxStyleSheet*>(mpEditEngine->GetStyleSheetPool()->Find( pRTFStyle->sName, SfxStyleFamily::All ));
+ if ( pStyle )
+ return pStyle;
+
+ OUString aName( pRTFStyle->sName );
+ OUString aParent;
+ if ( pRTFStyle->nBasedOn )
+ {
+ SvxRTFStyleTbl::iterator it = GetStyleTbl().find( pRTFStyle->nBasedOn );
+ if ( it != GetStyleTbl().end())
+ {
+ SvxRTFStyleType const& rS = it->second;
+ if ( &rS != pRTFStyle )
+ aParent = rS.sName;
+ }
+ }
+
+ pStyle = static_cast<SfxStyleSheet*>( &mpEditEngine->GetStyleSheetPool()->Make( aName, SfxStyleFamily::Para ) );
+
+ // 1) convert and take over Items ...
+ ConvertAndPutItems( pStyle->GetItemSet(), pRTFStyle->aAttrSet );
+
+ // 2) As long as Parent is not in the pool, also create this ...
+ if ( !aParent.isEmpty() && ( aParent != aName ) )
+ {
+ SfxStyleSheet* pS = static_cast<SfxStyleSheet*>(mpEditEngine->GetStyleSheetPool()->Find( aParent, SfxStyleFamily::All ));
+ if ( !pS )
+ {
+ // If not found anywhere, create from RTF ...
+ SvxRTFStyleType* _pRTFStyle = FindStyleSheet( aParent );
+ if ( _pRTFStyle )
+ pS = CreateStyleSheet( _pRTFStyle );
+ }
+ // 2b) Link Itemset with Parent ...
+ if ( pS )
+ pStyle->GetItemSet().SetParent( &pS->GetItemSet() );
+ }
+ return pStyle;
+}
+
+void EditRTFParser::CreateStyleSheets()
+{
+ // the SvxRTFParser has now created the template...
+ if (mpEditEngine->GetStyleSheetPool() && mpEditEngine->IsImportRTFStyleSheetsSet())
+ {
+ for (auto & elem : GetStyleTbl())
+ {
+ SvxRTFStyleType& rRTFStyle = elem.second;
+ CreateStyleSheet( &rRTFStyle );
+ }
+ }
+}
+
+void EditRTFParser::CalcValue()
+{
+ const MapUnit eDestUnit = aEditMapMode.GetMapUnit();
+ if (eDestUnit != gRTFMapUnit)
+ nTokenValue = OutputDevice::LogicToLogic( nTokenValue, gRTFMapUnit, eDestUnit );
+}
+
+void EditRTFParser::ReadField()
+{
+ // From SwRTFParser::ReadField()
+ int _nOpenBrackets = 1; // the first was already detected earlier
+ bool bFldInst = false;
+ bool bFldRslt = false;
+ OUString aFldInst;
+ OUString aFldRslt;
+
+ while( _nOpenBrackets && IsParserWorking() )
+ {
+ switch( GetNextToken() )
+ {
+ case '}':
+ {
+ _nOpenBrackets--;
+ if ( _nOpenBrackets == 1 )
+ {
+ bFldInst = false;
+ bFldRslt = false;
+ }
+ }
+ break;
+
+ case '{': _nOpenBrackets++;
+ break;
+
+ case RTF_FIELD: SkipGroup();
+ break;
+
+ case RTF_FLDINST: bFldInst = true;
+ break;
+
+ case RTF_FLDRSLT: bFldRslt = true;
+ break;
+
+ case RTF_TEXTTOKEN:
+ {
+ if ( bFldInst )
+ aFldInst += aToken;
+ else if ( bFldRslt )
+ aFldRslt += aToken;
+ }
+ break;
+ }
+ }
+ if ( !aFldInst.isEmpty() )
+ {
+ OUString aHyperLinkMarker( "HYPERLINK " );
+ if ( aFldInst.startsWithIgnoreAsciiCase( aHyperLinkMarker ) )
+ {
+ aFldInst = aFldInst.copy( aHyperLinkMarker.getLength() );
+ aFldInst = comphelper::string::strip(aFldInst, ' ');
+ // strip start and end quotes
+ aFldInst = aFldInst.copy( 1, aFldInst.getLength()-2 );
+
+ if ( aFldRslt.isEmpty() )
+ aFldRslt = aFldInst;
+
+ SvxFieldItem aField( SvxURLField( aFldInst, aFldRslt, SvxURLFormat::Repr ), EE_FEATURE_FIELD );
+ aCurSel = mpEditEngine->InsertField(aCurSel, aField);
+ mpEditEngine->UpdateFieldsOnly();
+ bLastActionInsertParaBreak = false;
+ }
+ }
+
+ SkipToken(); // the closing brace is evaluated "above"
+}
+
+void EditRTFParser::SkipGroup()
+{
+ int _nOpenBrackets = 1; // the first was already detected earlier
+
+ while( _nOpenBrackets && IsParserWorking() )
+ {
+ switch( GetNextToken() )
+ {
+ case '}':
+ {
+ _nOpenBrackets--;
+ }
+ break;
+
+ case '{':
+ {
+ _nOpenBrackets++;
+ }
+ break;
+ }
+ }
+
+ SkipToken(); // the closing brace is evaluated "above"
+}
+
+EditNodeIdx::EditNodeIdx(EditEngine* pEE, ContentNode* pNd) :
+ mpEditEngine(pEE), mpNode(pNd) {}
+
+sal_Int32 EditNodeIdx::GetIdx() const
+{
+ return mpEditEngine->GetEditDoc().GetPos(mpNode);
+}
+
+EditPosition::EditPosition(EditEngine* pEE, EditSelection* pSel) :
+ mpEditEngine(pEE), mpCurSel(pSel) {}
+
+EditNodeIdx EditPosition::MakeNodeIdx() const
+{
+ return EditNodeIdx(mpEditEngine, mpCurSel->Max().GetNode());
+}
+
+sal_Int32 EditPosition::GetNodeIdx() const
+{
+ ContentNode* pN = mpCurSel->Max().GetNode();
+ return mpEditEngine->GetEditDoc().GetPos(pN);
+}
+
+sal_Int32 EditPosition::GetCntIdx() const
+{
+ return mpCurSel->Max().GetIndex();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/eertfpar.hxx b/editeng/source/editeng/eertfpar.hxx
new file mode 100644
index 0000000000..19ce00ce32
--- /dev/null
+++ b/editeng/source/editeng/eertfpar.hxx
@@ -0,0 +1,66 @@
+/* -*- 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 .
+ */
+#pragma once
+
+#include <editeng/svxrtf.hxx>
+
+#include <editdoc.hxx>
+
+class EditEngine;
+
+class EditRTFParser final : public SvxRTFParser
+{
+private:
+ EditSelection aCurSel;
+ EditEngine* mpEditEngine;
+ MapMode aEditMapMode;
+
+ sal_uInt16 nDefFont;
+ bool bLastActionInsertParaBreak;
+
+ virtual void InsertPara() override;
+ virtual void InsertText() override;
+ virtual void MovePos( bool bForward = true ) override;
+ virtual void SetEndPrevPara( std::optional<EditNodeIdx>& rpNodePos,
+ sal_Int32& rCntPos ) override;
+
+ virtual void UnknownAttrToken( int nToken ) override;
+ virtual void NextToken( int nToken ) override;
+ virtual void SetAttrInDoc( SvxRTFItemStackType &rSet ) override;
+ virtual bool IsEndPara( EditNodeIdx* pNd, sal_Int32 nCnt ) const override;
+ virtual void CalcValue() override;
+ void CreateStyleSheets();
+ SfxStyleSheet* CreateStyleSheet( SvxRTFStyleType const * pRTFStyle );
+ SvxRTFStyleType* FindStyleSheet( std::u16string_view rName );
+ void AddRTFDefaultValues( const EditPaM& rStart, const EditPaM& rEnd );
+ void ReadField();
+ void SkipGroup();
+
+public:
+ EditRTFParser(SvStream& rIn, EditSelection aCurSel, SfxItemPool& rAttrPool, EditEngine* pEditEngine);
+ virtual ~EditRTFParser() override;
+
+ virtual SvParserState CallParser() override;
+
+ EditPaM const & GetCurPaM() const { return aCurSel.Max(); }
+};
+
+typedef tools::SvRef<EditRTFParser> EditRTFParserRef;
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/fieldupdater.cxx b/editeng/source/editeng/fieldupdater.cxx
new file mode 100644
index 0000000000..05eca45755
--- /dev/null
+++ b/editeng/source/editeng/fieldupdater.cxx
@@ -0,0 +1,70 @@
+/* -*- 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/.
+ */
+
+#include <memory>
+#include <editeng/fieldupdater.hxx>
+#include <editeng/flditem.hxx>
+#include "editobj2.hxx"
+
+#include <com/sun/star/text/textfield/Type.hpp>
+
+using namespace com::sun::star;
+
+namespace editeng {
+
+class FieldUpdaterImpl
+{
+ EditTextObjectImpl& mrObj;
+public:
+ explicit FieldUpdaterImpl(EditTextObject& rObj) : mrObj(toImpl(rObj)) {}
+
+ void updateTableFields(int nTab)
+ {
+ SfxItemPool* pPool = mrObj.GetPool();
+ EditTextObjectImpl::ContentInfosType& rContents = mrObj.GetContents();
+ for (std::unique_ptr<ContentInfo> & i : rContents)
+ {
+ ContentInfo& rContent = *i;
+ for (XEditAttribute & rAttr : rContent.GetCharAttribs())
+ {
+ const SfxPoolItem* pItem = rAttr.GetItem();
+ if (pItem->Which() != EE_FEATURE_FIELD)
+ // This is not a field item.
+ continue;
+
+ const SvxFieldItem* pFI = static_cast<const SvxFieldItem*>(pItem);
+ const SvxFieldData* pData = pFI->GetField();
+ if (pData->GetClassId() != text::textfield::Type::TABLE)
+ // This is not a table field.
+ continue;
+
+ // Create a new table field with the new ID, and set it to the
+ // attribute object.
+ SvxFieldItem aNewItem(SvxTableField(nTab), EE_FEATURE_FIELD);
+ rAttr.SetItem(*pPool, aNewItem);
+ }
+ }
+ }
+};
+
+FieldUpdater::FieldUpdater(EditTextObject& rObj) : mpImpl(new FieldUpdaterImpl(rObj)) {}
+FieldUpdater::FieldUpdater(const FieldUpdater& r) : mpImpl(new FieldUpdaterImpl(*r.mpImpl)) {}
+
+FieldUpdater::~FieldUpdater()
+{
+}
+
+void FieldUpdater::updateTableFields(int nTab)
+{
+ mpImpl->updateTableFields(nTab);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/impedit.cxx b/editeng/source/editeng/impedit.cxx
new file mode 100644
index 0000000000..92fb5affa6
--- /dev/null
+++ b/editeng/source/editeng/impedit.cxx
@@ -0,0 +1,2787 @@
+/* -*- 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 "impedit.hxx"
+#include <sal/log.hxx>
+#include <editeng/editeng.hxx>
+#include <editeng/editview.hxx>
+#include <editeng/outliner.hxx>
+#include <editeng/urlfieldhelper.hxx>
+#include <tools/poly.hxx>
+#include <editeng/unolingu.hxx>
+#include <com/sun/star/linguistic2/XDictionary.hpp>
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp>
+#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
+#include <com/sun/star/datatransfer/clipboard/XFlushableClipboard.hpp>
+#include <comphelper/lok.hxx>
+#include <editeng/flditem.hxx>
+#include <svl/intitem.hxx>
+#include <vcl/inputctx.hxx>
+#include <vcl/transfer.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/weldutils.hxx>
+#include <vcl/window.hxx>
+#include <sot/exchange.hxx>
+#include <sot/formats.hxx>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <comphelper/string.hxx>
+#include <sfx2/viewsh.hxx>
+#include <sfx2/lokhelper.hxx>
+#include <boost/property_tree/ptree.hpp>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::linguistic2;
+
+#define SCRLRANGE 20 // Scroll 1/20 of the width/height, when in QueryDrop
+
+static void lcl_AlignToPixel(Point& rPoint, const OutputDevice& rOutDev, short nDiffX, short nDiffY)
+{
+ rPoint = rOutDev.LogicToPixel( rPoint );
+
+ if ( nDiffX )
+ rPoint.AdjustX(nDiffX );
+ if ( nDiffY )
+ rPoint.AdjustY(nDiffY );
+
+ rPoint = rOutDev.PixelToLogic( rPoint );
+}
+
+LOKSpecialPositioning::LOKSpecialPositioning(const ImpEditView& rImpEditView, MapUnit eUnit,
+ const tools::Rectangle& rOutputArea,
+ const Point& rVisDocStartPos) :
+ mrImpEditView(rImpEditView),
+ maOutArea(rOutputArea),
+ maVisDocStartPos(rVisDocStartPos),
+ meUnit(eUnit),
+ meFlags(LOKSpecialFlags::NONE)
+{
+}
+
+void LOKSpecialPositioning::ReInit(MapUnit eUnit, const tools::Rectangle& rOutputArea, const Point& rVisDocStartPos)
+{
+ meUnit = eUnit;
+ maOutArea = rOutputArea;
+ maVisDocStartPos = rVisDocStartPos;
+}
+
+void LOKSpecialPositioning::SetOutputArea(const tools::Rectangle& rOutputArea)
+{
+ maOutArea = rOutputArea;
+}
+
+const tools::Rectangle& LOKSpecialPositioning::GetOutputArea() const
+{
+ return maOutArea;
+}
+
+void LOKSpecialPositioning::SetVisDocStartPos(const Point& rVisDocStartPos)
+{
+ maVisDocStartPos = rVisDocStartPos;
+}
+
+tools::Rectangle LOKSpecialPositioning::GetVisDocArea() const
+{
+ return tools::Rectangle(GetVisDocLeft(), GetVisDocTop(), GetVisDocRight(), GetVisDocBottom());
+}
+
+bool LOKSpecialPositioning::IsVertical() const
+{
+ return mrImpEditView.IsVertical();
+}
+
+bool LOKSpecialPositioning::IsTopToBottom() const
+{
+ return mrImpEditView.IsTopToBottom();
+}
+
+Point LOKSpecialPositioning::GetWindowPos(const Point& rDocPos, MapUnit eDocPosUnit) const
+{
+ const Point aDocPos = convertUnit(rDocPos, eDocPosUnit);
+ Point aPoint;
+ if ( !IsVertical() )
+ {
+ aPoint.setX(aDocPos.X() + maOutArea.Left() - GetVisDocLeft());
+ aPoint.setY(aDocPos.Y() + maOutArea.Top() - GetVisDocTop());
+ }
+ else
+ {
+ if (IsTopToBottom())
+ {
+ aPoint.setX(maOutArea.Right() - aDocPos.Y() + GetVisDocTop());
+ aPoint.setY(aDocPos.X() + maOutArea.Top() - GetVisDocLeft());
+ }
+ else
+ {
+ aPoint.setX(maOutArea.Left() + aDocPos.Y() - GetVisDocTop());
+ aPoint.setY(maOutArea.Bottom() - aDocPos.X() + GetVisDocLeft());
+ }
+ }
+
+ return aPoint;
+}
+
+tools::Rectangle LOKSpecialPositioning::GetWindowPos(const tools::Rectangle& rDocRect, MapUnit eDocRectUnit) const
+{
+ const tools::Rectangle aDocRect = convertUnit(rDocRect, eDocRectUnit);
+ Point aPos(GetWindowPos(aDocRect.TopLeft(), meUnit));
+ Size aSz = aDocRect.GetSize();
+ tools::Rectangle aRect;
+ if (!IsVertical())
+ {
+ aRect = tools::Rectangle(aPos, aSz);
+ }
+ else
+ {
+ Point aNewPos(aPos.X() - aSz.Height(), aPos.Y());
+ // coverity[swapped_arguments : FALSE] - this is in the correct order
+ aRect = tools::Rectangle(aNewPos, Size(aSz.Height(), aSz.Width()));
+ }
+ return aRect;
+}
+
+Point LOKSpecialPositioning::convertUnit(const Point& rPos, MapUnit ePosUnit) const
+{
+ if (ePosUnit == meUnit)
+ return rPos;
+
+ return OutputDevice::LogicToLogic(rPos, MapMode(ePosUnit), MapMode(meUnit));
+}
+
+tools::Rectangle LOKSpecialPositioning::convertUnit(const tools::Rectangle& rRect, MapUnit eRectUnit) const
+{
+ if (eRectUnit == meUnit)
+ return rRect;
+
+ return OutputDevice::LogicToLogic(rRect, MapMode(eRectUnit), MapMode(meUnit));
+}
+
+Point LOKSpecialPositioning::GetRefPoint() const
+{
+ return maOutArea.TopLeft();
+}
+
+// class ImpEditView
+
+ImpEditView::ImpEditView( EditView* pView, EditEngine* pEng, vcl::Window* pWindow ) :
+ pEditView(pView),
+ mpViewShell(nullptr),
+ mpOtherShell(nullptr),
+ pEditEngine(pEng),
+ pOutWin(pWindow),
+ nInvMore(1),
+ nControl(EVControlBits::AUTOSCROLL | EVControlBits::ENABLEPASTE),
+ nTravelXPos(TRAVEL_X_DONTKNOW),
+ nExtraCursorFlags(GetCursorFlags::NONE),
+ nCursorBidiLevel(CURSOR_BIDILEVEL_DONTKNOW),
+ nScrollDiffX(0),
+ bReadOnly(false),
+ bClickedInSelection(false),
+ bActiveDragAndDropListener(false),
+ aOutArea( Point(), pEng->GetPaperSize() ),
+ eSelectionMode(EESelectionMode::Std),
+ eAnchorMode(EEAnchorMode::TopLeft),
+ mpEditViewCallbacks(nullptr),
+ mbBroadcastLOKViewCursor(comphelper::LibreOfficeKit::isActive()),
+ mbSuppressLOKMessages(false),
+ mbNegativeX(false)
+{
+ aEditSelection.Min() = pEng->GetEditDoc().GetStartPaM();
+ aEditSelection.Max() = pEng->GetEditDoc().GetEndPaM();
+
+ SelectionChanged();
+}
+
+ImpEditView::~ImpEditView()
+{
+ RemoveDragAndDropListeners();
+
+ if ( pOutWin && ( pOutWin->GetCursor() == pCursor.get() ) )
+ pOutWin->SetCursor( nullptr );
+}
+
+void ImpEditView::SetBackgroundColor( const Color& rColor )
+{
+ mxBackgroundColor = rColor;
+}
+
+const Color& ImpEditView::GetBackgroundColor() const
+{
+ return mxBackgroundColor ? *mxBackgroundColor : GetOutputDevice().GetBackground().GetColor();
+}
+
+void ImpEditView::RegisterViewShell(OutlinerViewShell* pViewShell)
+{
+ mpViewShell = pViewShell;
+}
+
+void ImpEditView::RegisterOtherShell(OutlinerViewShell* pOtherShell)
+{
+ mpOtherShell = pOtherShell;
+}
+
+const OutlinerViewShell* ImpEditView::GetViewShell() const
+{
+ return mpViewShell;
+}
+
+void ImpEditView::SetEditSelection( const EditSelection& rEditSelection )
+{
+ // set state before notification
+ aEditSelection = rEditSelection;
+
+ SelectionChanged();
+
+ if (comphelper::LibreOfficeKit::isActive())
+ // Tiled rendering: selections are only painted when we are in selection mode.
+ pEditEngine->SetInSelectionMode(aEditSelection.HasRange());
+
+ if ( pEditEngine->pImpEditEngine->GetNotifyHdl().IsSet() )
+ {
+ const EditDoc& rDoc = pEditEngine->GetEditDoc();
+ const EditPaM pmEnd = rDoc.GetEndPaM();
+ EENotifyType eNotifyType;
+ if (rDoc.Count() > 1 &&
+ pmEnd == rEditSelection.Min() &&
+ pmEnd == rEditSelection.Max())//if move cursor to the last para.
+ {
+ eNotifyType = EE_NOTIFY_TEXTVIEWSELECTIONCHANGED_ENDD_PARA;
+ }
+ else
+ {
+ eNotifyType = EE_NOTIFY_TEXTVIEWSELECTIONCHANGED;
+ }
+ EENotify aNotify( eNotifyType );
+ pEditEngine->pImpEditEngine->GetNotifyHdl().Call( aNotify );
+ }
+ if(pEditEngine->pImpEditEngine->IsFormatted())
+ {
+ EENotify aNotify(EE_NOTIFY_PROCESSNOTIFICATIONS);
+ pEditEngine->pImpEditEngine->GetNotifyHdl().Call(aNotify);
+ }
+}
+
+/// Translate absolute <-> relative twips: LOK wants absolute coordinates as output and gives absolute coordinates as input.
+static void lcl_translateTwips(const OutputDevice& rParent, OutputDevice& rChild)
+{
+ // Don't translate if we already have a non-zero origin.
+ // This prevents multiple translate calls that negate
+ // one another.
+ const Point aOrigin = rChild.GetMapMode().GetOrigin();
+ if (aOrigin.getX() != 0 || aOrigin.getY() != 0)
+ return;
+
+ // Set map mode, so that callback payloads will contain absolute coordinates instead of relative ones.
+ Point aOffset(rChild.GetOutOffXPixel() - rParent.GetOutOffXPixel(), rChild.GetOutOffYPixel() - rParent.GetOutOffYPixel());
+ if (!rChild.IsMapModeEnabled())
+ {
+ MapMode aMapMode(rChild.GetMapMode());
+ aMapMode.SetMapUnit(MapUnit::MapTwip);
+ aMapMode.SetScaleX(rParent.GetMapMode().GetScaleX());
+ aMapMode.SetScaleY(rParent.GetMapMode().GetScaleY());
+ rChild.SetMapMode(aMapMode);
+ rChild.EnableMapMode();
+ }
+ aOffset = rChild.PixelToLogic(aOffset);
+ MapMode aMapMode(rChild.GetMapMode());
+ aMapMode.SetOrigin(aOffset);
+ aMapMode.SetMapUnit(rParent.GetMapMode().GetMapUnit());
+ rChild.SetMapMode(aMapMode);
+ rChild.EnableMapMode(false);
+}
+
+// EditView never had a central/secure place to react on SelectionChange since
+// Selection was changed in many places, often by not using SetEditSelection()
+// but (mis)using GetEditSelection() and manipulating this non-const return
+// value. Sorted this out now to have such a place, this is needed for safely
+// change/update the Selection visualization for enhanced mechanisms
+void ImpEditView::SelectionChanged()
+{
+ if (EditViewCallbacks* pCallbacks = getEditViewCallbacks())
+ {
+ // use callback to tell about change in selection visualisation
+ pCallbacks->EditViewSelectionChange();
+ }
+}
+
+// This function is also called when a text's font || size is changed. Because its highlight rectangle must be updated.
+void ImpEditView::lokSelectionCallback(const std::optional<tools::PolyPolygon> &pPolyPoly, bool bStartHandleVisible, bool bEndHandleVisible) {
+ VclPtr<vcl::Window> pParent = pOutWin->GetParentWithLOKNotifier();
+ vcl::Region aRegion( *pPolyPoly );
+
+ if (pParent && pParent->GetLOKWindowId() != 0)
+ {
+ const tools::Long nX = pOutWin->GetOutOffXPixel() - pParent->GetOutOffXPixel();
+ const tools::Long nY = pOutWin->GetOutOffYPixel() - pParent->GetOutOffYPixel();
+
+ std::vector<tools::Rectangle> aRectangles;
+ aRegion.GetRegionRectangles(aRectangles);
+
+ std::vector<OString> v;
+ for (tools::Rectangle & rRectangle : aRectangles)
+ {
+ rRectangle = pOutWin->LogicToPixel(rRectangle);
+ rRectangle.Move(nX, nY);
+ v.emplace_back(rRectangle.toString().getStr());
+ }
+ OString sRectangle = comphelper::string::join("; ", v);
+
+ const vcl::ILibreOfficeKitNotifier* pNotifier = pParent->GetLOKNotifier();
+ std::vector<vcl::LOKPayloadItem> aItems;
+ aItems.emplace_back("rectangles", sRectangle);
+ aItems.emplace_back("startHandleVisible", OString::boolean(bStartHandleVisible));
+ aItems.emplace_back("endHandleVisible", OString::boolean(bEndHandleVisible));
+ pNotifier->notifyWindow(pParent->GetLOKWindowId(), "text_selection", aItems);
+ }
+ else if (mpViewShell)
+ {
+ pOutWin->GetOutDev()->Push(vcl::PushFlags::MAPMODE);
+ if (pOutWin->GetMapMode().GetMapUnit() == MapUnit::MapTwip)
+ {
+ // Find the parent that is not right
+ // on top of us to use its offset.
+ vcl::Window* parent = pOutWin->GetParent();
+ while (parent &&
+ parent->GetOutOffXPixel() == pOutWin->GetOutOffXPixel() &&
+ parent->GetOutOffYPixel() == pOutWin->GetOutOffYPixel())
+ {
+ parent = parent->GetParent();
+ }
+
+ if (parent)
+ {
+ lcl_translateTwips(*parent->GetOutDev(), *pOutWin->GetOutDev());
+ }
+ }
+
+ bool bMm100ToTwip = !mpLOKSpecialPositioning &&
+ (pOutWin->GetMapMode().GetMapUnit() == MapUnit::Map100thMM);
+
+ Point aOrigin;
+ if (pOutWin->GetMapMode().GetMapUnit() == MapUnit::MapTwip)
+ // Writer comments: they use editeng, but are separate widgets.
+ aOrigin = pOutWin->GetMapMode().GetOrigin();
+
+ OString sRectangle;
+ OString sRefPoint;
+ if (mpLOKSpecialPositioning)
+ sRefPoint = mpLOKSpecialPositioning->GetRefPoint().toString();
+
+ std::vector<tools::Rectangle> aRectangles;
+ aRegion.GetRegionRectangles(aRectangles);
+
+ if (!aRectangles.empty())
+ {
+ if (pOutWin->IsChart())
+ {
+ const vcl::Window* pViewShellWindow = mpViewShell->GetEditWindowForActiveOLEObj();
+ if (pViewShellWindow && pViewShellWindow->IsAncestorOf(*pOutWin))
+ {
+ Point aOffsetPx = pOutWin->GetOffsetPixelFrom(*pViewShellWindow);
+ Point aLogicOffset = pOutWin->PixelToLogic(aOffsetPx);
+ for (tools::Rectangle& rRect : aRectangles)
+ rRect.Move(aLogicOffset.getX(), aLogicOffset.getY());
+ }
+ }
+
+ std::vector<OString> v;
+ for (tools::Rectangle & rRectangle : aRectangles)
+ {
+ if (bMm100ToTwip)
+ {
+ rRectangle = o3tl::convert(rRectangle, o3tl::Length::mm100, o3tl::Length::twip);
+ }
+ rRectangle.Move(aOrigin.getX(), aOrigin.getY());
+ v.emplace_back(rRectangle.toString().getStr());
+ }
+ sRectangle = comphelper::string::join("; ", v);
+
+ if (mpLOKSpecialPositioning && !sRectangle.isEmpty())
+ sRectangle += ":: " + sRefPoint;
+
+ tools::Rectangle& rStart = aRectangles.front();
+ tools::Rectangle aStart(rStart.Left(), rStart.Top(), rStart.Left() + 1, rStart.Bottom());
+
+ OString aPayload = aStart.toString();
+ if (mpLOKSpecialPositioning)
+ aPayload += ":: " + sRefPoint;
+
+ mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_START, aPayload);
+
+ tools::Rectangle& rEnd = aRectangles.back();
+ tools::Rectangle aEnd(rEnd.Right() - 1, rEnd.Top(), rEnd.Right(), rEnd.Bottom());
+
+ aPayload = aEnd.toString();
+ if (mpLOKSpecialPositioning)
+ aPayload += ":: " + sRefPoint;
+
+ mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION_END, aPayload);
+ }
+
+ if (mpOtherShell)
+ {
+ // Another shell wants to know about our existing selection.
+ if (mpViewShell != mpOtherShell)
+ mpViewShell->NotifyOtherView(mpOtherShell, LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection"_ostr, sRectangle);
+ }
+ else
+ {
+ mpViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, sRectangle);
+ mpViewShell->NotifyOtherViews(LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection"_ostr, sRectangle);
+ }
+
+ pOutWin->GetOutDev()->Pop();
+ }
+}
+
+// renamed from DrawSelection to DrawSelectionXOR to better reflect what this
+// method was used for: Paint Selection in XOR, change it and again paint it in XOR.
+// This can be safely assumed due to the EditView only being capable of painting the
+// selection in XOR until today.
+// This also means that all places calling DrawSelectionXOR are thoroughly weighted
+// and chosen to make this fragile XOR-paint water-proof and thus contain some
+// information in this sense.
+// Someone thankfully expanded it to collect the SelectionRectangles when called with
+// the Region*, see GetSelectionRectangles below.
+void ImpEditView::DrawSelectionXOR( EditSelection aTmpSel, vcl::Region* pRegion, OutputDevice* pTargetDevice )
+{
+ if (getEditViewCallbacks() && !pRegion && !comphelper::LibreOfficeKit::isActive())
+ {
+ // we are done, do *not* visualize self
+ // CAUTION: do not use when comphelper::LibreOfficeKit::isActive()
+ // due to event stuff triggered below. That *should* probably be moved
+ // to SelectionChanged() which exists now, but I do not know enough about
+ // that stuff to do it
+ return;
+ }
+
+ if ( eSelectionMode == EESelectionMode::Hidden )
+ return;
+
+ // It must be ensured before rendering the selection, that the contents of
+ // the window is completely valid! Must be here so that in any case if
+ // empty, then later on two-Paint Events! Must be done even before the
+ // query from bUpdate, if after Invalidate paints still in the queue,
+ // but someone switches the update mode!
+
+ // pRegion: When not NULL, then only calculate Region.
+
+ OutputDevice& rTarget = pTargetDevice ? *pTargetDevice : GetOutputDevice();
+ bool bClipRegion = rTarget.IsClipRegion();
+ vcl::Region aOldRegion = rTarget.GetClipRegion();
+
+ std::optional<tools::PolyPolygon> pPolyPoly;
+
+ if ( !pRegion && !comphelper::LibreOfficeKit::isActive())
+ {
+ if ( !pEditEngine->pImpEditEngine->IsUpdateLayout() )
+ return;
+ if ( pEditEngine->pImpEditEngine->IsInUndo() )
+ return;
+
+ if ( !aTmpSel.HasRange() )
+ return;
+
+ // aTmpOutArea: if OutputArea > Paper width and
+ // Text > Paper width ( over large fields )
+ tools::Rectangle aTmpOutArea( aOutArea );
+ if ( aTmpOutArea.GetWidth() > pEditEngine->pImpEditEngine->GetPaperSize().Width() )
+ aTmpOutArea.SetRight( aTmpOutArea.Left() + pEditEngine->pImpEditEngine->GetPaperSize().Width() );
+ rTarget.IntersectClipRegion( aTmpOutArea );
+
+ if (pOutWin && pOutWin->GetCursor())
+ pOutWin->GetCursor()->Hide();
+ }
+
+ if (comphelper::LibreOfficeKit::isActive() || pRegion)
+ pPolyPoly = tools::PolyPolygon();
+
+ DBG_ASSERT( !pEditEngine->IsIdleFormatterActive(), "DrawSelectionXOR: Not formatted!" );
+ aTmpSel.Adjust( pEditEngine->GetEditDoc() );
+
+ ContentNode* pStartNode = aTmpSel.Min().GetNode();
+ ContentNode* pEndNode = aTmpSel.Max().GetNode();
+ const sal_Int32 nStartPara = pEditEngine->GetEditDoc().GetPos(pStartNode);
+ const sal_Int32 nEndPara = pEditEngine->GetEditDoc().GetPos(pEndNode);
+ if (nStartPara == EE_PARA_NOT_FOUND || nEndPara == EE_PARA_NOT_FOUND)
+ return;
+
+ bool bStartHandleVisible = false;
+ bool bEndHandleVisible = false;
+ bool bLOKCalcRTL = mpLOKSpecialPositioning &&
+ (mpLOKSpecialPositioning->IsLayoutRTL() || pEditEngine->IsRightToLeft(nStartPara));
+
+ auto DrawHighlight = [&, nStartLine = sal_Int32(0), nEndLine = sal_Int32(0)](
+ const ImpEditEngine::LineAreaInfo& rInfo) mutable {
+ if (!rInfo.pLine) // Begin of ParaPortion
+ {
+ if (rInfo.nPortion < nStartPara)
+ return ImpEditEngine::CallbackResult::SkipThisPortion;
+ if (rInfo.nPortion > nEndPara)
+ return ImpEditEngine::CallbackResult::Stop;
+ DBG_ASSERT(!rInfo.rPortion.IsInvalid(), "Portion in Selection not formatted!");
+ if (rInfo.rPortion.IsInvalid())
+ return ImpEditEngine::CallbackResult::SkipThisPortion;
+
+ if (rInfo.nPortion == nStartPara)
+ nStartLine = rInfo.rPortion.GetLines().FindLine(aTmpSel.Min().GetIndex(), false);
+ else
+ nStartLine = 0;
+
+ if (rInfo.nPortion == nEndPara)
+ nEndLine = rInfo.rPortion.GetLines().FindLine(aTmpSel.Max().GetIndex(), true);
+ else
+ nEndLine = rInfo.rPortion.GetLines().Count() - 1;
+ }
+ else // This is a correct ParaPortion
+ {
+ if (rInfo.nLine < nStartLine)
+ return ImpEditEngine::CallbackResult::Continue;
+ if (rInfo.nLine > nEndLine)
+ return ImpEditEngine::CallbackResult::SkipThisPortion;
+
+ bool bPartOfLine = false;
+ sal_Int32 nStartIndex = rInfo.pLine->GetStart();
+ sal_Int32 nEndIndex = rInfo.pLine->GetEnd();
+ if ((rInfo.nPortion == nStartPara) && (rInfo.nLine == nStartLine)
+ && (nStartIndex != aTmpSel.Min().GetIndex()))
+ {
+ nStartIndex = aTmpSel.Min().GetIndex();
+ bPartOfLine = true;
+ }
+ if ((rInfo.nPortion == nEndPara) && (rInfo.nLine == nEndLine)
+ && (nEndIndex != aTmpSel.Max().GetIndex()))
+ {
+ nEndIndex = aTmpSel.Max().GetIndex();
+ bPartOfLine = true;
+ }
+
+ // Can happen if at the beginning of a wrapped line.
+ if (nEndIndex < nStartIndex)
+ nEndIndex = nStartIndex;
+
+ tools::Rectangle aTmpRect(pEditEngine->pImpEditEngine->GetEditCursor(
+ &rInfo.rPortion, rInfo.pLine, nStartIndex, GetCursorFlags::NONE));
+ const Size aLineOffset = pEditEngine->pImpEditEngine->getTopLeftDocOffset(rInfo.aArea);
+ aTmpRect.Move(0, aLineOffset.Height());
+
+ // Only paint if in the visible range ...
+ if (aTmpRect.Top() > GetVisDocBottom())
+ return ImpEditEngine::CallbackResult::Continue;
+
+ if (aTmpRect.Bottom() < GetVisDocTop())
+ return ImpEditEngine::CallbackResult::Continue;
+
+ if ((rInfo.nPortion == nStartPara) && (rInfo.nLine == nStartLine))
+ bStartHandleVisible = true;
+ if ((rInfo.nPortion == nEndPara) && (rInfo.nLine == nEndLine))
+ bEndHandleVisible = true;
+
+ // Now that we have Bidi, the first/last index doesn't have to be the 'most outside' position
+ if (!bPartOfLine)
+ {
+ Range aLineXPosStartEnd
+ = pEditEngine->GetLineXPosStartEnd(&rInfo.rPortion, rInfo.pLine);
+ aTmpRect.SetLeft(aLineXPosStartEnd.Min());
+ aTmpRect.SetRight(aLineXPosStartEnd.Max());
+ aTmpRect.Move(aLineOffset.Width(), 0);
+ ImplDrawHighlightRect(rTarget, aTmpRect.TopLeft(), aTmpRect.BottomRight(),
+ pPolyPoly ? &*pPolyPoly : nullptr, bLOKCalcRTL);
+ }
+ else
+ {
+ sal_Int32 nTmpStartIndex = nStartIndex;
+ sal_Int32 nWritingDirStart, nTmpEndIndex;
+
+ while (nTmpStartIndex < nEndIndex)
+ {
+ pEditEngine->pImpEditEngine->GetRightToLeft(rInfo.nPortion, nTmpStartIndex + 1,
+ &nWritingDirStart, &nTmpEndIndex);
+ if (nTmpEndIndex > nEndIndex)
+ nTmpEndIndex = nEndIndex;
+
+ DBG_ASSERT(nTmpEndIndex > nTmpStartIndex, "DrawSelectionXOR, Start >= End?");
+
+ tools::Long nX1
+ = pEditEngine->GetXPos(&rInfo.rPortion, rInfo.pLine, nTmpStartIndex, true);
+ tools::Long nX2
+ = pEditEngine->GetXPos(&rInfo.rPortion, rInfo.pLine, nTmpEndIndex);
+
+ aTmpRect.SetLeft(std::min(nX1, nX2));
+ aTmpRect.SetRight(std::max(nX1, nX2));
+ aTmpRect.Move(aLineOffset.Width(), 0);
+
+ ImplDrawHighlightRect(rTarget, aTmpRect.TopLeft(), aTmpRect.BottomRight(),
+ pPolyPoly ? &*pPolyPoly : nullptr, bLOKCalcRTL);
+ nTmpStartIndex = nTmpEndIndex;
+ }
+ }
+ }
+ return ImpEditEngine::CallbackResult::Continue;
+ };
+ pEditEngine->pImpEditEngine->IterateLineAreas(DrawHighlight, ImpEditEngine::IterFlag::none);
+
+ if (comphelper::LibreOfficeKit::isActive() && mpViewShell && pOutWin)
+ lokSelectionCallback(pPolyPoly, bStartHandleVisible, bEndHandleVisible);
+
+ if (pRegion || comphelper::LibreOfficeKit::isActive())
+ {
+ if (pRegion)
+ *pRegion = vcl::Region( *pPolyPoly );
+ pPolyPoly.reset();
+ }
+ else
+ {
+ if (pOutWin && pOutWin->GetCursor())
+ pOutWin->GetCursor()->Show();
+
+ if (bClipRegion)
+ rTarget.SetClipRegion(aOldRegion);
+ else
+ rTarget.SetClipRegion();
+ }
+}
+
+void ImpEditView::GetSelectionRectangles(EditSelection aTmpSel, std::vector<tools::Rectangle>& rLogicRects)
+{
+ vcl::Region aRegion;
+ DrawSelectionXOR(aTmpSel, &aRegion);
+ aRegion.GetRegionRectangles(rLogicRects);
+}
+
+void ImpEditView::ImplDrawHighlightRect( OutputDevice& rTarget, const Point& rDocPosTopLeft, const Point& rDocPosBottomRight, tools::PolyPolygon* pPolyPoly, bool bLOKCalcRTL )
+{
+ if ( rDocPosTopLeft.X() == rDocPosBottomRight.X() )
+ return;
+
+ if (mpLOKSpecialPositioning && pPolyPoly)
+ {
+ MapUnit eDevUnit = rTarget.GetMapMode().GetMapUnit();
+ tools::Rectangle aSelRect(rDocPosTopLeft, rDocPosBottomRight);
+ aSelRect = GetWindowPos(aSelRect);
+ Point aRefPointLogical = GetOutputArea().TopLeft();
+ // Get the relative coordinates w.r.t refpoint in display units.
+ aSelRect.Move(-aRefPointLogical.X(), -aRefPointLogical.Y());
+ if (bLOKCalcRTL)
+ {
+ tools::Long nMirrorW = GetOutputArea().GetWidth();
+ tools::Long nLeft = aSelRect.Left(), nRight = aSelRect.Right();
+ aSelRect.SetLeft(nMirrorW - nRight);
+ aSelRect.SetRight(nMirrorW - nLeft);
+ }
+ // Convert from display unit to twips.
+ aSelRect = OutputDevice::LogicToLogic(aSelRect, MapMode(eDevUnit), MapMode(MapUnit::MapTwip));
+
+ tools::Polygon aTmpPoly(4);
+ aTmpPoly[0] = aSelRect.TopLeft();
+ aTmpPoly[1] = aSelRect.TopRight();
+ aTmpPoly[2] = aSelRect.BottomRight();
+ aTmpPoly[3] = aSelRect.BottomLeft();
+ pPolyPoly->Insert(aTmpPoly);
+ return;
+ }
+
+ bool bPixelMode = rTarget.GetMapMode().GetMapUnit() == MapUnit::MapPixel;
+
+ Point aPnt1( GetWindowPos( rDocPosTopLeft ) );
+ Point aPnt2( GetWindowPos( rDocPosBottomRight ) );
+
+ if ( !IsVertical() )
+ {
+ lcl_AlignToPixel(aPnt1, rTarget, +1, 0);
+ lcl_AlignToPixel(aPnt2, rTarget, 0, (bPixelMode ? 0 : -1));
+ }
+ else
+ {
+ lcl_AlignToPixel(aPnt1, rTarget, 0, +1 );
+ lcl_AlignToPixel(aPnt2, rTarget, (bPixelMode ? 0 : +1), 0);
+ }
+
+ tools::Rectangle aRect( aPnt1, aPnt2 );
+ if ( pPolyPoly )
+ {
+ tools::Polygon aTmpPoly( 4 );
+ aTmpPoly[0] = aRect.TopLeft();
+ aTmpPoly[1] = aRect.TopRight();
+ aTmpPoly[2] = aRect.BottomRight();
+ aTmpPoly[3] = aRect.BottomLeft();
+ pPolyPoly->Insert( aTmpPoly );
+ }
+ else
+ {
+ vcl::Window* pWindow = rTarget.GetOwnerWindow();
+
+ if (pWindow)
+ {
+ pWindow->GetOutDev()->Invert( aRect );
+ }
+ else
+ {
+ rTarget.Push(vcl::PushFlags::LINECOLOR|vcl::PushFlags::FILLCOLOR|vcl::PushFlags::RASTEROP);
+ rTarget.SetLineColor();
+ rTarget.SetFillColor(COL_BLACK);
+ rTarget.SetRasterOp(RasterOp::Invert);
+ rTarget.DrawRect(aRect);
+ rTarget.Pop();
+ }
+ }
+}
+
+
+bool ImpEditView::IsVertical() const
+{
+ return pEditEngine->pImpEditEngine->IsEffectivelyVertical();
+}
+
+bool ImpEditView::IsTopToBottom() const
+{
+ return pEditEngine->pImpEditEngine->IsTopToBottom();
+}
+
+tools::Rectangle ImpEditView::GetVisDocArea() const
+{
+ return tools::Rectangle( GetVisDocLeft(), GetVisDocTop(), GetVisDocRight(), GetVisDocBottom() );
+}
+
+Point ImpEditView::GetDocPos( const Point& rWindowPos ) const
+{
+ // Window Position => Position Document
+ Point aPoint;
+
+ if ( !pEditEngine->pImpEditEngine->IsEffectivelyVertical() )
+ {
+ aPoint.setX( rWindowPos.X() - aOutArea.Left() + GetVisDocLeft() );
+ aPoint.setY( rWindowPos.Y() - aOutArea.Top() + GetVisDocTop() );
+ }
+ else
+ {
+ if (pEditEngine->pImpEditEngine->IsTopToBottom())
+ {
+ aPoint.setX( rWindowPos.Y() - aOutArea.Top() + GetVisDocLeft() );
+ aPoint.setY( aOutArea.Right() - rWindowPos.X() + GetVisDocTop() );
+ }
+ else
+ {
+ aPoint.setX( aOutArea.Bottom() - rWindowPos.Y() + GetVisDocLeft() );
+ aPoint.setY( rWindowPos.X() - aOutArea.Left() + GetVisDocTop() );
+ }
+ }
+
+ return aPoint;
+}
+
+Point ImpEditView::GetWindowPos( const Point& rDocPos ) const
+{
+ // Document position => window position
+ Point aPoint;
+
+ if ( !pEditEngine->pImpEditEngine->IsEffectivelyVertical() )
+ {
+ aPoint.setX( rDocPos.X() + aOutArea.Left() - GetVisDocLeft() );
+ aPoint.setY( rDocPos.Y() + aOutArea.Top() - GetVisDocTop() );
+ }
+ else
+ {
+ if (pEditEngine->pImpEditEngine->IsTopToBottom())
+ {
+ aPoint.setX( aOutArea.Right() - rDocPos.Y() + GetVisDocTop() );
+ aPoint.setY( rDocPos.X() + aOutArea.Top() - GetVisDocLeft() );
+ }
+ else
+ {
+ aPoint.setX( aOutArea.Left() + rDocPos.Y() - GetVisDocTop() );
+ aPoint.setY( aOutArea.Bottom() - rDocPos.X() + GetVisDocLeft() );
+ }
+ }
+
+ return aPoint;
+}
+
+tools::Rectangle ImpEditView::GetWindowPos( const tools::Rectangle& rDocRect ) const
+{
+ // Document position => window position
+ Point aPos( GetWindowPos( rDocRect.TopLeft() ) );
+ Size aSz = rDocRect.GetSize();
+ tools::Rectangle aRect;
+ if ( !pEditEngine->pImpEditEngine->IsEffectivelyVertical() )
+ {
+ aRect = tools::Rectangle( aPos, aSz );
+ }
+ else
+ {
+ Point aNewPos( aPos.X()-aSz.Height(), aPos.Y() );
+ // coverity[swapped_arguments : FALSE] - this is in the correct order
+ aRect = tools::Rectangle( aNewPos, Size( aSz.Height(), aSz.Width() ) );
+ }
+ return aRect;
+}
+
+void ImpEditView::SetSelectionMode( EESelectionMode eNewMode )
+{
+ if ( eSelectionMode != eNewMode )
+ {
+ DrawSelectionXOR();
+ eSelectionMode = eNewMode;
+ DrawSelectionXOR(); // redraw
+ }
+}
+
+OutputDevice& ImpEditView::GetOutputDevice() const
+{
+ if (EditViewCallbacks* pCallbacks = getEditViewCallbacks())
+ return pCallbacks->EditViewOutputDevice();
+ return *pOutWin->GetOutDev();
+}
+
+weld::Widget* ImpEditView::GetPopupParent(tools::Rectangle& rRect) const
+{
+ if (EditViewCallbacks* pCallbacks = getEditViewCallbacks())
+ {
+ weld::Widget* pParent = pCallbacks->EditViewPopupParent();
+ if (pParent)
+ return pParent;
+ }
+ return weld::GetPopupParent(*pOutWin, rRect);
+}
+
+void ImpEditView::SetOutputArea( const tools::Rectangle& rRect )
+{
+ const OutputDevice& rOutDev = GetOutputDevice();
+ // should be better be aligned on pixels!
+ tools::Rectangle aNewRect(rOutDev.LogicToPixel(rRect));
+ aNewRect = rOutDev.PixelToLogic(aNewRect);
+ aOutArea = aNewRect;
+ if ( !aOutArea.IsWidthEmpty() && aOutArea.Right() < aOutArea.Left() )
+ aOutArea.SetRight( aOutArea.Left() );
+ if ( !aOutArea.IsHeightEmpty() && aOutArea.Bottom() < aOutArea.Top() )
+ aOutArea.SetBottom( aOutArea.Top() );
+
+ SetScrollDiffX( static_cast<sal_uInt16>(aOutArea.GetWidth()) * 2 / 10 );
+}
+
+namespace {
+
+tools::Rectangle lcl_negateRectX(const tools::Rectangle& rRect)
+{
+ return tools::Rectangle(-rRect.Right(), rRect.Top(), -rRect.Left(), rRect.Bottom());
+}
+
+}
+
+void ImpEditView::InvalidateAtWindow(const tools::Rectangle& rRect)
+{
+ if (EditViewCallbacks* pCallbacks = getEditViewCallbacks())
+ {
+ // do not invalidate and trigger a global repaint, but forward
+ // the need for change to the applied EditViewCallback, can e.g.
+ // be used to visualize the active edit text in an OverlayObject
+ pCallbacks->EditViewInvalidate(mbNegativeX ? lcl_negateRectX(rRect) : rRect);
+ }
+ else
+ {
+ // classic mode: invalidate and trigger full repaint
+ // of the changed area
+ GetWindow()->Invalidate(mbNegativeX ? lcl_negateRectX(rRect) : rRect);
+ }
+}
+
+void ImpEditView::ResetOutputArea( const tools::Rectangle& rRect )
+{
+ // remember old out area
+ const tools::Rectangle aOldArea(aOutArea);
+
+ // apply new one
+ SetOutputArea(rRect);
+
+ // invalidate surrounding areas if update is true
+ if(aOldArea.IsEmpty() || !pEditEngine->pImpEditEngine->IsUpdateLayout())
+ return;
+
+ // #i119885# use grown area if needed; do when getting bigger OR smaller
+ const sal_Int32 nMore(DoInvalidateMore() ? GetOutputDevice().PixelToLogic(Size(nInvMore, 0)).Width() : 0);
+
+ if(aOldArea.Left() > aOutArea.Left())
+ {
+ const tools::Rectangle aRect(aOutArea.Left() - nMore, aOldArea.Top() - nMore, aOldArea.Left(), aOldArea.Bottom() + nMore);
+ InvalidateAtWindow(aRect);
+ }
+ else if(aOldArea.Left() < aOutArea.Left())
+ {
+ const tools::Rectangle aRect(aOldArea.Left() - nMore, aOldArea.Top() - nMore, aOutArea.Left(), aOldArea.Bottom() + nMore);
+ InvalidateAtWindow(aRect);
+ }
+
+ if(aOldArea.Right() > aOutArea.Right())
+ {
+ const tools::Rectangle aRect(aOutArea.Right(), aOldArea.Top() - nMore, aOldArea.Right() + nMore, aOldArea.Bottom() + nMore);
+ InvalidateAtWindow(aRect);
+ }
+ else if(aOldArea.Right() < aOutArea.Right())
+ {
+ const tools::Rectangle aRect(aOldArea.Right(), aOldArea.Top() - nMore, aOutArea.Right() + nMore, aOldArea.Bottom() + nMore);
+ InvalidateAtWindow(aRect);
+ }
+
+ if(aOldArea.Top() > aOutArea.Top())
+ {
+ const tools::Rectangle aRect(aOldArea.Left() - nMore, aOutArea.Top() - nMore, aOldArea.Right() + nMore, aOldArea.Top());
+ InvalidateAtWindow(aRect);
+ }
+ else if(aOldArea.Top() < aOutArea.Top())
+ {
+ const tools::Rectangle aRect(aOldArea.Left() - nMore, aOldArea.Top() - nMore, aOldArea.Right() + nMore, aOutArea.Top());
+ InvalidateAtWindow(aRect);
+ }
+
+ if(aOldArea.Bottom() > aOutArea.Bottom())
+ {
+ const tools::Rectangle aRect(aOldArea.Left() - nMore, aOutArea.Bottom(), aOldArea.Right() + nMore, aOldArea.Bottom() + nMore);
+ InvalidateAtWindow(aRect);
+ }
+ else if(aOldArea.Bottom() < aOutArea.Bottom())
+ {
+ const tools::Rectangle aRect(aOldArea.Left() - nMore, aOldArea.Bottom(), aOldArea.Right() + nMore, aOutArea.Bottom() + nMore);
+ InvalidateAtWindow(aRect);
+ }
+}
+
+void ImpEditView::RecalcOutputArea()
+{
+ Point aNewTopLeft( aOutArea.TopLeft() );
+ Size aNewSz( aOutArea.GetSize() );
+
+ // X:
+ if ( DoAutoWidth() )
+ {
+ if ( pEditEngine->pImpEditEngine->GetStatus().AutoPageWidth() )
+ aNewSz.setWidth( pEditEngine->pImpEditEngine->GetPaperSize().Width() );
+ switch ( eAnchorMode )
+ {
+ case EEAnchorMode::TopLeft:
+ case EEAnchorMode::VCenterLeft:
+ case EEAnchorMode::BottomLeft:
+ {
+ aNewTopLeft.setX( aAnchorPoint.X() );
+ }
+ break;
+ case EEAnchorMode::TopHCenter:
+ case EEAnchorMode::VCenterHCenter:
+ case EEAnchorMode::BottomHCenter:
+ {
+ aNewTopLeft.setX( aAnchorPoint.X() - aNewSz.Width() / 2 );
+ }
+ break;
+ case EEAnchorMode::TopRight:
+ case EEAnchorMode::VCenterRight:
+ case EEAnchorMode::BottomRight:
+ {
+ aNewTopLeft.setX( aAnchorPoint.X() - aNewSz.Width() - 1 );
+ }
+ break;
+ }
+ }
+
+ // Y:
+ if ( DoAutoHeight() )
+ {
+ if ( pEditEngine->pImpEditEngine->GetStatus().AutoPageHeight() )
+ aNewSz.setHeight( pEditEngine->pImpEditEngine->GetPaperSize().Height() );
+ switch ( eAnchorMode )
+ {
+ case EEAnchorMode::TopLeft:
+ case EEAnchorMode::TopHCenter:
+ case EEAnchorMode::TopRight:
+ {
+ aNewTopLeft.setY( aAnchorPoint.Y() );
+ }
+ break;
+ case EEAnchorMode::VCenterLeft:
+ case EEAnchorMode::VCenterHCenter:
+ case EEAnchorMode::VCenterRight:
+ {
+ aNewTopLeft.setY( aAnchorPoint.Y() - aNewSz.Height() / 2 );
+ }
+ break;
+ case EEAnchorMode::BottomLeft:
+ case EEAnchorMode::BottomHCenter:
+ case EEAnchorMode::BottomRight:
+ {
+ aNewTopLeft.setY( aAnchorPoint.Y() - aNewSz.Height() - 1 );
+ }
+ break;
+ }
+ }
+ ResetOutputArea( tools::Rectangle( aNewTopLeft, aNewSz ) );
+}
+
+void ImpEditView::SetAnchorMode( EEAnchorMode eMode )
+{
+ eAnchorMode = eMode;
+ CalcAnchorPoint();
+}
+
+void ImpEditView::CalcAnchorPoint()
+{
+ // GetHeight() and GetWidth() -1, because rectangle calculation not preferred.
+
+ // X:
+ switch ( eAnchorMode )
+ {
+ case EEAnchorMode::TopLeft:
+ case EEAnchorMode::VCenterLeft:
+ case EEAnchorMode::BottomLeft:
+ {
+ aAnchorPoint.setX( aOutArea.Left() );
+ }
+ break;
+ case EEAnchorMode::TopHCenter:
+ case EEAnchorMode::VCenterHCenter:
+ case EEAnchorMode::BottomHCenter:
+ {
+ aAnchorPoint.setX( aOutArea.Left() + (aOutArea.GetWidth()-1) / 2 );
+ }
+ break;
+ case EEAnchorMode::TopRight:
+ case EEAnchorMode::VCenterRight:
+ case EEAnchorMode::BottomRight:
+ {
+ aAnchorPoint.setX( aOutArea.Right() );
+ }
+ break;
+ }
+
+ // Y:
+ switch ( eAnchorMode )
+ {
+ case EEAnchorMode::TopLeft:
+ case EEAnchorMode::TopHCenter:
+ case EEAnchorMode::TopRight:
+ {
+ aAnchorPoint.setY( aOutArea.Top() );
+ }
+ break;
+ case EEAnchorMode::VCenterLeft:
+ case EEAnchorMode::VCenterHCenter:
+ case EEAnchorMode::VCenterRight:
+ {
+ aAnchorPoint.setY( aOutArea.Top() + (aOutArea.GetHeight()-1) / 2 );
+ }
+ break;
+ case EEAnchorMode::BottomLeft:
+ case EEAnchorMode::BottomHCenter:
+ case EEAnchorMode::BottomRight:
+ {
+ aAnchorPoint.setY( aOutArea.Bottom() - 1 );
+ }
+ break;
+ }
+}
+
+namespace
+{
+
+// For building JSON message to be sent to Online
+boost::property_tree::ptree getHyperlinkPropTree(const OUString& sText, const OUString& sLink)
+{
+ boost::property_tree::ptree aTree;
+ aTree.put("text", sText);
+ aTree.put("link", sLink);
+ return aTree;
+}
+
+} // End of anon namespace
+
+tools::Rectangle ImpEditView::ImplGetEditCursor(EditPaM& aPaM, GetCursorFlags nShowCursorFlags, sal_Int32& nTextPortionStart,
+ const ParaPortion* pParaPortion) const
+{
+ tools::Rectangle aEditCursor = pEditEngine->pImpEditEngine->PaMtoEditCursor( aPaM, nShowCursorFlags );
+ if ( !IsInsertMode() && !aEditSelection.HasRange() )
+ {
+ if ( aPaM.GetNode()->Len() && ( aPaM.GetIndex() < aPaM.GetNode()->Len() ) )
+ {
+ // If we are behind a portion, and the next portion has other direction, we must change position...
+ aEditCursor.SetLeft( pEditEngine->pImpEditEngine->PaMtoEditCursor( aPaM, GetCursorFlags::TextOnly|GetCursorFlags::PreferPortionStart ).Left() );
+ aEditCursor.SetRight( aEditCursor.Left() );
+
+ sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTextPortionStart, true );
+ const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion];
+ if ( rTextPortion.GetKind() == PortionKind::TAB )
+ {
+ aEditCursor.AdjustRight(rTextPortion.GetSize().Width() );
+ }
+ else
+ {
+ EditPaM aNext = pEditEngine->CursorRight( aPaM );
+ tools::Rectangle aTmpRect = pEditEngine->pImpEditEngine->PaMtoEditCursor( aNext, GetCursorFlags::TextOnly );
+ if ( aTmpRect.Top() != aEditCursor.Top() )
+ aTmpRect = pEditEngine->pImpEditEngine->PaMtoEditCursor( aNext, GetCursorFlags::TextOnly|GetCursorFlags::EndOfLine );
+ aEditCursor.SetRight( aTmpRect.Left() );
+ }
+ }
+ }
+
+ tools::Long nMaxHeight = !IsVertical() ? aOutArea.GetHeight() : aOutArea.GetWidth();
+ if ( aEditCursor.GetHeight() > nMaxHeight )
+ {
+ aEditCursor.SetBottom( aEditCursor.Top() + nMaxHeight - 1 );
+ }
+
+ return aEditCursor;
+}
+
+tools::Rectangle ImpEditView::GetEditCursor() const
+{
+ EditPaM aPaM( aEditSelection.Max() );
+
+ sal_Int32 nTextPortionStart = 0;
+ sal_Int32 nPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() );
+ if (nPara == EE_PARA_NOT_FOUND) // #i94322
+ return tools::Rectangle();
+
+ const ParaPortion* pParaPortion = pEditEngine->GetParaPortions()[nPara];
+
+ GetCursorFlags nShowCursorFlags = nExtraCursorFlags | GetCursorFlags::TextOnly;
+
+ // Use CursorBidiLevel 0/1 in meaning of
+ // 0: prefer portion end, normal mode
+ // 1: prefer portion start
+
+ if ( ( GetCursorBidiLevel() != CURSOR_BIDILEVEL_DONTKNOW ) && GetCursorBidiLevel() )
+ {
+ nShowCursorFlags |= GetCursorFlags::PreferPortionStart;
+ }
+
+ return ImplGetEditCursor(aPaM, nShowCursorFlags, nTextPortionStart, pParaPortion);
+}
+
+void ImpEditView::ShowCursor( bool bGotoCursor, bool bForceVisCursor )
+{
+ // No ShowCursor in an empty View ...
+ if (aOutArea.IsEmpty())
+ return;
+ if ( ( aOutArea.Left() >= aOutArea.Right() ) && ( aOutArea.Top() >= aOutArea.Bottom() ) )
+ return;
+
+ pEditEngine->CheckIdleFormatter();
+ if (!pEditEngine->IsFormatted())
+ pEditEngine->pImpEditEngine->FormatDoc();
+
+ // For some reasons I end up here during the formatting, if the Outliner
+ // is initialized in Paint, because no SetPool();
+ if ( pEditEngine->pImpEditEngine->IsFormatting() )
+ return;
+ if ( !pEditEngine->pImpEditEngine->IsUpdateLayout() )
+ return;
+ if ( pEditEngine->pImpEditEngine->IsInUndo() )
+ return;
+
+ if (pOutWin && pOutWin->GetCursor() != GetCursor())
+ pOutWin->SetCursor(GetCursor());
+
+ EditPaM aPaM( aEditSelection.Max() );
+
+ sal_Int32 nTextPortionStart = 0;
+ sal_Int32 nPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() );
+ if (nPara == EE_PARA_NOT_FOUND) // #i94322
+ return;
+
+ const ParaPortion* pParaPortion = pEditEngine->GetParaPortions()[nPara];
+
+ GetCursorFlags nShowCursorFlags = nExtraCursorFlags | GetCursorFlags::TextOnly;
+
+ // Use CursorBidiLevel 0/1 in meaning of
+ // 0: prefer portion end, normal mode
+ // 1: prefer portion start
+
+ if ( ( GetCursorBidiLevel() != CURSOR_BIDILEVEL_DONTKNOW ) && GetCursorBidiLevel() )
+ {
+ nShowCursorFlags |= GetCursorFlags::PreferPortionStart;
+ }
+
+ tools::Rectangle aEditCursor = ImplGetEditCursor(aPaM, nShowCursorFlags, nTextPortionStart, pParaPortion);
+
+ if ( bGotoCursor ) // && (!pEditEngine->pImpEditEngine->GetStatus().AutoPageSize() ) )
+ {
+ // check if scrolling is necessary...
+ // if scrolling, then update () and Scroll ()!
+ tools::Long nDocDiffX = 0;
+ tools::Long nDocDiffY = 0;
+
+ tools::Rectangle aTmpVisArea( GetVisDocArea() );
+ // aTmpOutArea: if OutputArea > Paper width and
+ // Text > Paper width ( over large fields )
+ tools::Long nMaxTextWidth = !IsVertical() ? pEditEngine->pImpEditEngine->GetPaperSize().Width() : pEditEngine->pImpEditEngine->GetPaperSize().Height();
+ if ( aTmpVisArea.GetWidth() > nMaxTextWidth )
+ aTmpVisArea.SetRight( aTmpVisArea.Left() + nMaxTextWidth );
+
+ if ( aEditCursor.Bottom() > aTmpVisArea.Bottom() )
+ { // Scroll up, here positive
+ nDocDiffY = aEditCursor.Bottom() - aTmpVisArea.Bottom();
+ }
+ else if ( aEditCursor.Top() < aTmpVisArea.Top() )
+ { // Scroll down, here negative
+ nDocDiffY = aEditCursor.Top() - aTmpVisArea.Top();
+ }
+
+ if ( aEditCursor.Right() > aTmpVisArea.Right() )
+ {
+ // Scroll left, positive
+ nDocDiffX = aEditCursor.Right() - aTmpVisArea.Right();
+ // Can it be a little more?
+ if ( aEditCursor.Right() < ( nMaxTextWidth - GetScrollDiffX() ) )
+ nDocDiffX += GetScrollDiffX();
+ else
+ {
+ tools::Long n = nMaxTextWidth - aEditCursor.Right();
+ // If MapMode != RefMapMode then the EditCursor can go beyond
+ // the paper width!
+ nDocDiffX += ( n > 0 ? n : -n );
+ }
+ }
+ else if ( aEditCursor.Left() < aTmpVisArea.Left() )
+ {
+ // Scroll right, negative:
+ nDocDiffX = aEditCursor.Left() - aTmpVisArea.Left();
+ // Can it be a little more?
+ if ( aEditCursor.Left() > ( - static_cast<tools::Long>(GetScrollDiffX()) ) )
+ nDocDiffX -= GetScrollDiffX();
+ else
+ nDocDiffX -= aEditCursor.Left();
+ }
+ if ( aPaM.GetIndex() == 0 ) // Olli needed for the Outliner
+ {
+ // But make sure that the cursor is not leaving visible area
+ // because of this!
+ if ( aEditCursor.Left() < aTmpVisArea.GetWidth() )
+ {
+ nDocDiffX = -aTmpVisArea.Left();
+ }
+ }
+
+ if ( nDocDiffX | nDocDiffY )
+ {
+ tools::Long nDiffX = !IsVertical() ? nDocDiffX : (IsTopToBottom() ? -nDocDiffY : nDocDiffY);
+ tools::Long nDiffY = !IsVertical() ? nDocDiffY : (IsTopToBottom() ? nDocDiffX : -nDocDiffX);
+
+ if ( nDiffX )
+ pEditEngine->GetInternalEditStatus().GetStatusWord() = pEditEngine->GetInternalEditStatus().GetStatusWord() | EditStatusFlags::HSCROLL;
+ if ( nDiffY )
+ pEditEngine->GetInternalEditStatus().GetStatusWord() = pEditEngine->GetInternalEditStatus().GetStatusWord() | EditStatusFlags::VSCROLL;
+ Scroll( -nDiffX, -nDiffY );
+ pEditEngine->pImpEditEngine->DelayedCallStatusHdl();
+ }
+ }
+
+ // Cursor may trim a little ...
+ if ( ( aEditCursor.Bottom() > GetVisDocTop() ) &&
+ ( aEditCursor.Top() < GetVisDocBottom() ) )
+ {
+ if ( aEditCursor.Bottom() > GetVisDocBottom() )
+ aEditCursor.SetBottom( GetVisDocBottom() );
+ if ( aEditCursor.Top() < GetVisDocTop() )
+ aEditCursor.SetTop( GetVisDocTop() );
+ }
+
+ const OutputDevice& rOutDev = GetOutputDevice();
+
+ tools::Long nOnePixel = rOutDev.PixelToLogic( Size( 1, 0 ) ).Width();
+
+ if ( ( aEditCursor.Top() + nOnePixel >= GetVisDocTop() ) &&
+ ( aEditCursor.Bottom() - nOnePixel <= GetVisDocBottom() ) &&
+ ( aEditCursor.Left() + nOnePixel >= GetVisDocLeft() ) &&
+ ( aEditCursor.Right() - nOnePixel <= GetVisDocRight() ) )
+ {
+ tools::Rectangle aCursorRect = GetWindowPos( aEditCursor );
+ GetCursor()->SetPos( aCursorRect.TopLeft() );
+ Size aCursorSz( aCursorRect.GetSize() );
+ // Rectangle is inclusive
+ aCursorSz.AdjustWidth( -1 );
+ aCursorSz.AdjustHeight( -1 );
+ if ( !aCursorSz.Width() || !aCursorSz.Height() )
+ {
+ tools::Long nCursorSz = rOutDev.GetSettings().GetStyleSettings().GetCursorSize();
+ nCursorSz = rOutDev.PixelToLogic( Size( nCursorSz, 0 ) ).Width();
+ if ( !aCursorSz.Width() )
+ aCursorSz.setWidth( nCursorSz );
+ if ( !aCursorSz.Height() )
+ aCursorSz.setHeight( nCursorSz );
+ }
+ // #111036# Let VCL do orientation for cursor, otherwise problem when cursor has direction flag
+ if ( IsVertical() )
+ {
+ Size aOldSz( aCursorSz );
+ aCursorSz.setWidth( aOldSz.Height() );
+ aCursorSz.setHeight( aOldSz.Width() );
+ GetCursor()->SetPos( aCursorRect.TopRight() );
+ GetCursor()->SetOrientation( Degree10(IsTopToBottom() ? 2700 : 900) );
+ }
+ else
+ // #i32593# Reset correct orientation in horizontal layout
+ GetCursor()->SetOrientation();
+
+ GetCursor()->SetSize( aCursorSz );
+
+ if (comphelper::LibreOfficeKit::isActive() && mpViewShell && !mbSuppressLOKMessages)
+ {
+ Point aPos = GetCursor()->GetPos();
+ boost::property_tree::ptree aMessageParams;
+ if (mpLOKSpecialPositioning)
+ {
+ // Sending the absolute (pure) logical coordinates of the cursor to the client is not
+ // enough for it to accurately reconstruct the corresponding tile-twips coordinates of the cursor.
+ // This is because the editeng(doc) positioning is not pixel aligned for each cell involved in the output-area
+ // (it better not be!). A simple solution is to send the coordinates of a point ('refpoint') in the output-area
+ // along with the relative position of the cursor w.r.t this chosen 'refpoint'.
+
+ MapUnit eDevUnit = rOutDev.GetMapMode().GetMapUnit();
+ tools::Rectangle aCursorRectPureLogical(aEditCursor.TopLeft(), GetCursor()->GetSize());
+ // Get rectangle in window-coordinates from editeng(doc) coordinates in hmm.
+ aCursorRectPureLogical = GetWindowPos(aCursorRectPureLogical);
+ Point aRefPointLogical = GetOutputArea().TopLeft();
+ // Get the relative coordinates w.r.t refpoint in display hmm.
+ aCursorRectPureLogical.Move(-aRefPointLogical.X(), -aRefPointLogical.Y());
+ if (pEditEngine->IsRightToLeft(nPara) || mpLOKSpecialPositioning->IsLayoutRTL())
+ {
+ tools::Long nMirrorW = GetOutputArea().GetWidth();
+ tools::Long nLeft = aCursorRectPureLogical.Left(), nRight = aCursorRectPureLogical.Right();
+ aCursorRectPureLogical.SetLeft(nMirrorW - nRight);
+ aCursorRectPureLogical.SetRight(nMirrorW - nLeft);
+ }
+ // Convert to twips.
+ aCursorRectPureLogical = OutputDevice::LogicToLogic(aCursorRectPureLogical, MapMode(eDevUnit), MapMode(MapUnit::MapTwip));
+ // "refpoint" in print twips.
+ const Point aRefPoint = mpLOKSpecialPositioning->GetRefPoint();
+ aMessageParams.put("relrect", aCursorRectPureLogical.toString());
+ aMessageParams.put("refpoint", aRefPoint.toString());
+ }
+
+ if (pOutWin && pOutWin->IsChart())
+ {
+ const vcl::Window* pViewShellWindow = mpViewShell->GetEditWindowForActiveOLEObj();
+ if (pViewShellWindow && pViewShellWindow->IsAncestorOf(*pOutWin))
+ {
+ Point aOffsetPx = pOutWin->GetOffsetPixelFrom(*pViewShellWindow);
+ Point aLogicOffset = pOutWin->PixelToLogic(aOffsetPx);
+ aPos.Move(aLogicOffset.getX(), aLogicOffset.getY());
+ }
+ }
+
+ tools::Rectangle aRect(aPos.getX(), aPos.getY(), aPos.getX() + GetCursor()->GetWidth(), aPos.getY() + GetCursor()->GetHeight());
+
+ // LOK output is always in twips, convert from mm100 if necessary.
+ if (rOutDev.GetMapMode().GetMapUnit() == MapUnit::Map100thMM)
+ {
+ aRect = o3tl::convert(aRect, o3tl::Length::mm100, o3tl::Length::twip);
+ }
+ else if (rOutDev.GetMapMode().GetMapUnit() == MapUnit::MapTwip)
+ {
+ // Writer comments: they use editeng, but are separate widgets.
+ Point aOrigin = rOutDev.GetMapMode().GetOrigin();
+ // Move the rectangle, so that we output absolute twips.
+ aRect.Move(aOrigin.getX(), aOrigin.getY());
+ }
+ // Let the LOK client decide the cursor width.
+ aRect.setWidth(0);
+
+ OString sRect = aRect.toString();
+ aMessageParams.put("rectangle", sRect);
+
+ SfxViewShell* pThisShell = dynamic_cast<SfxViewShell*>(mpViewShell);
+ SfxViewShell* pOtherShell = dynamic_cast<SfxViewShell*>(mpOtherShell);
+ assert(pThisShell);
+
+ if (pOtherShell && pThisShell != pOtherShell)
+ {
+ // Another shell wants to know about our existing cursor.
+ SfxLokHelper::notifyOtherView(pThisShell, pOtherShell,
+ LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, aMessageParams);
+ }
+ else
+ {
+ // is cursor at a misspelled word ?
+ Reference< linguistic2::XSpellChecker1 > xSpeller( pEditEngine->pImpEditEngine->GetSpeller() );
+ bool bIsWrong = xSpeller.is() && IsWrongSpelledWord(aPaM, /*bMarkIfWrong*/ false);
+ EditView* pActiveView = GetEditViewPtr();
+
+ boost::property_tree::ptree aHyperlinkTree;
+ if (pActiveView && URLFieldHelper::IsCursorAtURLField(*pActiveView))
+ {
+ if (const SvxFieldItem* pFld = GetField(aPos, nullptr, nullptr))
+ if (auto pUrlField = dynamic_cast<const SvxURLField*>(pFld->GetField()))
+ aHyperlinkTree = getHyperlinkPropTree(pUrlField->GetRepresentation(), pUrlField->GetURL());
+ }
+ else if (GetEditSelection().HasRange())
+ {
+ if (pActiveView)
+ {
+ const SvxFieldItem* pFieldItem = pActiveView->GetFieldAtSelection();
+ if (pFieldItem)
+ {
+ const SvxFieldData* pField = pFieldItem->GetField();
+ if ( auto pUrlField = dynamic_cast<const SvxURLField*>( pField) )
+ {
+ aHyperlinkTree = getHyperlinkPropTree(pUrlField->GetRepresentation(), pUrlField->GetURL());
+ }
+ }
+ }
+ }
+
+ if (mbBroadcastLOKViewCursor)
+ SfxLokHelper::notifyOtherViews(pThisShell,
+ LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, aMessageParams);
+
+ aMessageParams.put("mispelledWord", bIsWrong ? 1 : 0);
+ aMessageParams.add_child("hyperlink", aHyperlinkTree);
+
+ if (comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation())
+ SfxLokHelper::notifyOtherView(pThisShell, pThisShell,
+ LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, aMessageParams);
+ else
+ pThisShell->libreOfficeKitViewCallback(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR,
+ OString(aMessageParams.get<std::string>("rectangle")));
+ }
+ }
+
+ CursorDirection nCursorDir = CursorDirection::NONE;
+ if ( IsInsertMode() && !aEditSelection.HasRange() && ( pEditEngine->pImpEditEngine->HasDifferentRTLLevels( aPaM.GetNode() ) ) )
+ {
+ sal_uInt16 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTextPortionStart, bool(nShowCursorFlags & GetCursorFlags::PreferPortionStart) );
+ const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion];
+ if (rTextPortion.IsRightToLeft())
+ nCursorDir = CursorDirection::RTL;
+ else
+ nCursorDir = CursorDirection::LTR;
+
+ }
+ GetCursor()->SetDirection( nCursorDir );
+
+ if ( bForceVisCursor )
+ GetCursor()->Show();
+ {
+ SvxFont aFont;
+ pEditEngine->SeekCursor( aPaM.GetNode(), aPaM.GetIndex()+1, aFont );
+
+ InputContext aInputContext(std::move(aFont), InputContextFlags::Text | InputContextFlags::ExtText);
+ if (EditViewCallbacks* pCallbacks = getEditViewCallbacks())
+ pCallbacks->EditViewInputContext(aInputContext);
+ else if (auto xWindow = GetWindow())
+ xWindow->SetInputContext(aInputContext);
+ }
+ }
+ else
+ {
+ pEditEngine->pImpEditEngine->GetStatus().GetStatusWord() = pEditEngine->pImpEditEngine->GetStatus().GetStatusWord() | EditStatusFlags::CURSOROUT;
+ GetCursor()->Hide();
+ GetCursor()->SetPos( Point( -1, -1 ) );
+ GetCursor()->SetSize( Size( 0, 0 ) );
+ }
+}
+
+// call this so users of EditViewCallbacks can update their scrollbar state
+// so called when we have either scrolled to a new location
+// or the size of document has changed
+void ImpEditView::ScrollStateChange()
+{
+ if (EditViewCallbacks* pCallbacks = getEditViewCallbacks())
+ pCallbacks->EditViewScrollStateChange();
+}
+
+Pair ImpEditView::Scroll( tools::Long ndX, tools::Long ndY, ScrollRangeCheck nRangeCheck )
+{
+ DBG_ASSERT( pEditEngine->pImpEditEngine->IsFormatted(), "Scroll: Not formatted!" );
+ if ( !ndX && !ndY )
+ return Pair( 0, 0 );
+
+ const OutputDevice& rOutDev = GetOutputDevice();
+
+#ifdef DBG_UTIL
+ tools::Rectangle aR( aOutArea );
+ aR = rOutDev.LogicToPixel( aR );
+ aR = rOutDev.PixelToLogic( aR );
+ SAL_WARN_IF( aR != aOutArea, "editeng", "OutArea before Scroll not aligned" );
+#endif
+
+ tools::Rectangle aNewVisArea( GetVisDocArea() );
+
+ // Vertical:
+ if ( !IsVertical() )
+ {
+ aNewVisArea.AdjustTop( -ndY );
+ aNewVisArea.AdjustBottom( -ndY );
+ }
+ else
+ {
+ if( IsTopToBottom() )
+ {
+ aNewVisArea.AdjustTop(ndX );
+ aNewVisArea.AdjustBottom(ndX );
+ }
+ else
+ {
+ aNewVisArea.AdjustTop( -ndX );
+ aNewVisArea.AdjustBottom( -ndX );
+ }
+ }
+ if ( ( nRangeCheck == ScrollRangeCheck::PaperWidthTextSize ) && ( aNewVisArea.Bottom() > static_cast<tools::Long>(pEditEngine->pImpEditEngine->GetTextHeight()) ) )
+ {
+ // GetTextHeight still optimizing!
+ tools::Long nDiff = pEditEngine->pImpEditEngine->GetTextHeight() - aNewVisArea.Bottom(); // negative
+ aNewVisArea.Move( 0, nDiff ); // could end up in the negative area...
+ }
+ if ( aNewVisArea.Top() < 0 )
+ aNewVisArea.Move( 0, -aNewVisArea.Top() );
+
+ // Horizontal:
+ if ( !IsVertical() )
+ {
+ aNewVisArea.AdjustLeft( -ndX );
+ aNewVisArea.AdjustRight( -ndX );
+ }
+ else
+ {
+ if (IsTopToBottom())
+ {
+ aNewVisArea.AdjustLeft( -ndY );
+ aNewVisArea.AdjustRight( -ndY );
+ }
+ else
+ {
+ aNewVisArea.AdjustLeft(ndY );
+ aNewVisArea.AdjustRight(ndY );
+ }
+ }
+ if ( ( nRangeCheck == ScrollRangeCheck::PaperWidthTextSize ) && ( aNewVisArea.Right() > static_cast<tools::Long>(pEditEngine->pImpEditEngine->CalcTextWidth( false )) ) )
+ {
+ tools::Long nDiff = pEditEngine->pImpEditEngine->CalcTextWidth( false ) - aNewVisArea.Right(); // negative
+ aNewVisArea.Move( nDiff, 0 ); // could end up in the negative area...
+ }
+ if ( aNewVisArea.Left() < 0 )
+ aNewVisArea.Move( -aNewVisArea.Left(), 0 );
+
+ // The difference must be alignt on pixel (due to scroll!)
+ tools::Long nDiffX = !IsVertical() ? ( GetVisDocLeft() - aNewVisArea.Left() ) : (IsTopToBottom() ? -( GetVisDocTop() - aNewVisArea.Top() ) : (GetVisDocTop() - aNewVisArea.Top()));
+ tools::Long nDiffY = !IsVertical() ? ( GetVisDocTop() - aNewVisArea.Top() ) : (IsTopToBottom() ? (GetVisDocLeft() - aNewVisArea.Left()) : -(GetVisDocTop() - aNewVisArea.Top()));
+
+ Size aDiffs( nDiffX, nDiffY );
+ aDiffs = rOutDev.LogicToPixel( aDiffs );
+ aDiffs = rOutDev.PixelToLogic( aDiffs );
+
+ tools::Long nRealDiffX = aDiffs.Width();
+ tools::Long nRealDiffY = aDiffs.Height();
+
+
+ if ( nRealDiffX || nRealDiffY )
+ {
+ vcl::Cursor* pCrsr = GetCursor();
+ bool bVisCursor = pCrsr->IsVisible();
+ pCrsr->Hide();
+ if (pOutWin)
+ pOutWin->PaintImmediately();
+ if ( !IsVertical() )
+ aVisDocStartPos.Move( -nRealDiffX, -nRealDiffY );
+ else
+ {
+ if (IsTopToBottom())
+ aVisDocStartPos.Move(-nRealDiffY, nRealDiffX);
+ else
+ aVisDocStartPos.Move(nRealDiffY, -nRealDiffX);
+ }
+ // Move by aligned value does not necessarily result in aligned
+ // rectangle ...
+ aVisDocStartPos = rOutDev.LogicToPixel( aVisDocStartPos );
+ aVisDocStartPos = rOutDev.PixelToLogic( aVisDocStartPos );
+ tools::Rectangle aRect( aOutArea );
+
+ if (pOutWin)
+ {
+ pOutWin->Scroll( nRealDiffX, nRealDiffY, aRect, ScrollFlags::Clip );
+ }
+
+ if (comphelper::LibreOfficeKit::isActive() || getEditViewCallbacks())
+ {
+ // Need to invalidate the window, otherwise no tile will be re-painted.
+ pEditView->Invalidate();
+ }
+
+ if (pOutWin)
+ pOutWin->PaintImmediately();
+ pCrsr->SetPos( pCrsr->GetPos() + Point( nRealDiffX, nRealDiffY ) );
+ if ( bVisCursor )
+ {
+ tools::Rectangle aCursorRect( pCrsr->GetPos(), pCrsr->GetSize() );
+ if ( aOutArea.Contains( aCursorRect ) )
+ pCrsr->Show();
+ }
+
+ if ( pEditEngine->pImpEditEngine->GetNotifyHdl().IsSet() )
+ {
+ EENotify aNotify( EE_NOTIFY_TEXTVIEWSCROLLED );
+ pEditEngine->pImpEditEngine->GetNotifyHdl().Call( aNotify );
+ }
+
+ if (EditViewCallbacks* pCallbacks = getEditViewCallbacks())
+ pCallbacks->EditViewScrollStateChange();
+
+ if (comphelper::LibreOfficeKit::isActive())
+ {
+ DrawSelectionXOR();
+ }
+ }
+
+ return Pair( nRealDiffX, nRealDiffY );
+}
+
+Reference<css::datatransfer::clipboard::XClipboard> ImpEditView::GetClipboard() const
+{
+ if (EditViewCallbacks* pCallbacks = getEditViewCallbacks())
+ return pCallbacks->GetClipboard();
+ if (vcl::Window* pWindow = GetWindow())
+ return pWindow->GetClipboard();
+ SAL_WARN("editeng", "falling back to using GetSystemClipboard");
+ return GetSystemClipboard();
+}
+
+bool ImpEditView::PostKeyEvent( const KeyEvent& rKeyEvent, vcl::Window const * pFrameWin )
+{
+ bool bDone = false;
+
+ KeyFuncType eFunc = rKeyEvent.GetKeyCode().GetFunction();
+ if ( eFunc != KeyFuncType::DONTKNOW )
+ {
+ switch ( eFunc )
+ {
+ case KeyFuncType::CUT:
+ {
+ if ( !bReadOnly )
+ {
+ Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard());
+ CutCopy( aClipBoard, true );
+ bDone = true;
+ }
+ }
+ break;
+ case KeyFuncType::COPY:
+ {
+ Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard());
+ CutCopy( aClipBoard, false );
+ bDone = true;
+ }
+ break;
+ case KeyFuncType::PASTE:
+ {
+ if ( !bReadOnly && IsPasteEnabled() )
+ {
+ pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_PASTE );
+ Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetClipboard());
+ Paste( aClipBoard, pEditEngine->pImpEditEngine->GetStatus().AllowPasteSpecial() );
+ pEditEngine->pImpEditEngine->UndoActionEnd();
+ bDone = true;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ if( !bDone )
+ bDone = pEditEngine->PostKeyEvent( rKeyEvent, GetEditViewPtr(), pFrameWin );
+
+ return bDone;
+}
+
+bool ImpEditView::MouseButtonUp( const MouseEvent& rMouseEvent )
+{
+ nTravelXPos = TRAVEL_X_DONTKNOW;
+ nCursorBidiLevel = CURSOR_BIDILEVEL_DONTKNOW;
+ nExtraCursorFlags = GetCursorFlags::NONE;
+ bClickedInSelection = false;
+
+ if ( rMouseEvent.IsMiddle() && !bReadOnly &&
+ Application::GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection )
+ {
+ Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetSystemPrimarySelection());
+ Paste( aClipBoard );
+ }
+ else if ( rMouseEvent.IsLeft() && GetEditSelection().HasRange() )
+ {
+ Reference<css::datatransfer::clipboard::XClipboard> aClipBoard(GetSystemPrimarySelection());
+ CutCopy( aClipBoard, false );
+ }
+
+ return pEditEngine->pImpEditEngine->MouseButtonUp( rMouseEvent, GetEditViewPtr() );
+}
+
+void ImpEditView::ReleaseMouse()
+{
+ pEditEngine->pImpEditEngine->ReleaseMouse();
+}
+
+bool ImpEditView::MouseButtonDown( const MouseEvent& rMouseEvent )
+{
+ pEditEngine->CheckIdleFormatter(); // If fast typing and mouse button downs
+ nTravelXPos = TRAVEL_X_DONTKNOW;
+ nExtraCursorFlags = GetCursorFlags::NONE;
+ nCursorBidiLevel = CURSOR_BIDILEVEL_DONTKNOW;
+ bool bPrevUpdateLayout = pEditEngine->pImpEditEngine->SetUpdateLayout(true);
+ bClickedInSelection = IsSelectionAtPoint( rMouseEvent.GetPosPixel() );
+ bool bRet = pEditEngine->pImpEditEngine->MouseButtonDown( rMouseEvent, GetEditViewPtr() );
+ pEditEngine->pImpEditEngine->SetUpdateLayout(bPrevUpdateLayout);
+ return bRet;
+}
+
+bool ImpEditView::MouseMove( const MouseEvent& rMouseEvent )
+{
+ return pEditEngine->pImpEditEngine->MouseMove( rMouseEvent, GetEditViewPtr() );
+}
+
+bool ImpEditView::Command(const CommandEvent& rCEvt)
+{
+ pEditEngine->CheckIdleFormatter(); // If fast typing and mouse button down
+ return pEditEngine->pImpEditEngine->Command(rCEvt, GetEditViewPtr());
+}
+
+
+void ImpEditView::SetInsertMode( bool bInsert )
+{
+ if ( bInsert != IsInsertMode() )
+ {
+ SetFlags( nControl, EVControlBits::OVERWRITE, !bInsert );
+ ShowCursor( DoAutoScroll(), false );
+ }
+}
+
+bool ImpEditView::IsWrongSpelledWord( const EditPaM& rPaM, bool bMarkIfWrong )
+{
+ bool bIsWrong = false;
+ if ( rPaM.GetNode()->GetWrongList() )
+ {
+ EditSelection aSel = pEditEngine->SelectWord( rPaM, css::i18n::WordType::DICTIONARY_WORD );
+ bIsWrong = rPaM.GetNode()->GetWrongList()->HasWrong( aSel.Min().GetIndex(), aSel.Max().GetIndex() );
+ if ( bIsWrong && bMarkIfWrong )
+ {
+ DrawSelectionXOR();
+ SetEditSelection( aSel );
+ DrawSelectionXOR();
+ }
+ }
+ return bIsWrong;
+}
+
+OUString ImpEditView::SpellIgnoreWord()
+{
+ OUString aWord;
+ if ( pEditEngine->pImpEditEngine->GetSpeller().is() )
+ {
+ EditPaM aPaM = GetEditSelection().Max();
+ if ( !HasSelection() )
+ {
+ EditSelection aSel = pEditEngine->SelectWord(aPaM);
+ aWord = pEditEngine->pImpEditEngine->GetSelected( aSel );
+ }
+ else
+ {
+ aWord = pEditEngine->pImpEditEngine->GetSelected( GetEditSelection() );
+ // And deselect
+ DrawSelectionXOR();
+ SetEditSelection( EditSelection( aPaM, aPaM ) );
+ DrawSelectionXOR();
+ }
+
+ if ( !aWord.isEmpty() )
+ {
+ Reference< XDictionary > xDic = LinguMgr::GetIgnoreAllList();
+ if (xDic.is())
+ xDic->add( aWord, false, OUString() );
+ EditDoc& rDoc = pEditEngine->GetEditDoc();
+ sal_Int32 nNodes = rDoc.Count();
+ for ( sal_Int32 n = 0; n < nNodes; n++ )
+ {
+ ContentNode* pNode = rDoc.GetObject( n );
+ pNode->GetWrongList()->MarkWrongsInvalid();
+ }
+ pEditEngine->pImpEditEngine->DoOnlineSpelling( aPaM.GetNode() );
+ pEditEngine->pImpEditEngine->StartOnlineSpellTimer();
+ }
+ }
+ return aWord;
+}
+
+void ImpEditView::DeleteSelected()
+{
+ DrawSelectionXOR();
+
+ pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_DELETE );
+
+ EditPaM aPaM = pEditEngine->pImpEditEngine->DeleteSelected( GetEditSelection() );
+
+ pEditEngine->pImpEditEngine->UndoActionEnd();
+
+ SetEditSelection( EditSelection( aPaM, aPaM ) );
+
+ DrawSelectionXOR();
+
+ pEditEngine->pImpEditEngine->FormatAndLayout( GetEditViewPtr() );
+ ShowCursor( DoAutoScroll(), true );
+}
+
+const SvxFieldItem* ImpEditView::GetField( const Point& rPos, sal_Int32* pPara, sal_Int32* pPos ) const
+{
+ if( !GetOutputArea().Contains( rPos ) )
+ return nullptr;
+
+ Point aDocPos( GetDocPos( rPos ) );
+ EditPaM aPaM = pEditEngine->GetPaM(aDocPos, false);
+ if (!aPaM)
+ return nullptr;
+
+ if ( aPaM.GetIndex() == aPaM.GetNode()->Len() )
+ {
+ // Otherwise, whenever the Field at the very end and mouse under the text
+ return nullptr;
+ }
+
+ const CharAttribList::AttribsType& rAttrs = aPaM.GetNode()->GetCharAttribs().GetAttribs();
+ const sal_Int32 nXPos = aPaM.GetIndex();
+ for (size_t nAttr = rAttrs.size(); nAttr; )
+ {
+ const EditCharAttrib& rAttr = *rAttrs[--nAttr];
+ if (rAttr.GetStart() == nXPos || rAttr.GetEnd() == nXPos)
+ {
+ if (rAttr.Which() == EE_FEATURE_FIELD)
+ {
+ DBG_ASSERT(dynamic_cast<const SvxFieldItem*>(rAttr.GetItem()), "No FieldItem...");
+ if ( pPara )
+ *pPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() );
+ if ( pPos )
+ *pPos = rAttr.GetStart();
+ return static_cast<const SvxFieldItem*>(rAttr.GetItem());
+ }
+ }
+ }
+ return nullptr;
+}
+
+bool ImpEditView::IsBulletArea( const Point& rPos, sal_Int32* pPara )
+{
+ if ( pPara )
+ *pPara = EE_PARA_NOT_FOUND;
+
+ if( !GetOutputArea().Contains( rPos ) )
+ return false;
+
+ Point aDocPos( GetDocPos( rPos ) );
+ EditPaM aPaM = pEditEngine->GetPaM(aDocPos, false);
+ if (!aPaM)
+ return false;
+
+ if ( aPaM.GetIndex() == 0 )
+ {
+ sal_Int32 nPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() );
+ tools::Rectangle aBulletArea = pEditEngine->GetBulletArea( nPara );
+ tools::Long nY = pEditEngine->GetDocPosTopLeft( nPara ).Y();
+ const ParaPortion* pParaPortion = pEditEngine->GetParaPortions()[nPara];
+ nY += pParaPortion->GetFirstLineOffset();
+ if ( ( aDocPos.Y() > ( nY + aBulletArea.Top() ) ) &&
+ ( aDocPos.Y() < ( nY + aBulletArea.Bottom() ) ) &&
+ ( aDocPos.X() > ( aBulletArea.Left() ) ) &&
+ ( aDocPos.X() < ( aBulletArea.Right() ) ) )
+ {
+ if ( pPara )
+ *pPara = nPara;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void ImpEditView::CutCopy( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard, bool bCut )
+{
+ if ( !(rxClipboard.is() && HasSelection()) )
+ return;
+
+ uno::Reference<datatransfer::XTransferable> xData = pEditEngine->CreateTransferable( GetEditSelection() );
+
+ {
+ SolarMutexReleaser aReleaser;
+
+ try
+ {
+ rxClipboard->setContents( xData, nullptr );
+
+ // #87756# FlushClipboard, but it would be better to become a TerminateListener to the Desktop and flush on demand...
+ uno::Reference< datatransfer::clipboard::XFlushableClipboard > xFlushableClipboard( rxClipboard, uno::UNO_QUERY );
+ if( xFlushableClipboard.is() )
+ xFlushableClipboard->flushClipboard();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ }
+
+ if (bCut)
+ {
+ pEditEngine->pImpEditEngine->UndoActionStart(EDITUNDO_CUT);
+ DeleteSelected();
+ pEditEngine->pImpEditEngine->UndoActionEnd();
+ }
+}
+
+void ImpEditView::Paste( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard, bool bUseSpecial, SotClipboardFormatId format)
+{
+ if ( !rxClipboard.is() )
+ return;
+
+ uno::Reference< datatransfer::XTransferable > xDataObj;
+
+ try
+ {
+ SolarMutexReleaser aReleaser;
+ xDataObj = rxClipboard->getContents();
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+
+ if ( !xDataObj.is() || !EditEngine::HasValidData( xDataObj ) )
+ return;
+
+ pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_PASTE );
+
+ EditSelection aSel( GetEditSelection() );
+ if ( aSel.HasRange() )
+ {
+ DrawSelectionXOR();
+ aSel = pEditEngine->DeleteSelection(aSel);
+ }
+
+ PasteOrDropInfos aPasteOrDropInfos;
+ aPasteOrDropInfos.nStartPara = pEditEngine->GetEditDoc().GetPos( aSel.Min().GetNode() );
+ pEditEngine->HandleBeginPasteOrDrop(aPasteOrDropInfos);
+
+ if ( DoSingleLinePaste() )
+ {
+ datatransfer::DataFlavor aFlavor;
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
+ if ( xDataObj->isDataFlavorSupported( aFlavor ) )
+ {
+ try
+ {
+ uno::Any aData = xDataObj->getTransferData( aFlavor );
+ OUString aTmpText;
+ aData >>= aTmpText;
+ OUString aText(convertLineEnd(aTmpText, LINEEND_LF));
+ aText = aText.replaceAll( OUStringChar(LINE_SEP), " " );
+ aSel = pEditEngine->InsertText(aSel, aText);
+ }
+ catch( ... )
+ {
+ ; // #i9286# can happen, even if isDataFlavorSupported returns true...
+ }
+ }
+ }
+ else
+ {
+ // Prevent notifications of paragraph inserts et al that would trigger
+ // a11y to format content in a half-ready state when obtaining
+ // paragraphs. Collect and broadcast when done instead.
+ aSel = pEditEngine->InsertText(
+ xDataObj, OUString(), aSel.Min(),
+ bUseSpecial && pEditEngine->GetInternalEditStatus().AllowPasteSpecial(), format);
+ }
+
+ aPasteOrDropInfos.nEndPara = pEditEngine->GetEditDoc().GetPos( aSel.Max().GetNode() );
+ pEditEngine->HandleEndPasteOrDrop(aPasteOrDropInfos);
+
+ pEditEngine->pImpEditEngine->UndoActionEnd();
+ SetEditSelection( aSel );
+ pEditEngine->pImpEditEngine->UpdateSelections();
+ pEditEngine->pImpEditEngine->FormatAndLayout( GetEditViewPtr() );
+ ShowCursor( DoAutoScroll(), true );
+}
+
+
+bool ImpEditView::IsInSelection( const EditPaM& rPaM )
+{
+ EditSelection aSel = GetEditSelection();
+ if ( !aSel.HasRange() )
+ return false;
+
+ aSel.Adjust( pEditEngine->GetEditDoc() );
+
+ sal_Int32 nStartNode = pEditEngine->GetEditDoc().GetPos( aSel.Min().GetNode() );
+ sal_Int32 nEndNode = pEditEngine->GetEditDoc().GetPos( aSel.Max().GetNode() );
+ sal_Int32 nCurNode = pEditEngine->GetEditDoc().GetPos( rPaM.GetNode() );
+
+ if ( ( nCurNode > nStartNode ) && ( nCurNode < nEndNode ) )
+ return true;
+
+ if ( nStartNode == nEndNode )
+ {
+ if ( nCurNode == nStartNode )
+ if ( ( rPaM.GetIndex() >= aSel.Min().GetIndex() ) && ( rPaM.GetIndex() < aSel.Max().GetIndex() ) )
+ return true;
+ }
+ else if ( ( nCurNode == nStartNode ) && ( rPaM.GetIndex() >= aSel.Min().GetIndex() ) )
+ return true;
+ else if ( ( nCurNode == nEndNode ) && ( rPaM.GetIndex() < aSel.Max().GetIndex() ) )
+ return true;
+
+ return false;
+}
+
+bool ImpEditView::IsSelectionFullPara() const
+{
+ if (!IsSelectionInSinglePara())
+ return false;
+
+ sal_Int32 nSelectionStartPos = GetEditSelection().Min().GetIndex();
+ sal_Int32 nSelectionEndPos = GetEditSelection().Max().GetIndex();
+
+ if (nSelectionStartPos > nSelectionEndPos)
+ std::swap(nSelectionStartPos, nSelectionEndPos);
+
+ if (nSelectionStartPos != 0)
+ return false;
+
+ const ContentNode* pNode = GetEditSelection().Min().GetNode();
+ return pNode->Len() == nSelectionEndPos;
+}
+
+bool ImpEditView::IsSelectionInSinglePara() const
+{
+ return GetEditSelection().Min().GetNode() == GetEditSelection().Max().GetNode();
+}
+
+void ImpEditView::CreateAnchor()
+{
+ pEditEngine->SetInSelectionMode(true);
+ EditSelection aNewSelection(GetEditSelection());
+ aNewSelection.Min() = aNewSelection.Max();
+ SetEditSelection(aNewSelection);
+ // const_cast<EditPaM&>(GetEditSelection().Min()) = GetEditSelection().Max();
+}
+
+void ImpEditView::DeselectAll()
+{
+ pEditEngine->SetInSelectionMode(false);
+ DrawSelectionXOR();
+ EditSelection aNewSelection(GetEditSelection());
+ aNewSelection.Min() = aNewSelection.Max();
+ SetEditSelection(aNewSelection);
+ // const_cast<EditPaM&>(GetEditSelection().Min()) = GetEditSelection().Max();
+
+ if (comphelper::LibreOfficeKit::isActive() && mpViewShell && pOutWin)
+ {
+ VclPtr<vcl::Window> pParent = pOutWin->GetParentWithLOKNotifier();
+ if (pParent && pParent->GetLOKWindowId())
+ {
+ const vcl::ILibreOfficeKitNotifier* pNotifier = pParent->GetLOKNotifier();
+ std::vector<vcl::LOKPayloadItem> aItems;
+ aItems.emplace_back("rectangles", "");
+ pNotifier->notifyWindow(pParent->GetLOKWindowId(), "text_selection", aItems);
+ }
+ }
+}
+
+bool ImpEditView::IsSelectionAtPoint( const Point& rPosPixel )
+{
+ if ( pDragAndDropInfo && pDragAndDropInfo->pField )
+ return true;
+
+ // Logical units ...
+ const OutputDevice& rOutDev = GetOutputDevice();
+ Point aMousePos = rOutDev.PixelToLogic(rPosPixel);
+
+ if ( ( !GetOutputArea().Contains( aMousePos ) ) && !pEditEngine->pImpEditEngine->IsInSelectionMode() )
+ {
+ return false;
+ }
+
+ Point aDocPos( GetDocPos( aMousePos ) );
+ EditPaM aPaM = pEditEngine->GetPaM(aDocPos, false);
+ return IsInSelection( aPaM );
+}
+
+bool ImpEditView::SetCursorAtPoint( const Point& rPointPixel )
+{
+ pEditEngine->CheckIdleFormatter();
+
+ Point aMousePos( rPointPixel );
+
+ // Logical units ...
+ const OutputDevice& rOutDev = GetOutputDevice();
+ aMousePos = rOutDev.PixelToLogic( aMousePos );
+
+ if ( ( !GetOutputArea().Contains( aMousePos ) ) && !pEditEngine->pImpEditEngine->IsInSelectionMode() )
+ {
+ return false;
+ }
+
+ Point aDocPos( GetDocPos( aMousePos ) );
+
+ // Can be optimized: first go through the lines within a paragraph for PAM,
+ // then again with the PaM for the Rect, even though the line is already
+ // known... This must not be, though!
+ EditPaM aPaM = pEditEngine->GetPaM(aDocPos);
+ bool bGotoCursor = DoAutoScroll();
+
+ // aTmpNewSel: Diff between old and new, not the new selection, unless for tiled rendering
+ EditSelection aTmpNewSel( comphelper::LibreOfficeKit::isActive() ? GetEditSelection().Min() : GetEditSelection().Max(), aPaM );
+
+ // #i27299#
+ // work on copy of current selection and set new selection, if it has changed.
+ EditSelection aNewEditSelection( GetEditSelection() );
+
+ aNewEditSelection.Max() = aPaM;
+ if (!pEditEngine->GetSelectionEngine().HasAnchor())
+ {
+ if ( aNewEditSelection.Min() != aPaM )
+ {
+ const ContentNode* pNode(aNewEditSelection.Min().GetNode());
+ if (nullptr != pNode)
+ pNode->checkAndDeleteEmptyAttribs();
+ }
+ aNewEditSelection.Min() = aPaM;
+ }
+ else
+ {
+ DrawSelectionXOR( aTmpNewSel );
+ }
+
+ // set changed text selection
+ if ( GetEditSelection() != aNewEditSelection )
+ {
+ SetEditSelection( aNewEditSelection );
+ }
+
+ bool bForceCursor = pDragAndDropInfo == nullptr && !pEditEngine->pImpEditEngine->IsInSelectionMode();
+ ShowCursor( bGotoCursor, bForceCursor );
+ return true;
+}
+
+void ImpEditView::HideDDCursor()
+{
+ if ( pDragAndDropInfo && pDragAndDropInfo->bVisCursor )
+ {
+ OutputDevice& rOutDev = GetOutputDevice();
+ rOutDev.DrawOutDev( pDragAndDropInfo->aCurSavedCursor.TopLeft(), pDragAndDropInfo->aCurSavedCursor.GetSize(),
+ Point(0,0), pDragAndDropInfo->aCurSavedCursor.GetSize(),*pDragAndDropInfo->pBackground );
+ pDragAndDropInfo->bVisCursor = false;
+ }
+}
+
+void ImpEditView::ShowDDCursor( const tools::Rectangle& rRect )
+{
+ if ( !pDragAndDropInfo || pDragAndDropInfo->bVisCursor )
+ return;
+
+ if (pOutWin && pOutWin->GetCursor())
+ pOutWin->GetCursor()->Hide();
+
+ OutputDevice& rOutDev = GetOutputDevice();
+ Color aOldFillColor = rOutDev.GetFillColor();
+ rOutDev.SetFillColor( Color(4210752) ); // GRAY BRUSH_50, OLDSV, change to DDCursor!
+
+ // Save background ...
+ tools::Rectangle aSaveRect( rOutDev.LogicToPixel( rRect ) );
+ // prefer to save some more ...
+ aSaveRect.AdjustRight(1 );
+ aSaveRect.AdjustBottom(1 );
+
+ if ( !pDragAndDropInfo->pBackground )
+ {
+ pDragAndDropInfo->pBackground = VclPtr<VirtualDevice>::Create(rOutDev);
+ MapMode aMapMode( rOutDev.GetMapMode() );
+ aMapMode.SetOrigin( Point( 0, 0 ) );
+ pDragAndDropInfo->pBackground->SetMapMode( aMapMode );
+
+ }
+
+ Size aNewSzPx( aSaveRect.GetSize() );
+ Size aCurSzPx( pDragAndDropInfo->pBackground->GetOutputSizePixel() );
+ if ( ( aCurSzPx.Width() < aNewSzPx.Width() ) ||( aCurSzPx.Height() < aNewSzPx.Height() ) )
+ {
+ bool bDone = pDragAndDropInfo->pBackground->SetOutputSizePixel( aNewSzPx );
+ DBG_ASSERT( bDone, "Virtual Device broken?" );
+ }
+
+ aSaveRect = rOutDev.PixelToLogic( aSaveRect );
+
+ pDragAndDropInfo->pBackground->DrawOutDev( Point(0,0), aSaveRect.GetSize(),
+ aSaveRect.TopLeft(), aSaveRect.GetSize(), rOutDev );
+ pDragAndDropInfo->aCurSavedCursor = aSaveRect;
+
+ // Draw Cursor...
+ rOutDev.DrawRect( rRect );
+
+ pDragAndDropInfo->bVisCursor = true;
+ pDragAndDropInfo->aCurCursor = rRect;
+
+ rOutDev.SetFillColor( aOldFillColor );
+}
+
+void ImpEditView::dragGestureRecognized(const css::datatransfer::dnd::DragGestureEvent& rDGE)
+{
+ DBG_ASSERT( !pDragAndDropInfo, "dragGestureRecognized - DragAndDropInfo exist!" );
+
+ SolarMutexGuard aVclGuard;
+
+ pDragAndDropInfo.reset();
+
+ Point aMousePosPixel( rDGE.DragOriginX, rDGE.DragOriginY );
+
+ EditSelection aCopySel( GetEditSelection() );
+ aCopySel.Adjust( pEditEngine->GetEditDoc() );
+
+ if ( HasSelection() && bClickedInSelection )
+ {
+ pDragAndDropInfo.reset(new DragAndDropInfo());
+ }
+ else
+ {
+ // Field?!
+ sal_Int32 nPara;
+ sal_Int32 nPos;
+ Point aMousePos = GetOutputDevice().PixelToLogic( aMousePosPixel );
+ const SvxFieldItem* pField = GetField( aMousePos, &nPara, &nPos );
+ if ( pField )
+ {
+ pDragAndDropInfo.reset(new DragAndDropInfo());
+ pDragAndDropInfo->pField = pField;
+ ContentNode* pNode = pEditEngine->GetEditDoc().GetObject( nPara );
+ aCopySel = EditSelection( EditPaM( pNode, nPos ), EditPaM( pNode, nPos+1 ) );
+ SetEditSelection(aCopySel);
+ DrawSelectionXOR();
+ bool bGotoCursor = DoAutoScroll();
+ ShowCursor( bGotoCursor, /*bForceCursor=*/false );
+ }
+ else if ( IsBulletArea( aMousePos, &nPara ) )
+ {
+ pDragAndDropInfo.reset(new DragAndDropInfo());
+ pDragAndDropInfo->bOutlinerMode = true;
+ EditPaM aStartPaM( pEditEngine->GetEditDoc().GetObject( nPara ), 0 );
+ EditPaM aEndPaM( aStartPaM );
+ const SfxInt16Item& rLevel = pEditEngine->GetParaAttrib( nPara, EE_PARA_OUTLLEVEL );
+ for ( sal_Int32 n = nPara +1; n < pEditEngine->GetEditDoc().Count(); n++ )
+ {
+ const SfxInt16Item& rL = pEditEngine->GetParaAttrib( n, EE_PARA_OUTLLEVEL );
+ if ( rL.GetValue() > rLevel.GetValue() )
+ {
+ aEndPaM.SetNode( pEditEngine->GetEditDoc().GetObject( n ) );
+ }
+ else
+ {
+ break;
+ }
+ }
+ aEndPaM.SetIndex( aEndPaM.GetNode()->Len() );
+ SetEditSelection( EditSelection( aStartPaM, aEndPaM ) );
+ }
+ }
+
+ if ( !pDragAndDropInfo )
+ return;
+
+
+ pDragAndDropInfo->bStarterOfDD = true;
+
+ // Sensitive area to be scrolled.
+ Size aSz( 5, 0 );
+ aSz = GetOutputDevice().PixelToLogic( aSz );
+ pDragAndDropInfo->nSensibleRange = static_cast<sal_uInt16>(aSz.Width());
+ pDragAndDropInfo->nCursorWidth = static_cast<sal_uInt16>(aSz.Width()) / 2;
+ pDragAndDropInfo->aBeginDragSel = pEditEngine->pImpEditEngine->CreateESel( aCopySel );
+
+ uno::Reference<datatransfer::XTransferable> xData = pEditEngine->CreateTransferable(aCopySel);
+
+ sal_Int8 nActions = bReadOnly ? datatransfer::dnd::DNDConstants::ACTION_COPY : datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE;
+
+ rDGE.DragSource->startDrag( rDGE, nActions, 0 /*cursor*/, 0 /*image*/, xData, mxDnDListener );
+ // If Drag&Move in an Engine, then Copy&Del has to be optional!
+ GetCursor()->Hide();
+}
+
+void ImpEditView::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& rDSDE )
+{
+ SolarMutexGuard aVclGuard;
+
+ DBG_ASSERT( pDragAndDropInfo, "ImpEditView::dragDropEnd: pDragAndDropInfo is NULL!" );
+
+ // #123688# Shouldn't happen, but seems to happen...
+ if ( !pDragAndDropInfo )
+ return;
+
+ if ( !bReadOnly && rDSDE.DropSuccess && !pDragAndDropInfo->bOutlinerMode && ( rDSDE.DropAction & datatransfer::dnd::DNDConstants::ACTION_MOVE ) )
+ {
+ if ( pDragAndDropInfo->bStarterOfDD && pDragAndDropInfo->bDroppedInMe )
+ {
+ // DropPos: Where was it dropped, irrespective of length.
+ ESelection aDropPos( pDragAndDropInfo->aDropSel.nStartPara, pDragAndDropInfo->aDropSel.nStartPos, pDragAndDropInfo->aDropSel.nStartPara, pDragAndDropInfo->aDropSel.nStartPos );
+ ESelection aToBeDelSel = pDragAndDropInfo->aBeginDragSel;
+ ESelection aNewSel( pDragAndDropInfo->aDropSel.nEndPara, pDragAndDropInfo->aDropSel.nEndPos,
+ pDragAndDropInfo->aDropSel.nEndPara, pDragAndDropInfo->aDropSel.nEndPos );
+ bool bBeforeSelection = aDropPos < pDragAndDropInfo->aBeginDragSel;
+ sal_Int32 nParaDiff = pDragAndDropInfo->aBeginDragSel.nEndPara - pDragAndDropInfo->aBeginDragSel.nStartPara;
+ if ( bBeforeSelection )
+ {
+ // Adjust aToBeDelSel.
+ DBG_ASSERT( pDragAndDropInfo->aBeginDragSel.nStartPara >= pDragAndDropInfo->aDropSel.nStartPara, "But not before? ");
+ aToBeDelSel.nStartPara = aToBeDelSel.nStartPara + nParaDiff;
+ aToBeDelSel.nEndPara = aToBeDelSel.nEndPara + nParaDiff;
+ // To correct the character?
+ if ( aToBeDelSel.nStartPara == pDragAndDropInfo->aDropSel.nEndPara )
+ {
+ sal_uInt16 nMoreChars;
+ if ( pDragAndDropInfo->aDropSel.nStartPara == pDragAndDropInfo->aDropSel.nEndPara )
+ nMoreChars = pDragAndDropInfo->aDropSel.nEndPos - pDragAndDropInfo->aDropSel.nStartPos;
+ else
+ nMoreChars = pDragAndDropInfo->aDropSel.nEndPos;
+ aToBeDelSel.nStartPos =
+ aToBeDelSel.nStartPos + nMoreChars;
+ if ( aToBeDelSel.nStartPara == aToBeDelSel.nEndPara )
+ aToBeDelSel.nEndPos =
+ aToBeDelSel.nEndPos + nMoreChars;
+ }
+ }
+ else
+ {
+ // aToBeDelSel is ok, but the selection of the View
+ // has to be adapted, if it was deleted before!
+ DBG_ASSERT( pDragAndDropInfo->aBeginDragSel.nStartPara <= pDragAndDropInfo->aDropSel.nStartPara, "But not before? ");
+ aNewSel.nStartPara = aNewSel.nStartPara - nParaDiff;
+ aNewSel.nEndPara = aNewSel.nEndPara - nParaDiff;
+ // To correct the character?
+ if ( pDragAndDropInfo->aBeginDragSel.nEndPara == pDragAndDropInfo->aDropSel.nStartPara )
+ {
+ sal_uInt16 nLessChars;
+ if ( pDragAndDropInfo->aBeginDragSel.nStartPara == pDragAndDropInfo->aBeginDragSel.nEndPara )
+ nLessChars = pDragAndDropInfo->aBeginDragSel.nEndPos - pDragAndDropInfo->aBeginDragSel.nStartPos;
+ else
+ nLessChars = pDragAndDropInfo->aBeginDragSel.nEndPos;
+ aNewSel.nStartPos = aNewSel.nStartPos - nLessChars;
+ if ( aNewSel.nStartPara == aNewSel.nEndPara )
+ aNewSel.nEndPos = aNewSel.nEndPos - nLessChars;
+ }
+ }
+
+ DrawSelectionXOR();
+ EditSelection aDelSel( pEditEngine->pImpEditEngine->CreateSel( aToBeDelSel ) );
+ DBG_ASSERT( !aDelSel.DbgIsBuggy( pEditEngine->GetEditDoc() ), "ToBeDel is buggy!" );
+ pEditEngine->DeleteSelection(aDelSel);
+ if ( !bBeforeSelection )
+ {
+ DBG_ASSERT( !pEditEngine->pImpEditEngine->CreateSel( aNewSel ).DbgIsBuggy(pEditEngine->GetEditDoc()), "Bad" );
+ SetEditSelection( pEditEngine->pImpEditEngine->CreateSel( aNewSel ) );
+ }
+ pEditEngine->pImpEditEngine->FormatAndLayout( pEditEngine->pImpEditEngine->GetActiveView() );
+ DrawSelectionXOR();
+ }
+ else
+ {
+ // other EditEngine ...
+ if (pEditEngine->HasText()) // #88630# SC is removing the content when switching the task
+ DeleteSelected();
+ }
+ }
+
+ if ( pDragAndDropInfo->bUndoAction )
+ pEditEngine->pImpEditEngine->UndoActionEnd();
+
+ HideDDCursor();
+ ShowCursor( DoAutoScroll(), true );
+ pDragAndDropInfo.reset();
+ pEditEngine->GetEndDropHdl().Call(GetEditViewPtr());
+}
+
+void ImpEditView::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE )
+{
+ SolarMutexGuard aVclGuard;
+
+ DBG_ASSERT( pDragAndDropInfo, "Drop - No Drag&Drop info?!" );
+
+ if ( !(pDragAndDropInfo && pDragAndDropInfo->bDragAccepted) )
+ return;
+
+ pEditEngine->GetBeginDropHdl().Call(GetEditViewPtr());
+ bool bChanges = false;
+
+ HideDDCursor();
+
+ if ( pDragAndDropInfo->bStarterOfDD )
+ {
+ pEditEngine->pImpEditEngine->UndoActionStart( EDITUNDO_DRAGANDDROP );
+ pDragAndDropInfo->bUndoAction = true;
+ }
+
+ if ( pDragAndDropInfo->bOutlinerMode )
+ {
+ bChanges = true;
+ GetEditViewPtr()->MoveParagraphs( Range( pDragAndDropInfo->aBeginDragSel.nStartPara, pDragAndDropInfo->aBeginDragSel.nEndPara ), pDragAndDropInfo->nOutlinerDropDest );
+ }
+ else
+ {
+ uno::Reference< datatransfer::XTransferable > xDataObj = rDTDE.Transferable;
+ if ( xDataObj.is() )
+ {
+ bChanges = true;
+ // remove Selection ...
+ DrawSelectionXOR();
+ EditPaM aPaM( pDragAndDropInfo->aDropDest );
+
+ PasteOrDropInfos aPasteOrDropInfos;
+ aPasteOrDropInfos.nStartPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() );
+ pEditEngine->HandleBeginPasteOrDrop(aPasteOrDropInfos);
+
+ EditSelection aNewSel = pEditEngine->InsertText(
+ xDataObj, OUString(), aPaM, pEditEngine->GetInternalEditStatus().AllowPasteSpecial());
+
+ aPasteOrDropInfos.nEndPara = pEditEngine->GetEditDoc().GetPos( aNewSel.Max().GetNode() );
+ pEditEngine->HandleEndPasteOrDrop(aPasteOrDropInfos);
+
+ SetEditSelection( aNewSel );
+ pEditEngine->pImpEditEngine->FormatAndLayout( pEditEngine->pImpEditEngine->GetActiveView() );
+ if ( pDragAndDropInfo->bStarterOfDD )
+ {
+ // Only set if the same engine!
+ pDragAndDropInfo->aDropSel.nStartPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() );
+ pDragAndDropInfo->aDropSel.nStartPos = aPaM.GetIndex();
+ pDragAndDropInfo->aDropSel.nEndPara = pEditEngine->GetEditDoc().GetPos( aNewSel.Max().GetNode() );
+ pDragAndDropInfo->aDropSel.nEndPos = aNewSel.Max().GetIndex();
+ pDragAndDropInfo->bDroppedInMe = true;
+ }
+ }
+ }
+
+ if ( bChanges )
+ {
+ rDTDE.Context->acceptDrop( rDTDE.DropAction );
+ }
+
+ if ( !pDragAndDropInfo->bStarterOfDD )
+ {
+ pDragAndDropInfo.reset();
+ }
+
+ rDTDE.Context->dropComplete( bChanges );
+}
+
+void ImpEditView::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& rDTDEE )
+{
+ SolarMutexGuard aVclGuard;
+
+ if ( !pDragAndDropInfo )
+ pDragAndDropInfo.reset(new DragAndDropInfo());
+
+ pDragAndDropInfo->bHasValidData = false;
+
+ // Check for supported format...
+ // Only check for text, will also be there if bin or rtf
+ datatransfer::DataFlavor aTextFlavor;
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aTextFlavor );
+ const css::datatransfer::DataFlavor* pFlavors = rDTDEE.SupportedDataFlavors.getConstArray();
+ int nFlavors = rDTDEE.SupportedDataFlavors.getLength();
+ for ( int n = 0; n < nFlavors; n++ )
+ {
+ if( TransferableDataHelper::IsEqual( pFlavors[n], aTextFlavor ) )
+ {
+ pDragAndDropInfo->bHasValidData = true;
+ break;
+ }
+ }
+
+ dragOver( rDTDEE );
+}
+
+void ImpEditView::dragExit( const css::datatransfer::dnd::DropTargetEvent& )
+{
+ SolarMutexGuard aVclGuard;
+
+ HideDDCursor();
+
+ if ( pDragAndDropInfo && !pDragAndDropInfo->bStarterOfDD )
+ {
+ pDragAndDropInfo.reset();
+ }
+}
+
+void ImpEditView::dragOver(const css::datatransfer::dnd::DropTargetDragEvent& rDTDE)
+{
+ SolarMutexGuard aVclGuard;
+
+ const OutputDevice& rOutDev = GetOutputDevice();
+
+ Point aMousePos( rDTDE.LocationX, rDTDE.LocationY );
+ aMousePos = rOutDev.PixelToLogic( aMousePos );
+
+ bool bAccept = false;
+
+ if ( GetOutputArea().Contains( aMousePos ) && !bReadOnly )
+ {
+ if ( pDragAndDropInfo && pDragAndDropInfo->bHasValidData )
+ {
+ bAccept = true;
+
+ bool bAllowScroll = DoAutoScroll();
+ if ( bAllowScroll )
+ {
+ tools::Long nScrollX = 0;
+ tools::Long nScrollY = 0;
+ // Check if in the sensitive area
+ if ( ( (aMousePos.X()-pDragAndDropInfo->nSensibleRange) < GetOutputArea().Left() ) && ( ( aMousePos.X() + pDragAndDropInfo->nSensibleRange ) > GetOutputArea().Left() ) )
+ nScrollX = GetOutputArea().GetWidth() / SCRLRANGE;
+ else if ( ( (aMousePos.X()+pDragAndDropInfo->nSensibleRange) > GetOutputArea().Right() ) && ( ( aMousePos.X() - pDragAndDropInfo->nSensibleRange ) < GetOutputArea().Right() ) )
+ nScrollX = -( GetOutputArea().GetWidth() / SCRLRANGE );
+
+ if ( ( (aMousePos.Y()-pDragAndDropInfo->nSensibleRange) < GetOutputArea().Top() ) && ( ( aMousePos.Y() + pDragAndDropInfo->nSensibleRange ) > GetOutputArea().Top() ) )
+ nScrollY = GetOutputArea().GetHeight() / SCRLRANGE;
+ else if ( ( (aMousePos.Y()+pDragAndDropInfo->nSensibleRange) > GetOutputArea().Bottom() ) && ( ( aMousePos.Y() - pDragAndDropInfo->nSensibleRange ) < GetOutputArea().Bottom() ) )
+ nScrollY = -( GetOutputArea().GetHeight() / SCRLRANGE );
+
+ if ( nScrollX || nScrollY )
+ {
+ HideDDCursor();
+ Scroll( nScrollX, nScrollY, ScrollRangeCheck::PaperWidthTextSize );
+ }
+ }
+
+ Point aDocPos( GetDocPos( aMousePos ) );
+ EditPaM aPaM = pEditEngine->GetPaM( aDocPos );
+ pDragAndDropInfo->aDropDest = aPaM;
+ if ( pDragAndDropInfo->bOutlinerMode )
+ {
+ sal_Int32 nPara = pEditEngine->GetEditDoc().GetPos( aPaM.GetNode() );
+ ParaPortion* pPPortion = pEditEngine->GetParaPortions().SafeGetObject( nPara );
+ if (pPPortion)
+ {
+ tools::Long nDestParaStartY = pEditEngine->GetParaPortions().GetYOffset( pPPortion );
+ tools::Long nRel = aDocPos.Y() - nDestParaStartY;
+ if ( nRel < ( pPPortion->GetHeight() / 2 ) )
+ {
+ pDragAndDropInfo->nOutlinerDropDest = nPara;
+ }
+ else
+ {
+ pDragAndDropInfo->nOutlinerDropDest = nPara+1;
+ }
+
+ if( ( pDragAndDropInfo->nOutlinerDropDest >= pDragAndDropInfo->aBeginDragSel.nStartPara ) &&
+ ( pDragAndDropInfo->nOutlinerDropDest <= (pDragAndDropInfo->aBeginDragSel.nEndPara+1) ) )
+ {
+ bAccept = false;
+ }
+ }
+ }
+ else if ( HasSelection() )
+ {
+ // it must not be dropped into a selection
+ EPaM aP = pEditEngine->pImpEditEngine->CreateEPaM( aPaM );
+ ESelection aDestSel( aP.nPara, aP.nIndex, aP.nPara, aP.nIndex);
+ ESelection aCurSel = pEditEngine->pImpEditEngine->CreateESel( GetEditSelection() );
+ aCurSel.Adjust();
+ if ( !(aDestSel < aCurSel) && !(aDestSel > aCurSel) )
+ {
+ bAccept = false;
+ }
+ }
+ if ( bAccept )
+ {
+ tools::Rectangle aEditCursor;
+ if ( pDragAndDropInfo->bOutlinerMode )
+ {
+ tools::Long nDDYPos(0);
+ if ( pDragAndDropInfo->nOutlinerDropDest < pEditEngine->GetEditDoc().Count() )
+ {
+ ParaPortion* pPPortion = pEditEngine->GetParaPortions().SafeGetObject( pDragAndDropInfo->nOutlinerDropDest );
+ if (pPPortion)
+ nDDYPos = pEditEngine->GetParaPortions().GetYOffset( pPPortion );
+ }
+ else
+ {
+ nDDYPos = pEditEngine->pImpEditEngine->GetTextHeight();
+ }
+ Point aStartPos( 0, nDDYPos );
+ aStartPos = GetWindowPos( aStartPos );
+ Point aEndPos( GetOutputArea().GetWidth(), nDDYPos );
+ aEndPos = GetWindowPos( aEndPos );
+ aEditCursor = rOutDev.LogicToPixel( tools::Rectangle( aStartPos, aEndPos ) );
+ if ( !pEditEngine->IsEffectivelyVertical() )
+ {
+ aEditCursor.AdjustTop( -1 );
+ aEditCursor.AdjustBottom( 1 );
+ }
+ else
+ {
+ if( IsTopToBottom() )
+ {
+ aEditCursor.AdjustLeft( -1 );
+ aEditCursor.AdjustRight( 1 );
+ }
+ else
+ {
+ aEditCursor.AdjustLeft( 1 );
+ aEditCursor.AdjustRight( -1 );
+ }
+ }
+ aEditCursor = rOutDev.PixelToLogic( aEditCursor );
+ }
+ else
+ {
+ aEditCursor = pEditEngine->pImpEditEngine->PaMtoEditCursor( aPaM );
+ Point aTopLeft( GetWindowPos( aEditCursor.TopLeft() ) );
+ aEditCursor.SetPos( aTopLeft );
+ aEditCursor.SetRight( aEditCursor.Left() + pDragAndDropInfo->nCursorWidth );
+ aEditCursor = rOutDev.LogicToPixel( aEditCursor );
+ aEditCursor = rOutDev.PixelToLogic( aEditCursor );
+ }
+
+ bool bCursorChanged = !pDragAndDropInfo->bVisCursor || ( pDragAndDropInfo->aCurCursor != aEditCursor );
+ if ( bCursorChanged )
+ {
+ HideDDCursor();
+ ShowDDCursor(aEditCursor );
+ }
+ pDragAndDropInfo->bDragAccepted = true;
+ rDTDE.Context->acceptDrag( rDTDE.DropAction );
+ }
+ }
+ }
+
+ if ( !bAccept )
+ {
+ HideDDCursor();
+ if (pDragAndDropInfo)
+ pDragAndDropInfo->bDragAccepted = false;
+ rDTDE.Context->rejectDrag();
+ }
+}
+
+void ImpEditView::AddDragAndDropListeners()
+{
+ if (bActiveDragAndDropListener)
+ return;
+
+ css::uno::Reference<css::datatransfer::dnd::XDropTarget> xDropTarget;
+ if (EditViewCallbacks* pCallbacks = getEditViewCallbacks())
+ xDropTarget = pCallbacks->GetDropTarget();
+ else if (auto xWindow = GetWindow())
+ xDropTarget = xWindow->GetDropTarget();
+
+ if (!xDropTarget.is())
+ return;
+
+ mxDnDListener = new vcl::unohelper::DragAndDropWrapper(this);
+
+ css::uno::Reference<css::datatransfer::dnd::XDragGestureRecognizer> xDragGestureRecognizer(xDropTarget, uno::UNO_QUERY);
+ if (xDragGestureRecognizer.is())
+ {
+ uno::Reference<datatransfer::dnd::XDragGestureListener> xDGL(mxDnDListener, uno::UNO_QUERY);
+ xDragGestureRecognizer->addDragGestureListener(xDGL);
+ }
+
+ uno::Reference<datatransfer::dnd::XDropTargetListener> xDTL(mxDnDListener, uno::UNO_QUERY);
+ xDropTarget->addDropTargetListener(xDTL);
+ xDropTarget->setActive(true);
+ xDropTarget->setDefaultActions(datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE);
+
+ bActiveDragAndDropListener = true;
+}
+
+void ImpEditView::RemoveDragAndDropListeners()
+{
+ if (!bActiveDragAndDropListener)
+ return;
+
+ css::uno::Reference<css::datatransfer::dnd::XDropTarget> xDropTarget;
+ if (EditViewCallbacks* pCallbacks = getEditViewCallbacks())
+ xDropTarget = pCallbacks->GetDropTarget();
+ else if (auto xWindow = GetWindow())
+ xDropTarget = xWindow->GetDropTarget();
+
+ if (xDropTarget.is())
+ {
+ css::uno::Reference<css::datatransfer::dnd::XDragGestureRecognizer> xDragGestureRecognizer(xDropTarget, uno::UNO_QUERY);
+ if (xDragGestureRecognizer.is())
+ {
+ uno::Reference<datatransfer::dnd::XDragGestureListener> xDGL(mxDnDListener, uno::UNO_QUERY);
+ xDragGestureRecognizer->removeDragGestureListener(xDGL);
+ }
+
+ uno::Reference<datatransfer::dnd::XDropTargetListener> xDTL(mxDnDListener, uno::UNO_QUERY);
+ xDropTarget->removeDropTargetListener(xDTL);
+ }
+
+ if ( mxDnDListener.is() )
+ {
+ mxDnDListener->disposing( lang::EventObject() ); // #95154# Empty Source means it's the Client
+ mxDnDListener.clear();
+ }
+
+ bActiveDragAndDropListener = false;
+}
+
+void ImpEditView::InitLOKSpecialPositioning(MapUnit eUnit,
+ const tools::Rectangle& rOutputArea,
+ const Point& rVisDocStartPos)
+{
+ if (!mpLOKSpecialPositioning)
+ mpLOKSpecialPositioning.reset(new LOKSpecialPositioning(*this, eUnit, rOutputArea, rVisDocStartPos));
+ else
+ mpLOKSpecialPositioning->ReInit(eUnit, rOutputArea, rVisDocStartPos);
+}
+
+void ImpEditView::SetLOKSpecialOutputArea(const tools::Rectangle& rOutputArea)
+{
+ assert(mpLOKSpecialPositioning);
+ mpLOKSpecialPositioning->SetOutputArea(rOutputArea);
+}
+
+const tools::Rectangle & ImpEditView::GetLOKSpecialOutputArea() const
+{
+ assert(mpLOKSpecialPositioning);
+ return mpLOKSpecialPositioning->GetOutputArea();
+}
+
+void ImpEditView::SetLOKSpecialVisArea(const tools::Rectangle& rVisArea)
+{
+ assert(mpLOKSpecialPositioning);
+ mpLOKSpecialPositioning->SetVisDocStartPos(rVisArea.TopLeft());
+}
+
+tools::Rectangle ImpEditView::GetLOKSpecialVisArea() const
+{
+ assert(mpLOKSpecialPositioning);
+ return mpLOKSpecialPositioning->GetVisDocArea();
+}
+
+bool ImpEditView::HasLOKSpecialPositioning() const
+{
+ return bool(mpLOKSpecialPositioning);
+}
+
+void ImpEditView::SetLOKSpecialFlags(LOKSpecialFlags eFlags)
+{
+ assert(mpLOKSpecialPositioning);
+ mpLOKSpecialPositioning->SetFlags(eFlags);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/impedit.hxx b/editeng/source/editeng/impedit.hxx
new file mode 100644
index 0000000000..e4352f298f
--- /dev/null
+++ b/editeng/source/editeng/impedit.hxx
@@ -0,0 +1,1362 @@
+/* -*- 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 .
+ */
+#pragma once
+
+#include <eerdll2.hxx>
+#include <editdoc.hxx>
+#include "editsel.hxx"
+#include "editundo.hxx"
+#include "editstt2.hxx"
+#include <editeng/editdata.hxx>
+#include <editeng/SpellPortions.hxx>
+#include <editeng/editeng.hxx>
+#include <editeng/editview.hxx>
+#include <svtools/colorcfg.hxx>
+#include <editeng/outliner.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/cursor.hxx>
+#include <vcl/vclptr.hxx>
+#include <tools/fract.hxx>
+#include <vcl/idle.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/ptrstyle.hxx>
+
+#include <vcl/dndhelp.hxx>
+#include <svl/ondemand.hxx>
+#include <svl/languageoptions.hxx>
+#include <com/sun/star/linguistic2/XSpellAlternatives.hpp>
+#include <com/sun/star/linguistic2/XSpellChecker1.hpp>
+#include <com/sun/star/linguistic2/XHyphenator.hpp>
+#include <com/sun/star/lang/Locale.hpp>
+#include <com/sun/star/i18n/XBreakIterator.hpp>
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/XExtendedInputSequenceChecker.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+
+#include <i18nlangtag/lang.h>
+#include <o3tl/deleter.hxx>
+#include <o3tl/typed_flags_set.hxx>
+
+#include <functional>
+#include <optional>
+#include <memory>
+#include <tuple>
+#include <string_view>
+#include <vector>
+
+class EditView;
+class EditEngine;
+class OutlinerSearchable;
+
+class SvxSearchItem;
+class SvxLRSpaceItem;
+class TextRanger;
+class SvKeyValueIterator;
+class SvxForbiddenCharactersTable;
+namespace vcl { class Window; }
+class SvxNumberFormat;
+namespace com::sun::star::datatransfer::clipboard {
+ class XClipboard;
+}
+
+namespace editeng {
+ struct MisspellRanges;
+}
+
+#define DEL_LEFT 1
+#define DEL_RIGHT 2
+#define TRAVEL_X_DONTKNOW 0xFFFFFFFF
+#define CURSOR_BIDILEVEL_DONTKNOW 0xFFFF
+#define MAXCHARSINPARA 0x3FFF-CHARPOSGROW // Max 16K, because WYSIWYG array
+#define LINE_SEP '\x0A'
+
+#define ATTRSPECIAL_WHOLEWORD 1
+#define ATTRSPECIAL_EDGE 2
+
+enum class GetCursorFlags {
+ NONE = 0x0000,
+ TextOnly = 0x0001,
+ StartOfLine = 0x0002,
+ EndOfLine = 0x0004,
+ PreferPortionStart = 0x0008,
+};
+namespace o3tl {
+ template<> struct typed_flags<GetCursorFlags> : is_typed_flags<GetCursorFlags, 0x0f> {};
+}
+
+
+struct DragAndDropInfo
+{
+ tools::Rectangle aCurCursor;
+ tools::Rectangle aCurSavedCursor;
+ sal_uInt16 nSensibleRange;
+ sal_uInt16 nCursorWidth;
+ ESelection aBeginDragSel;
+ EditPaM aDropDest;
+ sal_Int32 nOutlinerDropDest;
+ ESelection aDropSel;
+ VclPtr<VirtualDevice> pBackground;
+ const SvxFieldItem* pField;
+ bool bVisCursor : 1;
+ bool bDroppedInMe : 1;
+ bool bStarterOfDD : 1;
+ bool bHasValidData : 1;
+ bool bUndoAction : 1;
+ bool bOutlinerMode : 1;
+ bool bDragAccepted : 1;
+
+ DragAndDropInfo()
+ : nSensibleRange(0), nCursorWidth(0), nOutlinerDropDest(0), pBackground(nullptr),
+ pField(nullptr), bVisCursor(false), bDroppedInMe(false), bStarterOfDD(false),
+ bHasValidData(false), bUndoAction(false), bOutlinerMode(false), bDragAccepted(false)
+ {
+ }
+ ~DragAndDropInfo()
+ {
+ pBackground.disposeAndClear();
+ }
+};
+
+struct ImplIMEInfos
+{
+ OUString aOldTextAfterStartPos;
+ std::unique_ptr<ExtTextInputAttr[]> pAttribs;
+ EditPaM aPos;
+ sal_Int32 nLen;
+ bool bWasCursorOverwrite;
+
+ ImplIMEInfos( const EditPaM& rPos, OUString aOldTextAfterStartPos );
+ ~ImplIMEInfos();
+
+ void CopyAttribs( const ExtTextInputAttr* pA, sal_uInt16 nL );
+ void DestroyAttribs();
+};
+
+// #i18881# to be able to identify the positions of changed words
+// the positions of each portion need to be saved
+typedef std::vector<EditSelection> SpellContentSelections;
+
+struct SpellInfo
+{
+ EditPaM aCurSentenceStart;
+ svx::SpellPortions aLastSpellPortions;
+ SpellContentSelections aLastSpellContentSelections;
+ EESpellState eState;
+ EPaM aSpellStart;
+ EPaM aSpellTo;
+ bool bSpellToEnd;
+ bool bMultipleDoc;
+ SpellInfo() : eState(EESpellState::Ok), bSpellToEnd(true), bMultipleDoc(false)
+ { }
+};
+
+// used for text conversion
+struct ConvInfo
+{
+ EPaM aConvStart;
+ EPaM aConvTo;
+ EPaM aConvContinue; // position to start search for next text portion (word) with
+ bool bConvToEnd;
+ bool bMultipleDoc;
+
+ ConvInfo() : bConvToEnd(true), bMultipleDoc(false) {}
+};
+
+struct FormatterFontMetric
+{
+ sal_uInt16 nMaxAscent;
+ sal_uInt16 nMaxDescent;
+
+ FormatterFontMetric() : nMaxAscent(0), nMaxDescent(0) { /* nMinLeading = 0xFFFF; */ }
+ sal_uInt16 GetHeight() const { return nMaxAscent+nMaxDescent; }
+};
+
+class IdleFormattter : public Idle
+{
+private:
+ EditView* pView;
+ int nRestarts;
+
+public:
+ IdleFormattter();
+ virtual ~IdleFormattter() override;
+
+ void DoIdleFormat( EditView* pV );
+ void ForceTimeout();
+ void ResetRestarts() { nRestarts = 0; }
+ EditView* GetView() { return pView; }
+};
+
+class ImpEditView;
+/// This is meant just for Calc, where all positions in logical units (twips for LOK) are computed by
+/// doing independent pixel-alignment for each cell's size. LOKSpecialPositioning stores
+/// both 'output-area' and 'visible-doc-position' in pure logical unit (twips for LOK).
+/// This allows the cursor/selection messages to be in regular(print) twips unit like in Writer.
+class LOKSpecialPositioning
+{
+public:
+ LOKSpecialPositioning(const ImpEditView& rImpEditView, MapUnit eUnit, const tools::Rectangle& rOutputArea,
+ const Point& rVisDocStartPos);
+
+ void ReInit(MapUnit eUnit, const tools::Rectangle& rOutputArea, const Point& rVisDocStartPos);
+
+ void SetOutputArea(const tools::Rectangle& rOutputArea);
+ const tools::Rectangle& GetOutputArea() const;
+ void SetVisDocStartPos(const Point& rVisDocStartPos);
+
+ bool IsVertical() const;
+ bool IsTopToBottom() const;
+
+ tools::Long GetVisDocLeft() const { return maVisDocStartPos.X(); }
+ tools::Long GetVisDocTop() const { return maVisDocStartPos.Y(); }
+ tools::Long GetVisDocRight() const { return maVisDocStartPos.X() + (!IsVertical() ? maOutArea.GetWidth() : maOutArea.GetHeight()); }
+ tools::Long GetVisDocBottom() const { return maVisDocStartPos.Y() + (!IsVertical() ? maOutArea.GetHeight() : maOutArea.GetWidth()); }
+ tools::Rectangle GetVisDocArea() const;
+
+ Point GetWindowPos(const Point& rDocPos, MapUnit eDocPosUnit) const;
+ tools::Rectangle GetWindowPos(const tools::Rectangle& rDocRect, MapUnit eDocRectUnit) const;
+
+ void SetFlags(LOKSpecialFlags eFlags) { meFlags = eFlags; }
+ bool IsLayoutRTL() { return bool(meFlags & LOKSpecialFlags::LayoutRTL); }
+
+ Point GetRefPoint() const;
+
+private:
+ Point convertUnit(const Point& rPos, MapUnit ePosUnit) const;
+ tools::Rectangle convertUnit(const tools::Rectangle& rRect, MapUnit eRectUnit) const;
+
+ const ImpEditView& mrImpEditView;
+ tools::Rectangle maOutArea;
+ Point maVisDocStartPos;
+ MapUnit meUnit;
+ LOKSpecialFlags meFlags;
+};
+
+
+
+class ImpEditView : public vcl::unohelper::DragAndDropClient
+{
+ friend class EditView;
+ friend class EditEngine;
+ friend class ImpEditEngine;
+ using vcl::unohelper::DragAndDropClient::dragEnter;
+ using vcl::unohelper::DragAndDropClient::dragExit;
+ using vcl::unohelper::DragAndDropClient::dragOver;
+
+private:
+ EditView* pEditView;
+ std::unique_ptr<vcl::Cursor, o3tl::default_delete<vcl::Cursor>> pCursor;
+ std::optional<Color> mxBackgroundColor;
+ /// Containing view shell, if any.
+ OutlinerViewShell* mpViewShell;
+ /// Another shell, just listening to our state, if any.
+ OutlinerViewShell* mpOtherShell;
+ EditEngine* pEditEngine;
+ VclPtr<vcl::Window> pOutWin;
+ EditView::OutWindowSet aOutWindowSet;
+ std::optional<PointerStyle> mxPointer;
+ std::unique_ptr<DragAndDropInfo> pDragAndDropInfo;
+
+ css::uno::Reference< css::datatransfer::dnd::XDragSourceListener > mxDnDListener;
+
+
+ tools::Long nInvMore;
+ EVControlBits nControl;
+ sal_uInt32 nTravelXPos;
+ GetCursorFlags nExtraCursorFlags;
+ sal_uInt16 nCursorBidiLevel;
+ sal_uInt16 nScrollDiffX;
+ bool bReadOnly;
+ bool bClickedInSelection;
+ bool bActiveDragAndDropListener;
+
+ Point aAnchorPoint;
+ tools::Rectangle aOutArea;
+ Point aVisDocStartPos;
+ EESelectionMode eSelectionMode;
+ EditSelection aEditSelection;
+ EEAnchorMode eAnchorMode;
+
+ /// mechanism to change from the classic refresh mode that simply
+ // invalidates the area where text was changed. When set, the invalidate
+ // and the direct repaint of the Window-plugged EditView will be suppressed.
+ // Instead, a consumer that has registered using an EditViewCallbacks
+ // incarnation has to handle that. Used e.g. to represent the edited text
+ // in Draw/Impress in an OverlayObject which avoids evtl. expensive full
+ // repaints of the EditView(s)
+ EditViewCallbacks* mpEditViewCallbacks;
+ std::unique_ptr<LOKSpecialPositioning> mpLOKSpecialPositioning;
+ bool mbBroadcastLOKViewCursor:1;
+ bool mbSuppressLOKMessages:1;
+ bool mbNegativeX:1;
+
+ EditViewCallbacks* getEditViewCallbacks() const
+ {
+ return mpEditViewCallbacks;
+ }
+
+ void lokSelectionCallback(const std::optional<tools::PolyPolygon> &pPolyPoly, bool bStartHandleVisible, bool bEndHandleVisible);
+
+ void setEditViewCallbacks(EditViewCallbacks* pEditViewCallbacks)
+ {
+ mpEditViewCallbacks = pEditViewCallbacks;
+ }
+
+ void InvalidateAtWindow(const tools::Rectangle& rRect);
+
+ css::uno::Reference<css::datatransfer::clipboard::XClipboard> GetClipboard() const;
+
+ void SetBroadcastLOKViewCursor(bool bSet)
+ {
+ mbBroadcastLOKViewCursor = bSet;
+ }
+
+protected:
+
+ // DragAndDropClient
+ void dragGestureRecognized(const css::datatransfer::dnd::DragGestureEvent& dge) override;
+ void dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& dsde ) override;
+ void drop(const css::datatransfer::dnd::DropTargetDropEvent& dtde) override;
+ void dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& dtdee ) override;
+ void dragExit( const css::datatransfer::dnd::DropTargetEvent& dte ) override;
+ void dragOver(const css::datatransfer::dnd::DropTargetDragEvent& dtde) override;
+
+ void ShowDDCursor( const tools::Rectangle& rRect );
+ void HideDDCursor();
+
+ void ImplDrawHighlightRect(OutputDevice& rTarget, const Point& rDocPosTopLeft, const Point& rDocPosBottomRight, tools::PolyPolygon* pPolyPoly, bool bLOKCalcRTL);
+ tools::Rectangle ImplGetEditCursor(EditPaM& aPaM, GetCursorFlags nShowCursorFlags,
+ sal_Int32& nTextPortionStart, const ParaPortion* pParaPortion) const;
+
+public:
+ ImpEditView( EditView* pView, EditEngine* pEng, vcl::Window* pWindow );
+ virtual ~ImpEditView() override;
+
+ EditView* GetEditViewPtr() { return pEditView; }
+
+ sal_uInt16 GetScrollDiffX() const { return nScrollDiffX; }
+ void SetScrollDiffX( sal_uInt16 n ) { nScrollDiffX = n; }
+
+ sal_uInt16 GetCursorBidiLevel() const { return nCursorBidiLevel; }
+ void SetCursorBidiLevel( sal_uInt16 n ) { nCursorBidiLevel = n; }
+
+ Point GetDocPos( const Point& rWindowPos ) const;
+ Point GetWindowPos( const Point& rDocPos ) const;
+ tools::Rectangle GetWindowPos( const tools::Rectangle& rDocPos ) const;
+
+ void SetOutputArea( const tools::Rectangle& rRect );
+ void ResetOutputArea( const tools::Rectangle& rRect );
+ const tools::Rectangle& GetOutputArea() const { return aOutArea; }
+
+ bool IsVertical() const;
+ bool IsTopToBottom() const;
+
+ bool PostKeyEvent( const KeyEvent& rKeyEvent, vcl::Window const * pFrameWin );
+
+ bool MouseButtonUp( const MouseEvent& rMouseEvent );
+ bool MouseButtonDown( const MouseEvent& rMouseEvent );
+ void ReleaseMouse();
+ bool MouseMove( const MouseEvent& rMouseEvent );
+ bool Command(const CommandEvent& rCEvt);
+
+ void CutCopy( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard, bool bCut );
+ void Paste( css::uno::Reference< css::datatransfer::clipboard::XClipboard > const & rxClipboard, bool bUseSpecial = false, SotClipboardFormatId format = SotClipboardFormatId::NONE);
+
+ void SetVisDocStartPos( const Point& rPos ) { aVisDocStartPos = rPos; }
+
+ tools::Long GetVisDocLeft() const { return aVisDocStartPos.X(); }
+ tools::Long GetVisDocTop() const { return aVisDocStartPos.Y(); }
+ tools::Long GetVisDocRight() const { return aVisDocStartPos.X() + ( !IsVertical() ? aOutArea.GetWidth() : aOutArea.GetHeight() ); }
+ tools::Long GetVisDocBottom() const { return aVisDocStartPos.Y() + ( !IsVertical() ? aOutArea.GetHeight() : aOutArea.GetWidth() ); }
+ tools::Rectangle GetVisDocArea() const;
+
+ const EditSelection& GetEditSelection() const { return aEditSelection; }
+ void SetEditSelection( const EditSelection& rEditSelection );
+ bool HasSelection() const { return aEditSelection.HasRange(); }
+
+ void SelectionChanged();
+ void DrawSelectionXOR() { DrawSelectionXOR( aEditSelection ); }
+ void DrawSelectionXOR( EditSelection, vcl::Region* pRegion = nullptr, OutputDevice* pTargetDevice = nullptr );
+ void GetSelectionRectangles(EditSelection aTmpSel, std::vector<tools::Rectangle>& rLogicRects);
+
+ void ScrollStateChange();
+
+ OutputDevice& GetOutputDevice() const;
+ weld::Widget* GetPopupParent(tools::Rectangle& rRect) const;
+ vcl::Window* GetWindow() const { return pOutWin; }
+
+ void SetSelectionMode( EESelectionMode eMode );
+
+ inline PointerStyle GetPointer();
+
+ inline vcl::Cursor* GetCursor();
+
+ void AddDragAndDropListeners();
+ void RemoveDragAndDropListeners();
+
+ bool IsBulletArea( const Point& rPos, sal_Int32* pPara );
+
+// For the Selection Engine...
+ void CreateAnchor();
+ void DeselectAll();
+ bool SetCursorAtPoint( const Point& rPointPixel );
+ bool IsSelectionAtPoint( const Point& rPosPixel );
+ bool IsInSelection( const EditPaM& rPaM );
+
+ bool IsSelectionFullPara() const;
+ bool IsSelectionInSinglePara() const;
+
+ void SetAnchorMode( EEAnchorMode eMode );
+ EEAnchorMode GetAnchorMode() const { return eAnchorMode; }
+ void CalcAnchorPoint();
+ void RecalcOutputArea();
+
+ tools::Rectangle GetEditCursor() const;
+
+ void ShowCursor( bool bGotoCursor, bool bForceVisCursor );
+ Pair Scroll( tools::Long ndX, tools::Long ndY, ScrollRangeCheck nRangeCheck = ScrollRangeCheck::NoNegative );
+
+ void SetInsertMode( bool bInsert );
+ bool IsInsertMode() const { return !( nControl & EVControlBits::OVERWRITE ); }
+
+ bool IsPasteEnabled() const { return bool( nControl & EVControlBits::ENABLEPASTE ); }
+
+ bool DoSingleLinePaste() const { return bool( nControl & EVControlBits::SINGLELINEPASTE ); }
+ bool DoAutoScroll() const { return bool( nControl & EVControlBits::AUTOSCROLL ); }
+ bool DoAutoSize() const { return bool( nControl & EVControlBits::AUTOSIZE ); }
+ bool DoAutoWidth() const { return bool( nControl & EVControlBits::AUTOSIZEX); }
+ bool DoAutoHeight() const { return bool( nControl & EVControlBits::AUTOSIZEY); }
+ bool DoInvalidateMore() const { return bool( nControl & EVControlBits::INVONEMORE ); }
+
+ void SetBackgroundColor( const Color& rColor );
+ const Color& GetBackgroundColor() const;
+
+ /// Informs this edit view about which view shell contains it.
+ void RegisterViewShell(OutlinerViewShell* pViewShell);
+ const OutlinerViewShell* GetViewShell() const;
+ /// Informs this edit view about which other shell listens to it.
+ void RegisterOtherShell(OutlinerViewShell* pViewShell);
+
+ bool IsWrongSpelledWord( const EditPaM& rPaM, bool bMarkIfWrong );
+ OUString SpellIgnoreWord();
+
+ const SvxFieldItem* GetField( const Point& rPos, sal_Int32* pPara, sal_Int32* pPos ) const;
+ void DeleteSelected();
+
+ // If possible invalidate more than OutputArea, for the DrawingEngine text frame
+ void SetInvalidateMore( sal_uInt16 nPixel ) { nInvMore = nPixel; }
+ sal_uInt16 GetInvalidateMore() const { return static_cast<sal_uInt16>(nInvMore); }
+
+ void InitLOKSpecialPositioning(MapUnit eUnit, const tools::Rectangle& rOutputArea,
+ const Point& rVisDocStartPos);
+ void SetLOKSpecialOutputArea(const tools::Rectangle& rOutputArea);
+ const tools::Rectangle & GetLOKSpecialOutputArea() const;
+ void SetLOKSpecialVisArea(const tools::Rectangle& rVisArea);
+ tools::Rectangle GetLOKSpecialVisArea() const;
+ bool HasLOKSpecialPositioning() const;
+
+ void SetLOKSpecialFlags(LOKSpecialFlags eFlags);
+
+ void SuppressLOKMessages(bool bSet) { mbSuppressLOKMessages = bSet; }
+ bool IsSuppressLOKMessages() const { return mbSuppressLOKMessages; }
+
+ void SetNegativeX(bool bSet) { mbNegativeX = bSet; }
+ bool IsNegativeX() const { return mbNegativeX; }
+};
+
+
+// ImpEditEngine
+
+
+class ImpEditEngine : public SfxListener, public svl::StyleSheetUser
+{
+ friend class EditEngine;
+
+ typedef EditEngine::ViewsType ViewsType;
+
+private:
+ std::shared_ptr<editeng::SharedVclResources> pSharedVCL;
+
+ // Document Specific data ...
+ ParaPortionList maParaPortionList; // Formatting
+ Size maPaperSize; // Layout
+ Size maMinAutoPaperSize; // Layout ?
+ Size maMaxAutoPaperSize; // Layout ?
+ tools::Long mnMinColumnWrapHeight = 0; // Corresponds to graphic object height
+ EditDoc maEditDoc; // Document content
+
+ // Engine Specific data ...
+ EditEngine* pEditEngine;
+ ViewsType aEditViews;
+ EditView* pActiveView;
+ std::unique_ptr<TextRanger> pTextRanger;
+
+ SfxStyleSheetPool* pStylePool;
+ SfxItemPool* pTextObjectPool;
+
+ VclPtr< VirtualDevice> pVirtDev;
+ VclPtr< OutputDevice > pRefDev;
+ VclPtr<VirtualDevice> mpOwnDev;
+
+ svtools::ColorConfig maColorConfig;
+
+ mutable std::unique_ptr<SfxItemSet> pEmptyItemSet;
+ EditUndoManager* pUndoManager;
+ std::optional<ESelection> moUndoMarkSelection;
+
+ std::unique_ptr<ImplIMEInfos> mpIMEInfos;
+
+ OUString aWordDelimiters;
+
+ EditSelFunctionSet aSelFuncSet;
+ EditSelectionEngine aSelEngine;
+
+ Color maBackgroundColor;
+
+ double mfFontScaleX;
+ double mfFontScaleY;
+ double mfSpacingScaleX;
+ double mfSpacingScaleY;
+ bool mbRoundToNearestPt;
+
+ CharCompressType mnAsianCompressionMode;
+
+ EEHorizontalTextDirection eDefaultHorizontalTextDirection;
+
+ sal_Int32 mnBigTextObjectStart;
+ css::uno::Reference< css::linguistic2::XSpellChecker1 > xSpeller;
+ css::uno::Reference< css::linguistic2::XHyphenator > xHyphenator;
+ std::unique_ptr<SpellInfo> pSpellInfo;
+ mutable css::uno::Reference < css::i18n::XBreakIterator > xBI;
+ mutable css::uno::Reference < css::i18n::XExtendedInputSequenceChecker > xISC;
+
+ std::unique_ptr<ConvInfo> pConvInfo;
+
+ OUString maAutoCompleteText;
+
+ InternalEditStatus maStatus;
+
+ LanguageType meDefLanguage;
+
+ OnDemandLocaleDataWrapper xLocaleDataWrapper;
+ OnDemandTransliterationWrapper xTransliterationWrapper;
+
+ // For Formatting / Update...
+ std::vector<std::unique_ptr<DeletedNodeInfo> > aDeletedNodes;
+ tools::Rectangle aInvalidRect;
+ tools::Long nCurTextHeight;
+ tools::Long nCurTextHeightNTP; // without trailing empty paragraphs
+ sal_uInt16 nOnePixelInRef;
+
+ IdleFormattter aIdleFormatter;
+
+ Timer aOnlineSpellTimer;
+
+ // For Chaining
+ sal_Int32 mnOverflowingPara = -1;
+ sal_Int32 mnOverflowingLine = -1;
+ bool mbNeedsChainingHandling = false;
+
+ sal_Int16 mnColumns = 1;
+ sal_Int32 mnColumnSpacing = 0;
+
+ // If it is detected at one point that the StatusHdl has to be called, but
+ // this should not happen immediately (critical section):
+ Timer aStatusTimer;
+ Size aLOKSpecialPaperSize;
+
+ Link<EditStatus&,void> aStatusHdlLink;
+ Link<EENotify&,void> aNotifyHdl;
+ Link<HtmlImportInfo&,void> aHtmlImportHdl;
+ Link<RtfImportInfo&,void> aRtfImportHdl;
+ Link<MoveParagraphsInfo&,void> aBeginMovingParagraphsHdl;
+ Link<MoveParagraphsInfo&,void> aEndMovingParagraphsHdl;
+ Link<PasteOrDropInfos&,void> aBeginPasteOrDropHdl;
+ Link<PasteOrDropInfos&,void> aEndPasteOrDropHdl;
+ Link<LinkParamNone*,void> aModifyHdl;
+ Link<EditView*,void> maBeginDropHdl;
+ Link<EditView*,void> maEndDropHdl;
+
+ bool mbKernAsianPunctuation : 1;
+ bool mbAddExtLeading : 1;
+ bool mbIsFormatting : 1;
+ bool mbFormatted : 1;
+ bool mbInSelection : 1;
+ bool mbIsInUndo : 1;
+ bool mbUpdateLayout : 1;
+ bool mbUndoEnabled : 1;
+ bool mbDowning : 1;
+ bool mbUseAutoColor : 1;
+ bool mbForceAutoColor : 1;
+ bool mbCallParaInsertedOrDeleted : 1;
+ bool mbFirstWordCapitalization : 1; // specifies if auto-correction should capitalize the first word or not
+ bool mbLastTryMerge : 1;
+ bool mbReplaceLeadingSingleQuotationMark : 1;
+ bool mbSkipOutsideFormat : 1;
+ bool mbFuzzing : 1;
+
+ bool mbNbspRunNext; // can't be a bitfield as it is passed as bool&
+
+ // Methods...
+
+
+ void ParaAttribsChanged( ContentNode const * pNode, bool bIgnoreUndoCheck = false );
+ void TextModified();
+ void CalcHeight( ParaPortion* pPortion );
+
+ void InsertUndo( std::unique_ptr<EditUndo> pUndo, bool bTryMerge = false );
+ void ResetUndoManager();
+ bool HasUndoManager() const { return pUndoManager != nullptr; }
+
+ std::unique_ptr<EditUndoSetAttribs> CreateAttribUndo( EditSelection aSel, const SfxItemSet& rSet );
+
+ std::unique_ptr<EditTextObject> GetEmptyTextObject();
+
+ std::tuple<const ParaPortion*, const EditLine*, tools::Long> GetPortionAndLine(Point aDocPos);
+ EditPaM GetPaM( Point aDocPos, bool bSmart = true );
+ bool IsTextPos(const Point& rDocPos, sal_uInt16 nBorder);
+ tools::Long GetXPos(const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart = false) const;
+ tools::Long GetPortionXOffset(const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nTextPortion) const;
+ sal_Int32 GetChar(const ParaPortion* pParaPortion, const EditLine* pLine, tools::Long nX, bool bSmart = true);
+ Range GetLineXPosStartEnd( const ParaPortion* pParaPortion, const EditLine* pLine ) const;
+
+ void ParaAttribsToCharAttribs( ContentNode* pNode );
+ void GetCharAttribs( sal_Int32 nPara, std::vector<EECharAttrib>& rLst ) const;
+
+ std::unique_ptr<EditTextObject>
+ CreateTextObject(EditSelection aSelection, SfxItemPool*, bool bAllowBigObjects = false, sal_Int32 nBigObjStart = 0);
+ EditSelection InsertTextObject( const EditTextObject&, EditPaM aPaM );
+ EditSelection PasteText( css::uno::Reference< css::datatransfer::XTransferable > const & rxDataObj, const OUString& rBaseURL, const EditPaM& rPaM, bool bUseSpecial, SotClipboardFormatId format = SotClipboardFormatId::NONE);
+
+ void CheckPageOverflow();
+
+ void Clear();
+ EditPaM RemoveText();
+ bool CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY );
+ void CreateAndInsertEmptyLine( ParaPortion* pParaPortion );
+ bool FinishCreateLines( ParaPortion* pParaPortion );
+ void CreateTextPortions( ParaPortion* pParaPortion, sal_Int32& rStartPos /*, sal_Bool bCreateBlockPortions */ );
+ void RecalcTextPortion( ParaPortion* pParaPortion, sal_Int32 nStartPos, sal_Int32 nNewChars );
+ sal_Int32 SplitTextPortion( ParaPortion* pParaPortion, sal_Int32 nPos, EditLine* pCurLine = nullptr );
+ void SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFont, OutputDevice* pOut = nullptr );
+ void RecalcFormatterFontMetrics( FormatterFontMetric& rCurMetrics, SvxFont& rFont );
+ void CheckAutoPageSize();
+
+ void ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, TextPortion const * pPortion, sal_Int32 nPortionStart, tools::Long nRemainingWidth, bool bCanHyphenate );
+ void ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine, tools::Long nRemainingSpace );
+ EditPaM ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pRight, bool bBackward = false );
+ EditPaM ImpDeleteSelection(const EditSelection& rCurSel);
+ EditPaM ImpInsertParaBreak( EditPaM& rPaM, bool bKeepEndingAttribs = true );
+ EditPaM ImpInsertParaBreak( const EditSelection& rEditSelection );
+ EditPaM ImpInsertText(const EditSelection& aCurEditSelection, const OUString& rStr);
+ EditPaM ImpInsertFeature(const EditSelection& rCurSel, const SfxPoolItem& rItem);
+ void ImpRemoveChars( const EditPaM& rPaM, sal_Int32 nChars );
+ void ImpRemoveParagraph( sal_Int32 nPara );
+ EditSelection ImpMoveParagraphs( Range aParagraphs, sal_Int32 nNewPos );
+
+ EditPaM ImpFastInsertText( EditPaM aPaM, const OUString& rStr );
+ EditPaM ImpFastInsertParagraph( sal_Int32 nPara );
+
+ bool ImpCheckRefMapMode();
+
+ bool ImplHasText() const;
+
+ void ImpFindKashidas( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, std::vector<sal_Int32>& rArray );
+
+ void InsertContent( ContentNode* pNode, sal_Int32 nPos );
+ EditPaM SplitContent( sal_Int32 nNode, sal_Int32 nSepPos );
+ EditPaM ConnectContents( sal_Int32 nLeftNode, bool bBackward );
+
+ void ShowParagraph( sal_Int32 nParagraph, bool bShow );
+
+ EditPaM PageUp( const EditPaM& rPaM, EditView const * pView);
+ EditPaM PageDown( const EditPaM& rPaM, EditView const * pView);
+ EditPaM CursorUp( const EditPaM& rPaM, EditView const * pEditView );
+ EditPaM CursorDown( const EditPaM& rPaM, EditView const * pEditView );
+ EditPaM CursorLeft( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode = css::i18n::CharacterIteratorMode::SKIPCELL );
+ EditPaM CursorRight( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode = css::i18n::CharacterIteratorMode::SKIPCELL );
+ EditPaM CursorStartOfLine( const EditPaM& rPaM );
+ EditPaM CursorEndOfLine( const EditPaM& rPaM );
+ static EditPaM CursorStartOfParagraph( const EditPaM& rPaM );
+ static EditPaM CursorEndOfParagraph( const EditPaM& rPaM );
+ EditPaM CursorStartOfDoc();
+ EditPaM CursorEndOfDoc();
+ EditPaM WordLeft( const EditPaM& rPaM );
+ EditPaM WordRight( const EditPaM& rPaM, sal_Int16 nWordType = css::i18n::WordType::ANYWORD_IGNOREWHITESPACES );
+ EditPaM StartOfWord( const EditPaM& rPaM );
+ EditPaM EndOfWord( const EditPaM& rPaM );
+ EditSelection SelectWord( const EditSelection& rCurSelection, sal_Int16 nWordType = css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, bool bAcceptStartOfWord = true );
+ EditSelection SelectSentence( const EditSelection& rCurSel ) const;
+ EditPaM CursorVisualLeftRight( EditView const * pEditView, const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode, bool bToLeft );
+ EditPaM CursorVisualStartEnd( EditView const * pEditView, const EditPaM& rPaM, bool bStart );
+
+
+ void InitScriptTypes( sal_Int32 nPara );
+ sal_uInt16 GetI18NScriptType( const EditPaM& rPaM, sal_Int32* pEndPos = nullptr ) const;
+ SvtScriptType GetItemScriptType( const EditSelection& rSel ) const;
+ bool IsScriptChange( const EditPaM& rPaM ) const;
+ bool HasScriptType( sal_Int32 nPara, sal_uInt16 nType ) const;
+
+ bool ImplCalcAsianCompression( ContentNode* pNode, TextPortion* pTextPortion, sal_Int32 nStartPos,
+ sal_Int32* pDXArray, sal_uInt16 n100thPercentFromMax, bool bManipulateDXArray );
+ void ImplExpandCompressedPortions( EditLine* pLine, ParaPortion* pParaPortion, tools::Long nRemainingWidth );
+
+ void ImplInitLayoutMode(OutputDevice& rOutDev, sal_Int32 nPara, sal_Int32 nIndex);
+ static LanguageType ImplCalcDigitLang(LanguageType eCurLang);
+ static void ImplInitDigitMode(OutputDevice& rOutDev, LanguageType eLang);
+ static OUString convertDigits(std::u16string_view rString, sal_Int32 nStt, sal_Int32 nLen, LanguageType eDigitLang);
+
+ EditPaM ReadText( SvStream& rInput, EditSelection aSel );
+ EditPaM ReadRTF( SvStream& rInput, EditSelection aSel );
+ EditPaM ReadXML( SvStream& rInput, EditSelection aSel );
+ EditPaM ReadHTML( SvStream& rInput, const OUString& rBaseURL, EditSelection aSel, SvKeyValueIterator* pHTTPHeaderAttrs );
+ ErrCode WriteText( SvStream& rOutput, EditSelection aSel );
+ ErrCode WriteRTF( SvStream& rOutput, EditSelection aSel );
+ void WriteXML(SvStream& rOutput, const EditSelection& rSel);
+
+ void WriteItemAsRTF( const SfxPoolItem& rItem, SvStream& rOutput, sal_Int32 nPara, sal_Int32 nPos,
+ std::vector<std::unique_ptr<SvxFontItem>>& rFontTable, SvxColorList& rColorList );
+ bool WriteItemListAsRTF( ItemList& rLst, SvStream& rOutput, sal_Int32 nPara, sal_Int32 nPos,
+ std::vector<std::unique_ptr<SvxFontItem>>& rFontTable, SvxColorList& rColorList );
+ sal_Int32 LogicToTwips( sal_Int32 n );
+
+ double scaleXSpacingValue(tools::Long nXValue) const
+ {
+ if (!maStatus.DoStretch() || mfSpacingScaleX == 100.0)
+ return nXValue;
+
+ return double(nXValue) * mfSpacingScaleX / 100.0;
+ }
+
+ double scaleYSpacingValue(sal_uInt16 nYValue) const
+ {
+ if (!maStatus.DoStretch() || mfSpacingScaleY == 100.0)
+ return nYValue;
+
+ return double(nYValue) * mfSpacingScaleY / 100.0;
+ }
+
+ double scaleYFontValue(sal_uInt16 nYValue) const
+ {
+ if (!maStatus.DoStretch() || (mfFontScaleY == 100.0))
+ return nYValue;
+
+ return double(nYValue) * mfFontScaleY / 100.0;
+ }
+
+ double scaleXFontValue(tools::Long nXValue) const
+ {
+ if (!maStatus.DoStretch() || (mfFontScaleX == 100.0))
+ return nXValue;
+
+ return double(nXValue) * mfFontScaleY / 100.0;
+ }
+
+ void setRoundToNearestPt(bool bRound) { mbRoundToNearestPt = bRound; }
+ double roundToNearestPt(double fInput) const;
+
+ ContentNode* GetPrevVisNode( ContentNode const * pCurNode );
+ ContentNode* GetNextVisNode( ContentNode const * pCurNode );
+
+ const ParaPortion* GetPrevVisPortion( const ParaPortion* pCurPortion ) const;
+ const ParaPortion* GetNextVisPortion( const ParaPortion* pCurPortion ) const;
+
+ void SetBackgroundColor( const Color& rColor ) { maBackgroundColor = rColor; }
+ const Color& GetBackgroundColor() const { return maBackgroundColor; }
+
+ tools::Long CalcVertLineSpacing(Point& rStartPos) const;
+
+ Color GetAutoColor() const;
+ void EnableAutoColor( bool b ) { mbUseAutoColor = b; }
+ bool IsAutoColorEnabled() const { return mbUseAutoColor; }
+ void ForceAutoColor( bool b ) { mbForceAutoColor = b; }
+ bool IsForceAutoColor() const { return mbForceAutoColor; }
+
+ inline VirtualDevice* GetVirtualDevice( const MapMode& rMapMode, DrawModeFlags nDrawMode );
+ void EraseVirtualDevice() { pVirtDev.disposeAndClear(); }
+
+ DECL_LINK( StatusTimerHdl, Timer *, void);
+ DECL_LINK( IdleFormatHdl, Timer *, void);
+ DECL_LINK( OnlineSpellHdl, Timer *, void);
+ DECL_LINK( DocModified, LinkParamNone*, void );
+
+ void CheckIdleFormatter();
+
+ inline const ParaPortion* FindParaPortion( const ContentNode* pNode ) const;
+ inline ParaPortion* FindParaPortion( ContentNode const * pNode );
+
+ css::uno::Reference< css::datatransfer::XTransferable > CreateTransferable( const EditSelection& rSelection );
+
+ void SetValidPaperSize( const Size& rSz );
+
+ css::uno::Reference < css::i18n::XBreakIterator > const & ImplGetBreakIterator() const;
+ css::uno::Reference < css::i18n::XExtendedInputSequenceChecker > const & ImplGetInputSequenceChecker() const;
+
+ void ImplUpdateOverflowingParaNum(tools::Long);
+ void ImplUpdateOverflowingLineNum(tools::Long, sal_uInt32, tools::Long);
+
+ void CreateSpellInfo( bool bMultipleDocs );
+ /// Obtains a view shell ID from the active EditView.
+ ViewShellId CreateViewShellId();
+
+ ImpEditEngine(EditEngine* pEditEngine, SfxItemPool* pPool);
+ void InitDoc(bool bKeepParaAttribs);
+ EditDoc& GetEditDoc() { return maEditDoc; }
+ const EditDoc& GetEditDoc() const { return maEditDoc; }
+
+ const ParaPortionList& GetParaPortions() const { return maParaPortionList; }
+ ParaPortionList& GetParaPortions() { return maParaPortionList; }
+
+ tools::Long Calc1ColumnTextHeight(tools::Long* pHeightNTP);
+
+ void IdleFormatAndLayout(EditView* pCurView) { aIdleFormatter.DoIdleFormat(pCurView); }
+
+protected:
+ virtual void Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) override;
+
+public:
+ virtual ~ImpEditEngine() override;
+ ImpEditEngine(const ImpEditEngine&) = delete;
+ ImpEditEngine& operator=(const ImpEditEngine&) = delete;
+
+ inline EditUndoManager& GetUndoManager();
+ inline EditUndoManager* SetUndoManager(EditUndoManager* pNew);
+
+ // @return the previous bUpdateLayout state
+ bool SetUpdateLayout( bool bUpdate, EditView* pCurView = nullptr, bool bForceUpdate = false );
+ bool IsUpdateLayout() const { return mbUpdateLayout; }
+
+ ViewsType& GetEditViews() { return aEditViews; }
+ const ViewsType& GetEditViews() const { return aEditViews; }
+
+ const Size& GetPaperSize() const { return maPaperSize; }
+ void SetPaperSize(const Size& rSize) { maPaperSize = rSize; }
+
+ void SetVertical( bool bVertical);
+ bool IsEffectivelyVertical() const { return GetEditDoc().IsEffectivelyVertical(); }
+ bool IsTopToBottom() const { return GetEditDoc().IsTopToBottom(); }
+ bool GetVertical() const { return GetEditDoc().GetVertical(); }
+ void SetRotation( TextRotation nRotation);
+ TextRotation GetRotation() const { return GetEditDoc().GetRotation(); }
+
+ void SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing);
+
+ bool IsPageOverflow( ) const;
+
+ void SetFixedCellHeight( bool bUseFixedCellHeight );
+ bool IsFixedCellHeight() const { return GetEditDoc().IsFixedCellHeight(); }
+
+ void SetDefaultHorizontalTextDirection( EEHorizontalTextDirection eHTextDir ) { eDefaultHorizontalTextDirection = eHTextDir; }
+ EEHorizontalTextDirection GetDefaultHorizontalTextDirection() const { return eDefaultHorizontalTextDirection; }
+
+
+ void InitWritingDirections( sal_Int32 nPara );
+ bool IsRightToLeft( sal_Int32 nPara ) const;
+ sal_uInt8 GetRightToLeft( sal_Int32 nPara, sal_Int32 nChar, sal_Int32* pStart = nullptr, sal_Int32* pEnd = nullptr );
+ bool HasDifferentRTLLevels( const ContentNode* pNode );
+
+ void SetTextRanger( std::unique_ptr<TextRanger> pRanger );
+ TextRanger* GetTextRanger() const { return pTextRanger.get(); }
+
+ const Size& GetMinAutoPaperSize() const { return maMinAutoPaperSize; }
+ void SetMinAutoPaperSize(const Size& rSize) { maMinAutoPaperSize = rSize; }
+
+ const Size& GetMaxAutoPaperSize() const { return maMaxAutoPaperSize; }
+ void SetMaxAutoPaperSize(const Size& rSize) { maMaxAutoPaperSize = rSize; }
+
+ void SetMinColumnWrapHeight(tools::Long nVal) { mnMinColumnWrapHeight = nVal; }
+
+ void FormatDoc();
+ void FormatFullDoc();
+ void UpdateViews( EditView* pCurView = nullptr );
+ void Paint( ImpEditView* pView, const tools::Rectangle& rRect, OutputDevice* pTargetDevice );
+ void Paint(OutputDevice& rOutDev, tools::Rectangle aClipRect, Point aStartPos, bool bStripOnly = false, Degree10 nOrientation = 0_deg10);
+
+ bool MouseButtonUp( const MouseEvent& rMouseEvent, EditView* pView );
+ bool MouseButtonDown( const MouseEvent& rMouseEvent, EditView* pView );
+ void ReleaseMouse();
+ bool MouseMove( const MouseEvent& rMouseEvent, EditView* pView );
+ bool Command(const CommandEvent& rCEvt, EditView* pView);
+
+ EditSelectionEngine& GetSelEngine() { return aSelEngine; }
+ OUString GetSelected( const EditSelection& rSel ) const;
+
+ const SfxItemSet& GetEmptyItemSet() const;
+
+ void UpdateSelections();
+
+ void EnableUndo( bool bEnable );
+ bool IsUndoEnabled() const { return mbUndoEnabled; }
+ void SetUndoMode( bool b ) { mbIsInUndo = b; }
+ bool IsInUndo() const { return mbIsInUndo; }
+
+ void SetCallParaInsertedOrDeleted( bool b ) { mbCallParaInsertedOrDeleted = b; }
+ bool IsCallParaInsertedOrDeleted() const { return mbCallParaInsertedOrDeleted; }
+
+ bool IsFormatted() const { return mbFormatted; }
+ bool IsFormatting() const { return mbIsFormatting; }
+
+ void SetText(const OUString& rText);
+ EditPaM DeleteSelected(const EditSelection& rEditSelection);
+ EditPaM InsertTextUserInput( const EditSelection& rCurEditSelection, sal_Unicode c, bool bOverwrite );
+ EditPaM InsertText(const EditSelection& aCurEditSelection, const OUString& rStr);
+ EditPaM AutoCorrect( const EditSelection& rCurEditSelection, sal_Unicode c, bool bOverwrite, vcl::Window const * pFrameWin = nullptr );
+ EditPaM DeleteLeftOrRight( const EditSelection& rEditSelection, sal_uInt8 nMode, DeleteMode nDelMode );
+ EditPaM InsertParaBreak(const EditSelection& rEditSelection);
+ EditPaM InsertLineBreak(const EditSelection& aEditSelection);
+ EditPaM InsertTab(const EditSelection& rEditSelection);
+ EditPaM InsertField(const EditSelection& rCurSel, const SvxFieldItem& rFld);
+ bool UpdateFields();
+
+ EditPaM Read(SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat, const EditSelection& rSel, SvKeyValueIterator* pHTTPHeaderAttrs = nullptr);
+ void Write(SvStream& rOutput, EETextFormat eFormat, const EditSelection& rSel);
+
+ std::unique_ptr<EditTextObject> CreateTextObject();
+ std::unique_ptr<EditTextObject> CreateTextObject(const EditSelection& rSel);
+ void SetText( const EditTextObject& rTextObject );
+ EditSelection InsertText( const EditTextObject& rTextObject, EditSelection aSel );
+
+ EditSelection const & MoveCursor( const KeyEvent& rKeyEvent, EditView* pEditView );
+
+ EditSelection MoveParagraphs( Range aParagraphs, sal_Int32 nNewPos, EditView* pCurView );
+
+ tools::Long CalcTextHeight( tools::Long* pHeightNTP );
+ sal_uInt32 GetTextHeight() const;
+ sal_uInt32 GetTextHeightNTP() const;
+ sal_uInt32 CalcTextWidth( bool bIgnoreExtraSpace);
+ sal_uInt32 CalcParaWidth( sal_Int32 nParagraph, bool bIgnoreExtraSpace );
+ sal_uInt32 CalcLineWidth( ParaPortion* pPortion, EditLine* pLine, bool bIgnoreExtraSpace);
+ sal_Int32 GetLineCount( sal_Int32 nParagraph ) const;
+ sal_Int32 GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine ) const;
+ void GetLineBoundaries( /*out*/sal_Int32& rStart, /*out*/sal_Int32& rEnd, sal_Int32 nParagraph, sal_Int32 nLine ) const;
+ sal_Int32 GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const;
+ sal_uInt16 GetLineHeight( sal_Int32 nParagraph, sal_Int32 nLine );
+ sal_uInt32 GetParaHeight( sal_Int32 nParagraph );
+
+ SfxItemSet GetAttribs( sal_Int32 nPara, sal_Int32 nStart, sal_Int32 nEnd, GetAttribsFlags nFlags = GetAttribsFlags::ALL ) const;
+ SfxItemSet GetAttribs( EditSelection aSel, EditEngineAttribs nOnlyHardAttrib = EditEngineAttribs::All );
+ void SetAttribs( EditSelection aSel, const SfxItemSet& rSet, SetAttribsMode nSpecial = SetAttribsMode::NONE, bool bSetSelection = true );
+ void RemoveCharAttribs( EditSelection aSel, EERemoveParaAttribsMode eMode, sal_uInt16 nWhich );
+ void RemoveCharAttribs( sal_Int32 nPara, sal_uInt16 nWhich = 0, bool bRemoveFeatures = false );
+ void SetFlatMode( bool bFlat );
+
+ void SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet );
+ const SfxItemSet& GetParaAttribs( sal_Int32 nPara ) const;
+
+ bool HasParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const;
+ const SfxPoolItem& GetParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const;
+ template<class T>
+ const T& GetParaAttrib( sal_Int32 nPara, TypedWhichId<T> nWhich ) const
+ {
+ return static_cast<const T&>(GetParaAttrib(nPara, sal_uInt16(nWhich)));
+ }
+
+ tools::Rectangle PaMtoEditCursor( EditPaM aPaM, GetCursorFlags nFlags = GetCursorFlags::NONE );
+ tools::Rectangle GetEditCursor(const ParaPortion* pPortion, const EditLine* pLine,
+ sal_Int32 nIndex, GetCursorFlags nFlags);
+
+ bool IsModified() const { return maEditDoc.IsModified(); }
+ void SetModifyFlag(bool b) { maEditDoc.SetModified( b ); }
+ void SetModifyHdl( const Link<LinkParamNone*,void>& rLink ) { aModifyHdl = rLink; }
+
+ bool IsInSelectionMode() const { return mbInSelection; }
+
+// For Undo/Redo
+ void Undo( EditView* pView );
+ void Redo( EditView* pView );
+
+// OV-Special
+ void InvalidateFromParagraph( sal_Int32 nFirstInvPara );
+ EditPaM InsertParagraph( sal_Int32 nPara );
+ std::optional<EditSelection> SelectParagraph( sal_Int32 nPara );
+
+ void SetStatusEventHdl( const Link<EditStatus&, void>& rLink ) { aStatusHdlLink = rLink; }
+ const Link<EditStatus&,void>& GetStatusEventHdl() const { return aStatusHdlLink; }
+
+ void SetNotifyHdl( const Link<EENotify&,void>& rLink ) { aNotifyHdl = rLink; }
+ const Link<EENotify&,void>& GetNotifyHdl() const { return aNotifyHdl; }
+
+ void FormatAndLayout( EditView* pCurView = nullptr, bool bCalledFromUndo = false );
+
+ const svtools::ColorConfig& GetColorConfig() const { return maColorConfig; }
+ static bool IsVisualCursorTravelingEnabled();
+ static bool DoVisualCursorTraveling();
+
+ EditSelection ConvertSelection( sal_Int32 nStartPara, sal_Int32 nStartPos, sal_Int32 nEndPara, sal_Int32 nEndPos );
+ inline EPaM CreateEPaM( const EditPaM& rPaM ) const;
+ inline EditPaM CreateEditPaM( const EPaM& rEPaM );
+ inline ESelection CreateESel( const EditSelection& rSel ) const;
+ inline EditSelection CreateSel( const ESelection& rSel );
+
+ void SetStyleSheetPool( SfxStyleSheetPool* pSPool );
+ SfxStyleSheetPool* GetStyleSheetPool() const { return pStylePool; }
+
+ void SetStyleSheet( EditSelection aSel, SfxStyleSheet* pStyle );
+ void SetStyleSheet( sal_Int32 nPara, SfxStyleSheet* pStyle );
+ const SfxStyleSheet* GetStyleSheet( sal_Int32 nPara ) const;
+ SfxStyleSheet* GetStyleSheet( sal_Int32 nPara );
+
+ void UpdateParagraphsWithStyleSheet( SfxStyleSheet* pStyle );
+ void RemoveStyleFromParagraphs( SfxStyleSheet const * pStyle );
+
+ bool isUsedByModel() const override { return true; }
+
+ OutputDevice* GetRefDevice() const { return pRefDev.get(); }
+ void SetRefDevice( OutputDevice* pRefDef );
+
+ const MapMode& GetRefMapMode() const { return pRefDev->GetMapMode(); }
+ void SetRefMapMode( const MapMode& rMapMode );
+
+ InternalEditStatus& GetStatus() { return maStatus; }
+ void CallStatusHdl();
+ void DelayedCallStatusHdl() { aStatusTimer.Start(); }
+
+ void UndoActionStart( sal_uInt16 nId );
+ void UndoActionStart( sal_uInt16 nId, const ESelection& rSel );
+ void UndoActionEnd();
+
+ EditView* GetActiveView() const { return pActiveView; }
+ void SetActiveView( EditView* pView );
+
+ css::uno::Reference< css::linguistic2::XSpellChecker1 > const &
+ GetSpeller();
+ void SetSpeller( css::uno::Reference< css::linguistic2::XSpellChecker1 > const &xSpl )
+ { xSpeller = xSpl; }
+ const css::uno::Reference< css::linguistic2::XHyphenator >&
+ GetHyphenator() const { return xHyphenator; }
+ void SetHyphenator( css::uno::Reference< css::linguistic2::XHyphenator > const &xHyph )
+ { xHyphenator = xHyph; }
+
+ void GetAllMisspellRanges( std::vector<editeng::MisspellRanges>& rRanges ) const;
+ void SetAllMisspellRanges( const std::vector<editeng::MisspellRanges>& rRanges );
+
+ SpellInfo* GetSpellInfo() const { return pSpellInfo.get(); }
+
+ void SetDefaultLanguage(LanguageType eLang) { meDefLanguage = eLang; }
+ LanguageType GetDefaultLanguage() const { return meDefLanguage; }
+
+ editeng::LanguageSpan GetLanguage( const EditPaM& rPaM, sal_Int32* pEndPos = nullptr ) const;
+ css::lang::Locale GetLocale( const EditPaM& rPaM ) const;
+
+ void DoOnlineSpelling( ContentNode* pThisNodeOnly = nullptr, bool bSpellAtCursorPos = false, bool bInterruptible = true );
+ EESpellState Spell(EditView* pEditView, weld::Widget* pDialogParent, bool bMultipleDoc);
+ EESpellState HasSpellErrors();
+ void ClearSpellErrors();
+ EESpellState StartThesaurus(EditView* pEditView, weld::Widget* pDialogParent);
+ css::uno::Reference< css::linguistic2::XSpellAlternatives >
+ ImpSpell( EditView* pEditView );
+
+ // text conversion functions
+ void Convert(EditView* pEditView, weld::Widget* pDialogParent, LanguageType nSrcLang, LanguageType nDestLang, const vcl::Font *pDestFont, sal_Int32 nOptions, bool bIsInteractive, bool bMultipleDoc);
+ void ImpConvert( OUString &rConvTxt, LanguageType &rConvTxtLang, EditView* pEditView, LanguageType nSrcLang, const ESelection &rConvRange,
+ bool bAllowImplicitChangesForNotConvertibleText, LanguageType nTargetLang, const vcl::Font *pTargetFont );
+ ConvInfo * GetConvInfo() const { return pConvInfo.get(); }
+ bool HasConvertibleTextPortion( LanguageType nLang );
+ void SetLanguageAndFont( const ESelection &rESel,
+ LanguageType nLang, sal_uInt16 nLangWhichId,
+ const vcl::Font *pFont, sal_uInt16 nFontWhichId );
+
+ // returns true if input sequence checking should be applied
+ bool IsInputSequenceCheckingRequired( sal_Unicode nChar, const EditSelection& rCurSel ) const;
+
+ //find the next error within the given selection - forward only!
+ css::uno::Reference< css::linguistic2::XSpellAlternatives >
+ ImpFindNextError(EditSelection& rSelection);
+ //spell and return a sentence
+ bool SpellSentence(EditView const & rView, svx::SpellPortions& rToFill );
+ //put spelling back to start of current sentence - needed after switch of grammar support
+ void PutSpellingToSentenceStart( EditView const & rEditView );
+ //applies a changed sentence
+ void ApplyChangedSentence(EditView const & rEditView, const svx::SpellPortions& rNewPortions, bool bRecheck );
+ //adds one or more portions of text to the SpellPortions depending on language changes
+ void AddPortionIterated(
+ EditView const & rEditView,
+ const EditSelection &rSel,
+ const css::uno::Reference< css::linguistic2::XSpellAlternatives >& xAlt,
+ svx::SpellPortions& rToFill);
+ //adds one portion to the SpellPortions
+ void AddPortion(
+ const EditSelection &rSel,
+ const css::uno::Reference< css::linguistic2::XSpellAlternatives >& xAlt,
+ svx::SpellPortions& rToFill,
+ bool bIsField );
+
+ bool Search( const SvxSearchItem& rSearchItem, EditView* pView );
+ bool ImpSearch( const SvxSearchItem& rSearchItem, const EditSelection& rSearchSelection, const EditPaM& rStartPos, EditSelection& rFoundSel );
+ sal_Int32 StartSearchAndReplace( EditView* pEditView, const SvxSearchItem& rSearchItem );
+ bool HasText( const SvxSearchItem& rSearchItem );
+
+ void SetEditTextObjectPool( SfxItemPool* pP ) { pTextObjectPool = pP; }
+ SfxItemPool* GetEditTextObjectPool() const { return pTextObjectPool; }
+
+ const SvxNumberFormat * GetNumberFormat( const ContentNode* pNode ) const;
+ sal_Int32 GetSpaceBeforeAndMinLabelWidth( const ContentNode *pNode, sal_Int32 *pnSpaceBefore = nullptr, sal_Int32 *pnMinLabelWidth = nullptr ) const;
+
+ const SvxLRSpaceItem& GetLRSpaceItem( ContentNode* pNode );
+ SvxAdjust GetJustification( sal_Int32 nPara ) const;
+ SvxCellJustifyMethod GetJustifyMethod( sal_Int32 nPara ) const;
+ SvxCellVerJustify GetVerJustification( sal_Int32 nPara ) const;
+
+ void setScale(double fFontScaleX, double fFontScaleY, double fSpacingScaleX, double fSpacingScaleY);
+
+ void getFontScale(double& rX, double& rY) const
+ {
+ rX = mfFontScaleX;
+ rY = mfFontScaleY;
+ }
+
+ void getSpacingScale(double& rX, double& rY) const
+ {
+ rX = mfSpacingScaleX;
+ rY = mfSpacingScaleY;
+ }
+
+ sal_Int32 GetBigTextObjectStart() const { return mnBigTextObjectStart; }
+
+ EditEngine* GetEditEnginePtr() const { return pEditEngine; }
+
+ void StartOnlineSpellTimer() { aOnlineSpellTimer.Start(); }
+ void StopOnlineSpellTimer() { aOnlineSpellTimer.Stop(); }
+
+ const OUString& GetAutoCompleteText() const { return maAutoCompleteText; }
+ void SetAutoCompleteText(const OUString& rStr, bool bUpdateTipWindow);
+
+ EditSelection TransliterateText( const EditSelection& rSelection, TransliterationFlags nTransliterationMode );
+ short ReplaceTextOnly( ContentNode* pNode, sal_Int32 nCurrentStart, std::u16string_view rText, const css::uno::Sequence< sal_Int32 >& rOffsets );
+
+ void SetAsianCompressionMode( CharCompressType n );
+ CharCompressType GetAsianCompressionMode() const { return mnAsianCompressionMode; }
+
+ void SetKernAsianPunctuation( bool b );
+ bool IsKernAsianPunctuation() const { return mbKernAsianPunctuation; }
+
+ sal_Int32 GetOverflowingParaNum() const { return mnOverflowingPara; }
+ sal_Int32 GetOverflowingLineNum() const { return mnOverflowingLine; }
+ void ClearOverflowingParaNum() { mnOverflowingPara = -1; }
+
+
+ void SetAddExtLeading( bool b );
+ bool IsAddExtLeading() const { return mbAddExtLeading; }
+
+ static std::shared_ptr<SvxForbiddenCharactersTable> const & GetForbiddenCharsTable();
+ static void SetForbiddenCharsTable( const std::shared_ptr<SvxForbiddenCharactersTable>& xForbiddenChars );
+
+ /** sets a link that is called at the beginning of a drag operation at an edit view */
+ void SetBeginDropHdl( const Link<EditView*,void>& rLink ) { maBeginDropHdl = rLink; }
+ const Link<EditView*,void>& GetBeginDropHdl() const { return maBeginDropHdl; }
+
+ /** sets a link that is called at the end of a drag operation at an edit view */
+ void SetEndDropHdl( const Link<EditView*,void>& rLink ) { maEndDropHdl = rLink; }
+ const Link<EditView*,void>& GetEndDropHdl() const { return maEndDropHdl; }
+
+ /// specifies if auto-correction should capitalize the first word or not (default is on)
+ void SetFirstWordCapitalization( bool bCapitalize ) { mbFirstWordCapitalization = bCapitalize; }
+ bool IsFirstWordCapitalization() const { return mbFirstWordCapitalization; }
+
+ /** specifies if auto-correction should replace a leading single quotation
+ mark (apostrophe) or not (default is on) */
+ void SetReplaceLeadingSingleQuotationMark( bool bReplace ) { mbReplaceLeadingSingleQuotationMark = bReplace; }
+ bool IsReplaceLeadingSingleQuotationMark() const { return mbReplaceLeadingSingleQuotationMark; }
+
+ /** Whether last AutoCorrect inserted a NO-BREAK SPACE that may need to be removed again. */
+ bool IsNbspRunNext() const { return mbNbspRunNext; }
+
+ void EnableSkipOutsideFormat(bool set) { mbSkipOutsideFormat = set; }
+
+ void Dispose();
+ void SetLOKSpecialPaperSize(const Size& rSize) { aLOKSpecialPaperSize = rSize; }
+ const Size& GetLOKSpecialPaperSize() const { return aLOKSpecialPaperSize; }
+
+ enum class CallbackResult
+ {
+ Continue,
+ SkipThisPortion, // Do not call callback until next portion
+ Stop, // Stop iteration
+ };
+ struct LineAreaInfo
+ {
+ ParaPortion& rPortion; // Current ParaPortion
+ EditLine* pLine; // Current line, or nullptr for paragraph start
+ tools::Long nHeightNeededToNotWrap;
+ tools::Rectangle aArea; // The area for the line (or for rPortion's first line offset)
+ // Bottom coordinate *does not* belong to the area
+ sal_Int32 nPortion;
+ sal_Int32 nLine;
+ sal_Int16 nColumn; // Column number; when overflowing, equal to total number of columns
+ };
+ using IterateLinesAreasFunc = std::function<CallbackResult(const LineAreaInfo&)>;
+ enum IterFlag // bitmask
+ {
+ none = 0,
+ inclILS = 1, // rArea includes interline space
+ };
+
+ void IterateLineAreas(const IterateLinesAreasFunc& f, IterFlag eOptions);
+
+ tools::Long GetColumnWidth(const Size& rPaperSize) const;
+ Point MoveToNextLine(Point& rMovePos, tools::Long nLineHeight, sal_Int16& nColumn,
+ Point aOrigin, tools::Long* pnHeightNeededToNotWrap = nullptr) const;
+
+ tools::Long getWidthDirectionAware(const Size& sz) const;
+ tools::Long getHeightDirectionAware(const Size& sz) const;
+ void adjustXDirectionAware(Point& pt, tools::Long x) const;
+ void adjustYDirectionAware(Point& pt, tools::Long y) const;
+ void setXDirectionAwareFrom(Point& ptDest, const Point& ptSrc) const;
+ void setYDirectionAwareFrom(Point& ptDest, const Point& ptSrc) const;
+ tools::Long getYOverflowDirectionAware(const Point& pt, const tools::Rectangle& rectMax) const;
+ bool isXOverflowDirectionAware(const Point& pt, const tools::Rectangle& rectMax) const;
+ // Offset of the rectangle's direction-aware corners in document coordinates
+ tools::Long getBottomDocOffset(const tools::Rectangle& rect) const;
+ Size getTopLeftDocOffset(const tools::Rectangle& rect) const;
+};
+
+inline EPaM ImpEditEngine::CreateEPaM( const EditPaM& rPaM ) const
+{
+ const ContentNode* pNode = rPaM.GetNode();
+ return EPaM(maEditDoc.GetPos(pNode), rPaM.GetIndex());
+}
+
+inline EditPaM ImpEditEngine::CreateEditPaM( const EPaM& rEPaM )
+{
+ DBG_ASSERT( rEPaM.nPara < maEditDoc.Count(), "CreateEditPaM: invalid paragraph" );
+ DBG_ASSERT( maEditDoc[ rEPaM.nPara ]->Len() >= rEPaM.nIndex, "CreateEditPaM: invalid Index" );
+ return EditPaM( maEditDoc[ rEPaM.nPara], rEPaM.nIndex );
+}
+
+inline ESelection ImpEditEngine::CreateESel( const EditSelection& rSel ) const
+{
+ const ContentNode* pStartNode = rSel.Min().GetNode();
+ const ContentNode* pEndNode = rSel.Max().GetNode();
+ ESelection aESel;
+ aESel.nStartPara = maEditDoc.GetPos( pStartNode );
+ aESel.nStartPos = rSel.Min().GetIndex();
+ aESel.nEndPara = maEditDoc.GetPos( pEndNode );
+ aESel.nEndPos = rSel.Max().GetIndex();
+ return aESel;
+}
+
+inline EditSelection ImpEditEngine::CreateSel( const ESelection& rSel )
+{
+ DBG_ASSERT( rSel.nStartPara < maEditDoc.Count(), "CreateSel: invalid start paragraph" );
+ DBG_ASSERT( rSel.nEndPara < maEditDoc.Count(), "CreateSel: invalid end paragraph" );
+ EditSelection aSel;
+ aSel.Min().SetNode( maEditDoc[ rSel.nStartPara ] );
+ aSel.Min().SetIndex( rSel.nStartPos );
+ aSel.Max().SetNode( maEditDoc[ rSel.nEndPara ] );
+ aSel.Max().SetIndex( rSel.nEndPos );
+ DBG_ASSERT( !aSel.DbgIsBuggy( maEditDoc ), "CreateSel: incorrect selection!" );
+ return aSel;
+}
+
+inline VirtualDevice* ImpEditEngine::GetVirtualDevice( const MapMode& rMapMode, DrawModeFlags nDrawMode )
+{
+ if ( !pVirtDev )
+ pVirtDev = VclPtr<VirtualDevice>::Create();
+
+ if ( ( pVirtDev->GetMapMode().GetMapUnit() != rMapMode.GetMapUnit() ) ||
+ ( pVirtDev->GetMapMode().GetScaleX() != rMapMode.GetScaleX() ) ||
+ ( pVirtDev->GetMapMode().GetScaleY() != rMapMode.GetScaleY() ) )
+ {
+ MapMode aMapMode( rMapMode );
+ aMapMode.SetOrigin( Point( 0, 0 ) );
+ pVirtDev->SetMapMode( aMapMode );
+ }
+
+ pVirtDev->SetDrawMode( nDrawMode );
+
+ return pVirtDev;
+}
+
+inline EditUndoManager& ImpEditEngine::GetUndoManager()
+{
+ if ( !pUndoManager )
+ {
+ pUndoManager = new EditUndoManager();
+ pUndoManager->SetEditEngine(pEditEngine);
+ }
+ return *pUndoManager;
+}
+
+inline EditUndoManager* ImpEditEngine::SetUndoManager(EditUndoManager* pNew)
+{
+ EditUndoManager* pRetval = pUndoManager;
+
+ if(pUndoManager)
+ {
+ pUndoManager->SetEditEngine(nullptr);
+ }
+
+ pUndoManager = pNew;
+
+ if(pUndoManager)
+ {
+ pUndoManager->SetEditEngine(pEditEngine);
+ }
+
+ return pRetval;
+}
+
+inline const ParaPortion* ImpEditEngine::FindParaPortion( const ContentNode* pNode ) const
+{
+ sal_Int32 nPos = maEditDoc.GetPos( pNode );
+ DBG_ASSERT( nPos < GetParaPortions().Count(), "Portionloser Node?" );
+ return GetParaPortions()[ nPos ];
+}
+
+inline ParaPortion* ImpEditEngine::FindParaPortion( ContentNode const * pNode )
+{
+ sal_Int32 nPos = maEditDoc.GetPos( pNode );
+ DBG_ASSERT( nPos < GetParaPortions().Count(), "Portionloser Node?" );
+ return GetParaPortions()[ nPos ];
+}
+
+inline PointerStyle ImpEditView::GetPointer()
+{
+ if ( !mxPointer )
+ {
+ mxPointer = IsVertical() ? PointerStyle::TextVertical : PointerStyle::Text;
+ return *mxPointer;
+ }
+
+ if(PointerStyle::Text == *mxPointer && IsVertical())
+ {
+ mxPointer = PointerStyle::TextVertical;
+ }
+ else if(PointerStyle::TextVertical == *mxPointer && !IsVertical())
+ {
+ mxPointer = PointerStyle::Text;
+ }
+
+ return *mxPointer;
+}
+
+inline vcl::Cursor* ImpEditView::GetCursor()
+{
+ if ( !pCursor )
+ pCursor.reset( new vcl::Cursor );
+ return pCursor.get();
+}
+
+void ConvertItem( std::unique_ptr<SfxPoolItem>& rPoolItem, MapUnit eSourceUnit, MapUnit eDestUnit );
+void ConvertAndPutItems( SfxItemSet& rDest, const SfxItemSet& rSource, const MapUnit* pSourceUnit = nullptr, const MapUnit* pDestUnit = nullptr );
+AsianCompressionFlags GetCharTypeForCompression( sal_Unicode cChar );
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/impedit2.cxx b/editeng/source/editeng/impedit2.cxx
new file mode 100644
index 0000000000..4b8f0a6379
--- /dev/null
+++ b/editeng/source/editeng/impedit2.cxx
@@ -0,0 +1,4509 @@
+/* -*- 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 <vcl/svapp.hxx>
+#include <vcl/window.hxx>
+#include <editeng/lspcitem.hxx>
+#include <editeng/flditem.hxx>
+#include "impedit.hxx"
+#include <editeng/editeng.hxx>
+#include <editeng/editview.hxx>
+#include <eerdll2.hxx>
+#include <editeng/eerdll.hxx>
+#include <edtspell.hxx>
+#include "eeobj.hxx"
+#include <editeng/txtrange.hxx>
+#include <sfx2/app.hxx>
+#include <sfx2/mieclip.hxx>
+#include <svtools/colorcfg.hxx>
+#include <svl/ctloptions.hxx>
+#include <unotools/securityoptions.hxx>
+#include <editeng/acorrcfg.hxx>
+#include <editeng/lrspitem.hxx>
+#include <editeng/ulspitem.hxx>
+#include <editeng/adjustitem.hxx>
+#include <editeng/frmdiritem.hxx>
+#include <editeng/justifyitem.hxx>
+#include <editeng/udlnitem.hxx>
+
+#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
+#include <com/sun/star/i18n/WordType.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <com/sun/star/lang/Locale.hpp>
+#include <com/sun/star/i18n/InputSequenceCheckMode.hpp>
+#include <com/sun/star/system/SystemShellExecute.hpp>
+#include <com/sun/star/system/SystemShellExecuteFlags.hpp>
+#include <com/sun/star/system/XSystemShellExecute.hpp>
+#include <com/sun/star/i18n/UnicodeType.hpp>
+
+#include <rtl/character.hxx>
+
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <sot/exchange.hxx>
+#include <sot/formats.hxx>
+#include <svl/asiancfg.hxx>
+#include <svl/voiditem.hxx>
+#include <i18nutil/unicode.hxx>
+#include <comphelper/diagnose_ex.hxx>
+#include <comphelper/flagguard.hxx>
+#include <comphelper/lok.hxx>
+#include <comphelper/processfactory.hxx>
+#include <unotools/configmgr.hxx>
+
+#include <unicode/ubidi.h>
+#include <algorithm>
+#include <limits>
+#include <memory>
+#include <string_view>
+#include <fstream>
+
+using namespace ::com::sun::star;
+
+static sal_uInt16 lcl_CalcExtraSpace( const SvxLineSpacingItem& rLSItem )
+{
+ sal_uInt16 nExtra = 0;
+ if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix )
+ {
+ nExtra = rLSItem.GetInterLineSpace();
+ }
+
+ return nExtra;
+}
+
+ImpEditEngine::ImpEditEngine( EditEngine* pEE, SfxItemPool* pItemPool ) :
+ pSharedVCL(EditDLL::Get().GetSharedVclResources()),
+ maPaperSize( 0x7FFFFFFF, 0x7FFFFFFF ),
+ maMinAutoPaperSize( 0x0, 0x0 ),
+ maMaxAutoPaperSize( 0x7FFFFFFF, 0x7FFFFFFF ),
+ maEditDoc( pItemPool ),
+ pEditEngine(pEE),
+ pActiveView(nullptr),
+ pStylePool(nullptr),
+ pTextObjectPool(nullptr),
+ pUndoManager(nullptr),
+ aWordDelimiters(" .,;:-`'?!_=\"{}()[]"),
+ maBackgroundColor(COL_AUTO),
+ mfFontScaleX(100.0),
+ mfFontScaleY(100.0),
+ mfSpacingScaleX(100.0),
+ mfSpacingScaleY(100.0),
+ mbRoundToNearestPt(false),
+ mnAsianCompressionMode(CharCompressType::NONE),
+ eDefaultHorizontalTextDirection(EEHorizontalTextDirection::Default),
+ mnBigTextObjectStart(20),
+ meDefLanguage(LANGUAGE_DONTKNOW),
+ nCurTextHeight(0),
+ nCurTextHeightNTP(0),
+ aOnlineSpellTimer( "editeng::ImpEditEngine aOnlineSpellTimer" ),
+ aStatusTimer( "editeng::ImpEditEngine aStatusTimer" ),
+ mbKernAsianPunctuation(false),
+ mbAddExtLeading(false),
+ mbIsFormatting(false),
+ mbFormatted(false),
+ mbInSelection(false),
+ mbIsInUndo(false),
+ mbUpdateLayout(true),
+ mbUndoEnabled(true),
+ mbDowning(false),
+ mbUseAutoColor(true),
+ mbForceAutoColor(false),
+ mbCallParaInsertedOrDeleted(false),
+ mbFirstWordCapitalization(true),
+ mbLastTryMerge(false),
+ mbReplaceLeadingSingleQuotationMark(true),
+ mbSkipOutsideFormat(false),
+ mbFuzzing(utl::ConfigManager::IsFuzzing()),
+ mbNbspRunNext(false)
+{
+ maStatus.GetControlWord() = EEControlBits::USECHARATTRIBS | EEControlBits::DOIDLEFORMAT |
+ EEControlBits::PASTESPECIAL | EEControlBits::UNDOATTRIBS |
+ EEControlBits::ALLOWBIGOBJS | EEControlBits::RTFSTYLESHEETS |
+ EEControlBits::FORMAT100;
+
+ aSelEngine.SetFunctionSet( &aSelFuncSet );
+
+ aStatusTimer.SetTimeout( 200 );
+ aStatusTimer.SetInvokeHandler( LINK( this, ImpEditEngine, StatusTimerHdl ) );
+
+ aIdleFormatter.SetPriority( TaskPriority::REPAINT );
+ aIdleFormatter.SetInvokeHandler( LINK( this, ImpEditEngine, IdleFormatHdl ) );
+
+ aOnlineSpellTimer.SetTimeout( 100 );
+ aOnlineSpellTimer.SetInvokeHandler( LINK( this, ImpEditEngine, OnlineSpellHdl ) );
+
+ // Access data already from here on!
+ SetRefDevice( nullptr );
+ InitDoc( false );
+
+ mbCallParaInsertedOrDeleted = true;
+
+ maEditDoc.SetModifyHdl( LINK( this, ImpEditEngine, DocModified ) );
+ StartListening(*SfxGetpApp());
+}
+
+void ImpEditEngine::Dispose()
+{
+ SolarMutexGuard g;
+ auto pApp = SfxApplication::Get();
+ if(pApp)
+ EndListening(*pApp);
+ pVirtDev.disposeAndClear();
+ mpOwnDev.disposeAndClear();
+ pSharedVCL.reset();
+}
+
+ImpEditEngine::~ImpEditEngine()
+{
+ aStatusTimer.Stop();
+ aOnlineSpellTimer.Stop();
+ aIdleFormatter.Stop();
+
+ // Destroying templates may otherwise cause unnecessary formatting,
+ // when a parent template is destroyed.
+ // And this after the destruction of the data!
+ mbDowning = true;
+ SetUpdateLayout( false );
+
+ Dispose();
+ // it's only legal to delete the pUndoManager if it was created by
+ // ImpEditEngine; if it was set by SetUndoManager() it must be cleared
+ // before destroying the ImpEditEngine!
+ assert(!pUndoManager || typeid(*pUndoManager) == typeid(EditUndoManager));
+ delete pUndoManager;
+ pTextRanger.reset();
+ mpIMEInfos.reset();
+ pSpellInfo.reset();
+}
+
+void ImpEditEngine::SetRefDevice( OutputDevice* pRef )
+{
+ if (pRef)
+ pRefDev = pRef;
+ else
+ pRefDev = pSharedVCL->GetVirtualDevice();
+
+ nOnePixelInRef = static_cast<sal_uInt16>(pRefDev->PixelToLogic( Size( 1, 0 ) ).Width());
+
+ if ( IsFormatted() )
+ {
+ FormatFullDoc();
+ UpdateViews();
+ }
+}
+
+void ImpEditEngine::SetRefMapMode( const MapMode& rMapMode )
+{
+ if ( GetRefDevice()->GetMapMode() == rMapMode )
+ return;
+
+ mpOwnDev.disposeAndClear();
+ mpOwnDev = VclPtr<VirtualDevice>::Create();
+ pRefDev = mpOwnDev;
+ pRefDev->SetMapMode(MapMode(MapUnit::MapTwip));
+ SetRefDevice( pRefDev );
+
+ pRefDev->SetMapMode( rMapMode );
+ nOnePixelInRef = static_cast<sal_uInt16>(pRefDev->PixelToLogic( Size( 1, 0 ) ).Width());
+ if ( IsFormatted() )
+ {
+ FormatFullDoc();
+ UpdateViews();
+ }
+}
+
+void ImpEditEngine::InitDoc(bool bKeepParaAttribs)
+{
+ sal_Int32 nParas = maEditDoc.Count();
+ for ( sal_Int32 n = bKeepParaAttribs ? 1 : 0; n < nParas; n++ )
+ {
+ if ( maEditDoc[n]->GetStyleSheet() )
+ EndListening( *maEditDoc[n]->GetStyleSheet() );
+ }
+
+ if ( bKeepParaAttribs )
+ maEditDoc.RemoveText();
+ else
+ maEditDoc.Clear();
+
+ GetParaPortions().Reset();
+
+ GetParaPortions().Insert(0, std::make_unique<ParaPortion>( maEditDoc[0] ));
+
+ mbFormatted = false;
+
+ if ( IsCallParaInsertedOrDeleted() )
+ {
+ GetEditEnginePtr()->ParagraphDeleted( EE_PARA_ALL );
+ GetEditEnginePtr()->ParagraphInserted( 0 );
+ }
+
+ if ( GetStatus().DoOnlineSpelling() )
+ maEditDoc.GetObject( 0 )->CreateWrongList();
+}
+
+EditPaM ImpEditEngine::DeleteSelected(const EditSelection& rSel)
+{
+ EditPaM aPaM (ImpDeleteSelection(rSel));
+ return aPaM;
+}
+
+OUString ImpEditEngine::GetSelected( const EditSelection& rSel ) const
+{
+ if ( !rSel.HasRange() )
+ return OUString();
+
+ EditSelection aSel( rSel );
+ aSel.Adjust( maEditDoc );
+
+ ContentNode* pStartNode = aSel.Min().GetNode();
+ ContentNode* pEndNode = aSel.Max().GetNode();
+ sal_Int32 nStartNode = maEditDoc.GetPos( pStartNode );
+ sal_Int32 nEndNode = maEditDoc.GetPos( pEndNode );
+
+ OSL_ENSURE( nStartNode <= nEndNode, "Selection not sorted ?" );
+
+ OUStringBuffer aText(256);
+ const OUString aSep = EditDoc::GetSepStr( LINEEND_LF );
+
+ // iterate over the paragraphs ...
+ for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ )
+ {
+ const ContentNode* pNode = maEditDoc.GetObject( nNode );
+ assert(pNode);
+
+ const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0;
+ const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // can also be == nStart!
+
+ aText.append(EditDoc::GetParaAsString( pNode, nStartPos, nEndPos ));
+ if ( nNode < nEndNode )
+ aText.append(aSep);
+ }
+ return aText.makeStringAndClear();
+}
+
+bool ImpEditEngine::MouseButtonDown( const MouseEvent& rMEvt, EditView* pView )
+{
+ GetSelEngine().SetCurView( pView );
+ SetActiveView( pView );
+
+ if (!GetAutoCompleteText().isEmpty())
+ SetAutoCompleteText( OUString(), true );
+
+ GetSelEngine().SelMouseButtonDown( rMEvt );
+ // Special treatment
+ EditSelection aCurSel( pView->pImpEditView->GetEditSelection() );
+ if ( rMEvt.IsShift() )
+ return true;
+
+ if ( rMEvt.GetClicks() == 2 )
+ {
+ // So that the SelectionEngine knows about the anchor.
+ aSelEngine.CursorPosChanging( true, false );
+
+ EditSelection aNewSelection( SelectWord( aCurSel ) );
+ pView->pImpEditView->DrawSelectionXOR();
+ pView->pImpEditView->SetEditSelection( aNewSelection );
+ pView->pImpEditView->DrawSelectionXOR();
+ pView->ShowCursor();
+ }
+ else if ( rMEvt.GetClicks() == 3 )
+ {
+ // So that the SelectionEngine knows about the anchor.
+ aSelEngine.CursorPosChanging( true, false );
+
+ EditSelection aNewSelection( aCurSel );
+ aNewSelection.Min().SetIndex( 0 );
+ aNewSelection.Max().SetIndex( aCurSel.Min().GetNode()->Len() );
+ pView->pImpEditView->DrawSelectionXOR();
+ pView->pImpEditView->SetEditSelection( aNewSelection );
+ pView->pImpEditView->DrawSelectionXOR();
+ pView->ShowCursor();
+ }
+ return true;
+}
+
+bool ImpEditEngine::Command( const CommandEvent& rCEvt, EditView* pView )
+{
+ bool bConsumed = true;
+
+ GetSelEngine().SetCurView( pView );
+ SetActiveView( pView );
+ if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput )
+ {
+ pView->DeleteSelected();
+ mpIMEInfos.reset();
+ EditPaM aPaM = pView->GetImpEditView()->GetEditSelection().Max();
+ OUString aOldTextAfterStartPos = aPaM.GetNode()->Copy( aPaM.GetIndex() );
+ sal_Int32 nMax = aOldTextAfterStartPos.indexOf( CH_FEATURE );
+ if ( nMax != -1 ) // don't overwrite features!
+ aOldTextAfterStartPos = aOldTextAfterStartPos.copy( 0, nMax );
+ mpIMEInfos.reset( new ImplIMEInfos( aPaM, aOldTextAfterStartPos ) );
+ mpIMEInfos->bWasCursorOverwrite = !pView->IsInsertMode();
+ UndoActionStart( EDITUNDO_INSERT );
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
+ {
+ OSL_ENSURE( mpIMEInfos, "CommandEventId::EndExtTextInput => No start ?" );
+ if( mpIMEInfos )
+ {
+ // #102812# convert quotes in IME text
+ // works on the last input character, this is especially in Korean text often done
+ // quotes that are inside of the string are not replaced!
+ // Borrowed from sw: edtwin.cxx
+ if ( mpIMEInfos->nLen )
+ {
+ EditSelection aSel( mpIMEInfos->aPos );
+ aSel.Min().SetIndex( aSel.Min().GetIndex() + mpIMEInfos->nLen-1 );
+ aSel.Max().SetIndex( aSel.Max().GetIndex() + mpIMEInfos->nLen );
+ // #102812# convert quotes in IME text
+ // works on the last input character, this is especially in Korean text often done
+ // quotes that are inside of the string are not replaced!
+ // See also tdf#155350
+ const sal_Unicode nCharCode = aSel.Min().GetNode()->GetChar( aSel.Min().GetIndex() );
+ if ( ( GetStatus().DoAutoCorrect() ) && SvxAutoCorrect::IsAutoCorrectChar(nCharCode) )
+ {
+ aSel = DeleteSelected( aSel );
+ aSel = AutoCorrect( aSel, nCharCode, mpIMEInfos->bWasCursorOverwrite );
+ pView->pImpEditView->SetEditSelection( aSel );
+ }
+ }
+
+ ParaPortion* pPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() );
+ if (pPortion)
+ pPortion->MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex() );
+
+ bool bWasCursorOverwrite = mpIMEInfos->bWasCursorOverwrite;
+
+ mpIMEInfos.reset();
+
+ FormatAndLayout( pView );
+
+ pView->SetInsertMode( !bWasCursorOverwrite );
+ }
+ UndoActionEnd();
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput )
+ {
+ OSL_ENSURE( mpIMEInfos, "CommandEventId::ExtTextInput => No Start ?" );
+ if( mpIMEInfos )
+ {
+ const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData();
+
+ if ( !pData->IsOnlyCursorChanged() )
+ {
+ EditSelection aSel( mpIMEInfos->aPos );
+ aSel.Max().SetIndex( aSel.Max().GetIndex() + mpIMEInfos->nLen );
+ aSel = DeleteSelected( aSel );
+ aSel = ImpInsertText( aSel, pData->GetText() );
+
+ if ( mpIMEInfos->bWasCursorOverwrite )
+ {
+ sal_Int32 nOldIMETextLen = mpIMEInfos->nLen;
+ sal_Int32 nNewIMETextLen = pData->GetText().getLength();
+
+ if ( ( nOldIMETextLen > nNewIMETextLen ) &&
+ ( nNewIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
+ {
+ // restore old characters
+ sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen;
+ EditPaM aPaM( mpIMEInfos->aPos );
+ aPaM.SetIndex( aPaM.GetIndex() + nNewIMETextLen );
+ ImpInsertText( aPaM, mpIMEInfos->aOldTextAfterStartPos.copy( nNewIMETextLen, nRestore ) );
+ }
+ else if ( ( nOldIMETextLen < nNewIMETextLen ) &&
+ ( nOldIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
+ {
+ // overwrite
+ sal_Int32 nOverwrite = nNewIMETextLen - nOldIMETextLen;
+ if ( ( nOldIMETextLen + nOverwrite ) > mpIMEInfos->aOldTextAfterStartPos.getLength() )
+ nOverwrite = mpIMEInfos->aOldTextAfterStartPos.getLength() - nOldIMETextLen;
+ OSL_ENSURE( nOverwrite && (nOverwrite < 0xFF00), "IME Overwrite?!" );
+ EditPaM aPaM( mpIMEInfos->aPos );
+ aPaM.SetIndex( aPaM.GetIndex() + nNewIMETextLen );
+ EditSelection _aSel( aPaM );
+ _aSel.Max().SetIndex( _aSel.Max().GetIndex() + nOverwrite );
+ DeleteSelected( _aSel );
+ }
+ }
+ if ( pData->GetTextAttr() )
+ {
+ mpIMEInfos->CopyAttribs( pData->GetTextAttr(), pData->GetText().getLength() );
+ }
+ else
+ {
+ mpIMEInfos->DestroyAttribs();
+ mpIMEInfos->nLen = pData->GetText().getLength();
+ }
+
+ ParaPortion* pPortion = FindParaPortion( mpIMEInfos->aPos.GetNode() );
+ pPortion->MarkSelectionInvalid( mpIMEInfos->aPos.GetIndex() );
+ FormatAndLayout( pView );
+ }
+
+ EditSelection aNewSel = EditPaM( mpIMEInfos->aPos.GetNode(), mpIMEInfos->aPos.GetIndex()+pData->GetCursorPos() );
+ pView->SetSelection( CreateESel( aNewSel ) );
+ pView->SetInsertMode( !pData->IsCursorOverwrite() );
+
+ if ( pData->IsCursorVisible() )
+ pView->ShowCursor();
+ else
+ pView->HideCursor();
+ }
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::InputContextChange )
+ {
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::CursorPos )
+ {
+ if (mpIMEInfos)
+ {
+ EditPaM aPaM( pView->pImpEditView->GetEditSelection().Max() );
+ tools::Rectangle aR1 = PaMtoEditCursor( aPaM );
+
+ sal_Int32 nInputEnd = mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen;
+
+ if ( !IsFormatted() )
+ FormatDoc();
+
+ ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( GetEditDoc().GetPos( aPaM.GetNode() ) );
+ if (pParaPortion)
+ {
+ sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), true );
+ const EditLine& rLine = pParaPortion->GetLines()[nLine];
+ if ( nInputEnd > rLine.GetEnd() )
+ nInputEnd = rLine.GetEnd();
+ tools::Rectangle aR2 = PaMtoEditCursor( EditPaM( aPaM.GetNode(), nInputEnd ), GetCursorFlags::EndOfLine );
+ tools::Rectangle aRect = pView->GetImpEditView()->GetWindowPos( aR1 );
+ auto nExtTextInputWidth = aR2.Left() - aR1.Right();
+ if (EditViewCallbacks* pEditViewCallbacks = pView->getEditViewCallbacks())
+ pEditViewCallbacks->EditViewCursorRect(aRect, nExtTextInputWidth);
+ else if (vcl::Window* pWindow = pView->GetWindow())
+ pWindow->SetCursorRect(&aRect, nExtTextInputWidth);
+ }
+ }
+ else
+ {
+ if (vcl::Window* pWindow = pView->GetWindow())
+ pWindow->SetCursorRect();
+ }
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::SelectionChange )
+ {
+ const CommandSelectionChangeData *pData = rCEvt.GetSelectionChangeData();
+
+ ESelection aSelection = pView->GetSelection();
+ aSelection.Adjust();
+
+ if( pView->HasSelection() )
+ {
+ aSelection.nEndPos = aSelection.nStartPos;
+ aSelection.nStartPos += pData->GetStart();
+ aSelection.nEndPos += pData->GetEnd();
+ }
+ else
+ {
+ aSelection.nStartPos = pData->GetStart();
+ aSelection.nEndPos = pData->GetEnd();
+ }
+ pView->SetSelection( aSelection );
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::PrepareReconversion )
+ {
+ if ( pView->HasSelection() )
+ {
+ ESelection aSelection = pView->GetSelection();
+ aSelection.Adjust();
+
+ if ( aSelection.nStartPara != aSelection.nEndPara )
+ {
+ sal_Int32 aParaLen = pEditEngine->GetTextLen( aSelection.nStartPara );
+ aSelection.nEndPara = aSelection.nStartPara;
+ aSelection.nEndPos = aParaLen;
+ pView->SetSelection( aSelection );
+ }
+ }
+ }
+ else if ( rCEvt.GetCommand() == CommandEventId::QueryCharPosition )
+ {
+ if (mpIMEInfos)
+ {
+ EditPaM aPaM( pView->pImpEditView->GetEditSelection().Max() );
+ if ( !IsFormatted() )
+ FormatDoc();
+
+ sal_Int32 nPortionPos = GetEditDoc().GetPos(aPaM.GetNode());
+ ParaPortion* pParaPortion = GetParaPortions().SafeGetObject(nPortionPos);
+ if (pParaPortion)
+ {
+ const sal_Int32 nMinPos = mpIMEInfos->aPos.GetIndex();
+ const sal_Int32 nMaxPos = nMinPos + mpIMEInfos->nLen - 1;
+ std::vector<tools::Rectangle> aRects(mpIMEInfos->nLen);
+
+ auto CollectCharPositions = [&](const LineAreaInfo& rInfo) {
+ if (!rInfo.pLine) // Start of ParaPortion
+ {
+ if (rInfo.nPortion < nPortionPos)
+ return CallbackResult::SkipThisPortion;
+ if (rInfo.nPortion > nPortionPos)
+ return CallbackResult::Stop;
+ assert(&rInfo.rPortion == pParaPortion);
+ }
+ else // This is the needed ParaPortion
+ {
+ if (rInfo.pLine->GetStart() > nMaxPos)
+ return CallbackResult::Stop;
+ if (rInfo.pLine->GetEnd() < nMinPos)
+ return CallbackResult::Continue;
+ for (sal_Int32 n = nMinPos; n <= nMaxPos; ++n)
+ {
+ if (rInfo.pLine->IsIn(n))
+ {
+ tools::Rectangle aR = GetEditCursor(pParaPortion, rInfo.pLine, n,
+ GetCursorFlags::NONE);
+ aR.Move(getTopLeftDocOffset(rInfo.aArea));
+ aRects[n - nMinPos] = pView->GetImpEditView()->GetWindowPos(aR);
+ }
+ }
+ }
+ return CallbackResult::Continue;
+ };
+ IterateLineAreas(CollectCharPositions, IterFlag::none);
+
+ if (vcl::Window* pWindow = pView->GetWindow())
+ pWindow->SetCompositionCharRect(aRects.data(), aRects.size());
+ }
+ }
+ }
+ else
+ bConsumed = false;
+
+ return GetSelEngine().Command(rCEvt) || bConsumed;
+}
+
+bool ImpEditEngine::MouseButtonUp( const MouseEvent& rMEvt, EditView* pView )
+{
+ GetSelEngine().SetCurView( pView );
+ GetSelEngine().SelMouseButtonUp( rMEvt );
+
+ // in the tiled rendering case, setting bInSelection here has unexpected
+ // consequences - further tiles painting removes the selection
+ // FIXME I believe resetting bInSelection should not be here even in the
+ // non-tiled-rendering case, but it has been here since 2000 (and before)
+ // so who knows what corner case it was supposed to solve back then
+ if (!comphelper::LibreOfficeKit::isActive())
+ mbInSelection = false;
+
+ // Special treatments
+ EditSelection aCurSel( pView->pImpEditView->GetEditSelection() );
+ if ( aCurSel.HasRange() )
+ return true;
+
+ if ( ( rMEvt.GetClicks() != 1 ) || !rMEvt.IsLeft() || rMEvt.IsMod2() )
+ return true;
+
+ const OutputDevice& rOutDev = pView->getEditViewCallbacks() ? pView->getEditViewCallbacks()->EditViewOutputDevice() : *pView->GetWindow()->GetOutDev();
+ Point aLogicClick = rOutDev.PixelToLogic(rMEvt.GetPosPixel());
+ const SvxFieldItem* pFld = pView->GetField(aLogicClick);
+ if (!pFld)
+ return true;
+
+ // tdf#121039 When in edit mode, editeng is responsible for opening the URL on mouse click
+ bool bUrlOpened = GetEditEnginePtr()->FieldClicked( *pFld );
+ if (bUrlOpened)
+ return true;
+
+ if (auto pUrlField = dynamic_cast<const SvxURLField*>(pFld->GetField()))
+ {
+ bool bCtrlClickHappened = rMEvt.IsMod1();
+ bool bCtrlClickSecOption
+ = SvtSecurityOptions::IsOptionSet(SvtSecurityOptions::EOption::CtrlClickHyperlink);
+ if ((bCtrlClickHappened && bCtrlClickSecOption)
+ || (!bCtrlClickHappened && !bCtrlClickSecOption))
+ {
+ css::uno::Reference<css::system::XSystemShellExecute> exec(
+ css::system::SystemShellExecute::create(
+ comphelper::getProcessComponentContext()));
+ exec->execute(pUrlField->GetURL(), OUString(),
+ css::system::SystemShellExecuteFlags::DEFAULTS);
+ }
+ }
+ return true;
+}
+
+void ImpEditEngine::ReleaseMouse()
+{
+ GetSelEngine().ReleaseMouse();
+}
+
+bool ImpEditEngine::MouseMove( const MouseEvent& rMEvt, EditView* pView )
+{
+ // MouseMove is called directly after ShowQuickHelp()!
+ GetSelEngine().SetCurView( pView );
+ GetSelEngine().SelMouseMove( rMEvt );
+ return true;
+}
+
+EditPaM ImpEditEngine::InsertText(const EditSelection& aSel, const OUString& rStr)
+{
+ EditPaM aPaM = ImpInsertText( aSel, rStr );
+ return aPaM;
+}
+
+void ImpEditEngine::Clear()
+{
+ InitDoc( false );
+
+ EditPaM aPaM = maEditDoc.GetStartPaM();
+ EditSelection aSel( aPaM );
+
+ nCurTextHeight = 0;
+ nCurTextHeightNTP = 0;
+
+ ResetUndoManager();
+
+ for (size_t nView = aEditViews.size(); nView; )
+ {
+ EditView* pView = aEditViews[--nView];
+ pView->pImpEditView->SetEditSelection( aSel );
+ }
+
+ // Related: tdf#82115 Fix crash when handling input method events.
+ // The nodes in mpIMEInfos may be deleted in ImpEditEngine::Clear() which
+ // causes a crash in the CommandEventId::ExtTextInput and
+ // CommandEventId::EndExtTextInput event handlers.
+ mpIMEInfos.reset();
+}
+
+EditPaM ImpEditEngine::RemoveText()
+{
+ InitDoc( true );
+
+ EditPaM aStartPaM = maEditDoc.GetStartPaM();
+ EditSelection aEmptySel( aStartPaM, aStartPaM );
+ for (EditView* pView : aEditViews)
+ {
+ pView->pImpEditView->SetEditSelection( aEmptySel );
+ }
+ ResetUndoManager();
+ return maEditDoc.GetStartPaM();
+}
+
+
+void ImpEditEngine::SetText(const OUString& rText)
+{
+ // RemoveText deletes the undo list!
+ EditPaM aStartPaM = RemoveText();
+ bool bUndoCurrentlyEnabled = IsUndoEnabled();
+ // The text inserted manually can not be made reversible by the user
+ EnableUndo( false );
+
+ EditSelection aEmptySel( aStartPaM, aStartPaM );
+ EditPaM aPaM = aStartPaM;
+ if (!rText.isEmpty())
+ aPaM = ImpInsertText( aEmptySel, rText );
+
+ for (EditView* pView : aEditViews)
+ {
+ pView->pImpEditView->SetEditSelection( EditSelection( aPaM, aPaM ) );
+ // If no text then also no Format&Update
+ // => The text remains.
+ if (rText.isEmpty() && IsUpdateLayout())
+ {
+ tools::Rectangle aTmpRect( pView->GetOutputArea().TopLeft(),
+ Size( maPaperSize.Width(), nCurTextHeight ) );
+ aTmpRect.Intersection( pView->GetOutputArea() );
+ pView->InvalidateWindow( aTmpRect );
+ }
+ }
+ if (rText.isEmpty()) { // otherwise it must be invalidated later, !bFormatted is enough.
+ nCurTextHeight = 0;
+ nCurTextHeightNTP = 0;
+ }
+ EnableUndo( bUndoCurrentlyEnabled );
+ OSL_ENSURE( !HasUndoManager() || !GetUndoManager().GetUndoActionCount(), "Undo after SetText?" );
+}
+
+
+const SfxItemSet& ImpEditEngine::GetEmptyItemSet() const
+{
+ if ( !pEmptyItemSet )
+ {
+ pEmptyItemSet = std::make_unique<SfxItemSetFixed<EE_ITEMS_START, EE_ITEMS_END>>(const_cast<SfxItemPool&>(maEditDoc.GetItemPool()));
+ for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++)
+ {
+ pEmptyItemSet->ClearItem( nWhich );
+ }
+ }
+ return *pEmptyItemSet;
+}
+
+
+// MISC
+
+void ImpEditEngine::TextModified()
+{
+ mbFormatted = false;
+
+ if ( GetNotifyHdl().IsSet() )
+ {
+ EENotify aNotify( EE_NOTIFY_TEXTMODIFIED );
+ GetNotifyHdl().Call( aNotify );
+ }
+}
+
+
+void ImpEditEngine::ParaAttribsChanged( ContentNode const * pNode, bool bIgnoreUndoCheck )
+{
+ assert(pNode && "ParaAttribsChanged: Which one?");
+
+ maEditDoc.SetModified( true );
+ mbFormatted = false;
+
+ ParaPortion* pPortion = FindParaPortion( pNode );
+ assert(pPortion);
+ pPortion->MarkSelectionInvalid( 0 );
+
+ sal_Int32 nPara = maEditDoc.GetPos( pNode );
+ if ( bIgnoreUndoCheck || pEditEngine->IsInUndo() )
+ pEditEngine->ParaAttribsChanged( nPara );
+
+ ParaPortion* pNextPortion = GetParaPortions().SafeGetObject( nPara+1 );
+ // => is formatted again anyway, if Invalid.
+ if ( pNextPortion && !pNextPortion->IsInvalid() )
+ CalcHeight( pNextPortion );
+}
+
+
+// Cursor movements
+
+
+EditSelection const & ImpEditEngine::MoveCursor( const KeyEvent& rKeyEvent, EditView* pEditView )
+{
+ // Actually, only necessary for up/down, but whatever.
+ CheckIdleFormatter();
+
+ EditPaM aPaM( pEditView->pImpEditView->GetEditSelection().Max() );
+
+ EditPaM aOldPaM( aPaM );
+
+ TextDirectionality eTextDirection = TextDirectionality::LeftToRight_TopToBottom;
+ if (IsEffectivelyVertical() && IsTopToBottom())
+ eTextDirection = TextDirectionality::TopToBottom_RightToLeft;
+ else if (IsEffectivelyVertical() && !IsTopToBottom())
+ eTextDirection = TextDirectionality::BottomToTop_LeftToRight;
+ else if ( IsRightToLeft( GetEditDoc().GetPos( aPaM.GetNode() ) ) )
+ eTextDirection = TextDirectionality::RightToLeft_TopToBottom;
+
+ KeyEvent aTranslatedKeyEvent = rKeyEvent.LogicalTextDirectionality( eTextDirection );
+
+ bool bCtrl = aTranslatedKeyEvent.GetKeyCode().IsMod1();
+ sal_uInt16 nCode = aTranslatedKeyEvent.GetKeyCode().GetCode();
+
+ if ( DoVisualCursorTraveling() )
+ {
+ // Only for simple cursor movement...
+ if ( !bCtrl && ( ( nCode == KEY_LEFT ) || ( nCode == KEY_RIGHT ) ) )
+ {
+ aPaM = CursorVisualLeftRight( pEditView, aPaM, rKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL, rKeyEvent.GetKeyCode().GetCode() == KEY_LEFT );
+ nCode = 0; // skip switch statement
+ }
+ }
+
+ bool bKeyModifySelection = aTranslatedKeyEvent.GetKeyCode().IsShift();
+ switch ( nCode )
+ {
+ case KEY_UP: aPaM = CursorUp( aPaM, pEditView );
+ break;
+ case KEY_DOWN: aPaM = CursorDown( aPaM, pEditView );
+ break;
+ case KEY_LEFT: aPaM = bCtrl ? WordLeft( aPaM ) : CursorLeft( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL );
+ break;
+ case KEY_RIGHT: aPaM = bCtrl ? WordRight( aPaM ) : CursorRight( aPaM, aTranslatedKeyEvent.GetKeyCode().IsMod2() ? i18n::CharacterIteratorMode::SKIPCHARACTER : i18n::CharacterIteratorMode::SKIPCELL );
+ break;
+ case KEY_HOME: aPaM = bCtrl ? CursorStartOfDoc() : CursorStartOfLine( aPaM );
+ break;
+ case KEY_END: aPaM = bCtrl ? CursorEndOfDoc() : CursorEndOfLine( aPaM );
+ break;
+ case KEY_PAGEUP: aPaM = bCtrl ? CursorStartOfDoc() : PageUp( aPaM, pEditView );
+ break;
+ case KEY_PAGEDOWN: aPaM = bCtrl ? CursorEndOfDoc() : PageDown( aPaM, pEditView );
+ break;
+ case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
+ aPaM = CursorStartOfLine( aPaM );
+ bKeyModifySelection = false;
+ break;
+ case css::awt::Key::MOVE_TO_END_OF_LINE:
+ aPaM = CursorEndOfLine( aPaM );
+ bKeyModifySelection = false;
+ break;
+ case css::awt::Key::MOVE_WORD_BACKWARD:
+ aPaM = WordLeft( aPaM );
+ bKeyModifySelection = false;
+ break;
+ case css::awt::Key::MOVE_WORD_FORWARD:
+ aPaM = WordRight( aPaM );
+ bKeyModifySelection = false;
+ break;
+ case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
+ aPaM = CursorStartOfParagraph( aPaM );
+ if( aPaM == aOldPaM )
+ {
+ aPaM = CursorLeft( aPaM );
+ aPaM = CursorStartOfParagraph( aPaM );
+ }
+ bKeyModifySelection = false;
+ break;
+ case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
+ aPaM = CursorEndOfParagraph( aPaM );
+ if( aPaM == aOldPaM )
+ {
+ aPaM = CursorRight( aPaM );
+ aPaM = CursorEndOfParagraph( aPaM );
+ }
+ bKeyModifySelection = false;
+ break;
+ case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
+ aPaM = CursorStartOfDoc();
+ bKeyModifySelection = false;
+ break;
+ case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
+ aPaM = CursorEndOfDoc();
+ bKeyModifySelection = false;
+ break;
+ case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
+ aPaM = CursorStartOfLine( aPaM );
+ bKeyModifySelection = true;
+ break;
+ case css::awt::Key::SELECT_TO_END_OF_LINE:
+ aPaM = CursorEndOfLine( aPaM );
+ bKeyModifySelection = true;
+ break;
+ case css::awt::Key::SELECT_BACKWARD:
+ aPaM = CursorLeft( aPaM );
+ bKeyModifySelection = true;
+ break;
+ case css::awt::Key::SELECT_FORWARD:
+ aPaM = CursorRight( aPaM );
+ bKeyModifySelection = true;
+ break;
+ case css::awt::Key::SELECT_WORD_BACKWARD:
+ aPaM = WordLeft( aPaM );
+ bKeyModifySelection = true;
+ break;
+ case css::awt::Key::SELECT_WORD_FORWARD:
+ aPaM = WordRight( aPaM );
+ bKeyModifySelection = true;
+ break;
+ case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
+ aPaM = CursorStartOfParagraph( aPaM );
+ if( aPaM == aOldPaM )
+ {
+ aPaM = CursorLeft( aPaM );
+ aPaM = CursorStartOfParagraph( aPaM );
+ }
+ bKeyModifySelection = true;
+ break;
+ case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
+ aPaM = CursorEndOfParagraph( aPaM );
+ if( aPaM == aOldPaM )
+ {
+ aPaM = CursorRight( aPaM );
+ aPaM = CursorEndOfParagraph( aPaM );
+ }
+ bKeyModifySelection = true;
+ break;
+ case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
+ aPaM = CursorStartOfDoc();
+ bKeyModifySelection = true;
+ break;
+ case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
+ aPaM = CursorEndOfDoc();
+ bKeyModifySelection = true;
+ break;
+ }
+
+ if ( aOldPaM != aPaM && nullptr != aOldPaM.GetNode() )
+ {
+ aOldPaM.GetNode()->checkAndDeleteEmptyAttribs();
+ }
+
+ // May cause, a CreateAnchor or deselection all
+ aSelEngine.SetCurView( pEditView );
+ aSelEngine.CursorPosChanging( bKeyModifySelection, aTranslatedKeyEvent.GetKeyCode().IsMod1() );
+ EditPaM aOldEnd( pEditView->pImpEditView->GetEditSelection().Max() );
+
+ {
+ EditSelection aNewSelection(pEditView->pImpEditView->GetEditSelection());
+ aNewSelection.Max() = aPaM;
+ pEditView->pImpEditView->SetEditSelection(aNewSelection);
+ // const_cast<EditPaM&>(pEditView->pImpEditView->GetEditSelection().Max()) = aPaM;
+ }
+
+ if ( bKeyModifySelection )
+ {
+ // Then the selection is expanded ... or the whole selection is painted in case of tiled rendering.
+ EditSelection aTmpNewSel( comphelper::LibreOfficeKit::isActive() ? pEditView->pImpEditView->GetEditSelection().Min() : aOldEnd, aPaM );
+ pEditView->pImpEditView->DrawSelectionXOR( aTmpNewSel );
+ }
+ else
+ {
+ EditSelection aNewSelection(pEditView->pImpEditView->GetEditSelection());
+ aNewSelection.Min() = aPaM;
+ pEditView->pImpEditView->SetEditSelection(aNewSelection);
+ // const_cast<EditPaM&>(pEditView->pImpEditView->GetEditSelection().Min()) = aPaM;
+ }
+
+ return pEditView->pImpEditView->GetEditSelection();
+}
+
+EditPaM ImpEditEngine::CursorVisualStartEnd( EditView const * pEditView, const EditPaM& rPaM, bool bStart )
+{
+ EditPaM aPaM( rPaM );
+
+ sal_Int32 nPara = GetEditDoc().GetPos( aPaM.GetNode() );
+ ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
+ if (!pParaPortion)
+ return aPaM;
+
+ sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), false );
+ const EditLine& rLine = pParaPortion->GetLines()[nLine];
+ bool bEmptyLine = rLine.GetStart() == rLine.GetEnd();
+
+ pEditView->pImpEditView->nExtraCursorFlags = GetCursorFlags::NONE;
+
+ if ( !bEmptyLine )
+ {
+ OUString aLine = aPaM.GetNode()->GetString().copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart());
+
+ UErrorCode nError = U_ZERO_ERROR;
+ UBiDi* pBidi = ubidi_openSized( aLine.getLength(), 0, &nError );
+
+ const UBiDiLevel nBidiLevel = IsRightToLeft( nPara ) ? 1 /*RTL*/ : 0 /*LTR*/;
+ ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aLine.getStr()), aLine.getLength(), nBidiLevel, nullptr, &nError );
+
+ sal_Int32 nVisPos = bStart ? 0 : aLine.getLength()-1;
+ const sal_Int32 nLogPos = ubidi_getLogicalIndex( pBidi, nVisPos, &nError );
+
+ ubidi_close( pBidi );
+
+ aPaM.SetIndex( nLogPos + rLine.GetStart() );
+
+ sal_Int32 nTmp;
+ sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nTmp, true );
+ const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion];
+ bool bPortionRTL = rTextPortion.IsRightToLeft();
+
+ if ( bStart )
+ {
+ pEditView->pImpEditView->SetCursorBidiLevel( bPortionRTL ? 0 : 1 );
+ // Maybe we must be *behind* the character
+ if ( bPortionRTL && pEditView->IsInsertMode() )
+ aPaM.SetIndex( aPaM.GetIndex()+1 );
+ }
+ else
+ {
+ pEditView->pImpEditView->SetCursorBidiLevel( bPortionRTL ? 1 : 0 );
+ if ( !bPortionRTL && pEditView->IsInsertMode() )
+ aPaM.SetIndex( aPaM.GetIndex()+1 );
+ }
+ }
+
+ return aPaM;
+}
+
+EditPaM ImpEditEngine::CursorVisualLeftRight( EditView const * pEditView, const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode, bool bVisualToLeft )
+{
+ EditPaM aPaM( rPaM );
+
+ sal_Int32 nPara = GetEditDoc().GetPos( aPaM.GetNode() );
+ ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
+ if (!pParaPortion)
+ return aPaM;
+
+ sal_Int32 nLine = pParaPortion->GetLines().FindLine( aPaM.GetIndex(), false );
+ const EditLine& rLine = pParaPortion->GetLines()[nLine];
+ bool bEmptyLine = rLine.GetStart() == rLine.GetEnd();
+
+ pEditView->pImpEditView->nExtraCursorFlags = GetCursorFlags::NONE;
+
+ bool bParaRTL = IsRightToLeft( nPara );
+
+ bool bDone = false;
+
+ if ( bEmptyLine )
+ {
+ if ( bVisualToLeft )
+ {
+ aPaM = CursorUp( aPaM, pEditView );
+ if ( aPaM != rPaM )
+ aPaM = CursorVisualStartEnd( pEditView, aPaM, false );
+ }
+ else
+ {
+ aPaM = CursorDown( aPaM, pEditView );
+ if ( aPaM != rPaM )
+ aPaM = CursorVisualStartEnd( pEditView, aPaM, true );
+ }
+
+ bDone = true;
+ }
+
+ bool bLogicalBackward = bParaRTL ? !bVisualToLeft : bVisualToLeft;
+
+ if ( !bDone && pEditView->IsInsertMode() )
+ {
+ // Check if we are within a portion and don't have overwrite mode, then it's easy...
+ sal_Int32 nPortionStart;
+ sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart );
+ const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion];
+
+ bool bPortionBoundary = ( aPaM.GetIndex() == nPortionStart ) || ( aPaM.GetIndex() == (nPortionStart+rTextPortion.GetLen()) );
+ sal_uInt16 nRTLLevel = rTextPortion.GetRightToLeftLevel();
+
+ // Portion boundary doesn't matter if both have same RTL level
+ sal_Int32 nRTLLevelNextPortion = -1;
+ if ( bPortionBoundary && aPaM.GetIndex() && ( aPaM.GetIndex() < aPaM.GetNode()->Len() ) )
+ {
+ sal_Int32 nTmp;
+ sal_Int32 nNextTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex()+1, nTmp, !bLogicalBackward );
+ const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nNextTextPortion];
+ nRTLLevelNextPortion = rNextTextPortion.GetRightToLeftLevel();
+ }
+
+ if ( !bPortionBoundary || ( nRTLLevel == nRTLLevelNextPortion ) )
+ {
+ if (bVisualToLeft != bool(nRTLLevel % 2))
+ {
+ aPaM = CursorLeft( aPaM, nCharacterIteratorMode );
+ pEditView->pImpEditView->SetCursorBidiLevel( 1 );
+ }
+ else
+ {
+ aPaM = CursorRight( aPaM, nCharacterIteratorMode );
+ pEditView->pImpEditView->SetCursorBidiLevel( 0 );
+ }
+ bDone = true;
+ }
+ }
+
+ if ( !bDone )
+ {
+ bool bGotoStartOfNextLine = false;
+ bool bGotoEndOfPrevLine = false;
+
+ OUString aLine = aPaM.GetNode()->GetString().copy(rLine.GetStart(), rLine.GetEnd() - rLine.GetStart());
+ const sal_Int32 nPosInLine = aPaM.GetIndex() - rLine.GetStart();
+
+ UErrorCode nError = U_ZERO_ERROR;
+ UBiDi* pBidi = ubidi_openSized( aLine.getLength(), 0, &nError );
+
+ const UBiDiLevel nBidiLevel = IsRightToLeft( nPara ) ? 1 /*RTL*/ : 0 /*LTR*/;
+ ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aLine.getStr()), aLine.getLength(), nBidiLevel, nullptr, &nError );
+
+ if ( !pEditView->IsInsertMode() )
+ {
+ bool bEndOfLine = nPosInLine == aLine.getLength();
+ sal_Int32 nVisPos = ubidi_getVisualIndex( pBidi, !bEndOfLine ? nPosInLine : nPosInLine-1, &nError );
+ if ( bVisualToLeft )
+ {
+ bGotoEndOfPrevLine = nVisPos == 0;
+ if ( !bEndOfLine )
+ nVisPos--;
+ }
+ else
+ {
+ bGotoStartOfNextLine = nVisPos == (aLine.getLength() - 1);
+ if ( !bEndOfLine )
+ nVisPos++;
+ }
+
+ if ( !bGotoEndOfPrevLine && !bGotoStartOfNextLine )
+ {
+ aPaM.SetIndex( rLine.GetStart() + ubidi_getLogicalIndex( pBidi, nVisPos, &nError ) );
+ pEditView->pImpEditView->SetCursorBidiLevel( 0 );
+ }
+ }
+ else
+ {
+ bool bWasBehind = false;
+ bool bBeforePortion = !nPosInLine || pEditView->pImpEditView->GetCursorBidiLevel() == 1;
+ if ( nPosInLine && ( !bBeforePortion ) ) // before the next portion
+ bWasBehind = true; // step one back, otherwise visual will be unusable when rtl portion follows.
+
+ sal_Int32 nPortionStart;
+ sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart, bBeforePortion );
+ const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[nTextPortion];
+ bool bRTLPortion = rTextPortion.IsRightToLeft();
+
+ // -1: We are 'behind' the character
+ tools::Long nVisPos = static_cast<tools::Long>(ubidi_getVisualIndex( pBidi, bWasBehind ? nPosInLine-1 : nPosInLine, &nError ));
+ if ( bVisualToLeft )
+ {
+ if ( !bWasBehind || bRTLPortion )
+ nVisPos--;
+ }
+ else
+ {
+ if ( bWasBehind || bRTLPortion || bBeforePortion )
+ nVisPos++;
+ }
+
+ bGotoEndOfPrevLine = nVisPos < 0;
+ bGotoStartOfNextLine = nVisPos >= aLine.getLength();
+
+ if ( !bGotoEndOfPrevLine && !bGotoStartOfNextLine )
+ {
+ aPaM.SetIndex( rLine.GetStart() + ubidi_getLogicalIndex( pBidi, nVisPos, &nError ) );
+
+ // RTL portion, stay visually on the left side.
+ sal_Int32 _nPortionStart;
+ // sal_uInt16 nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), nPortionStart, !bRTLPortion );
+ sal_Int32 _nTextPortion = pParaPortion->GetTextPortions().FindPortion( aPaM.GetIndex(), _nPortionStart, true );
+ const TextPortion& _rTextPortion = pParaPortion->GetTextPortions()[_nTextPortion];
+ if ( bVisualToLeft && !bRTLPortion && _rTextPortion.IsRightToLeft() )
+ aPaM.SetIndex( aPaM.GetIndex()+1 );
+ else if ( !bVisualToLeft && bRTLPortion && ( bWasBehind || !_rTextPortion.IsRightToLeft() ) )
+ aPaM.SetIndex( aPaM.GetIndex()+1 );
+
+ pEditView->pImpEditView->SetCursorBidiLevel( _nPortionStart );
+ }
+ }
+
+ ubidi_close( pBidi );
+
+ if ( bGotoEndOfPrevLine )
+ {
+ aPaM = CursorUp( aPaM, pEditView );
+ if ( aPaM != rPaM )
+ aPaM = CursorVisualStartEnd( pEditView, aPaM, false );
+ }
+ else if ( bGotoStartOfNextLine )
+ {
+ aPaM = CursorDown( aPaM, pEditView );
+ if ( aPaM != rPaM )
+ aPaM = CursorVisualStartEnd( pEditView, aPaM, true );
+ }
+ }
+ return aPaM;
+}
+
+
+EditPaM ImpEditEngine::CursorLeft( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
+{
+ EditPaM aCurPaM( rPaM );
+ EditPaM aNewPaM( aCurPaM );
+
+ if ( aCurPaM.GetIndex() )
+ {
+ sal_Int32 nCount = 1;
+ uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
+ aNewPaM.SetIndex(
+ _xBI->previousCharacters(
+ aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), GetLocale( aNewPaM ), nCharacterIteratorMode, nCount, nCount));
+ }
+ else
+ {
+ ContentNode* pNode = aCurPaM.GetNode();
+ pNode = GetPrevVisNode( pNode );
+ if ( pNode )
+ {
+ aNewPaM.SetNode( pNode );
+ aNewPaM.SetIndex( pNode->Len() );
+ }
+ }
+
+ return aNewPaM;
+}
+
+EditPaM ImpEditEngine::CursorRight( const EditPaM& rPaM, sal_uInt16 nCharacterIteratorMode )
+{
+ EditPaM aCurPaM( rPaM );
+ EditPaM aNewPaM( aCurPaM );
+
+ if ( aCurPaM.GetIndex() < aCurPaM.GetNode()->Len() )
+ {
+ uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
+ sal_Int32 nCount = 1;
+ aNewPaM.SetIndex(
+ _xBI->nextCharacters(
+ aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), GetLocale( aNewPaM ), nCharacterIteratorMode, nCount, nCount));
+ }
+ else
+ {
+ ContentNode* pNode = aCurPaM.GetNode();
+ pNode = GetNextVisNode( pNode );
+ if ( pNode )
+ {
+ aNewPaM.SetNode( pNode );
+ aNewPaM.SetIndex( 0 );
+ }
+ }
+
+ return aNewPaM;
+}
+
+EditPaM ImpEditEngine::CursorUp( const EditPaM& rPaM, EditView const * pView )
+{
+ assert(pView && "No View - No Cursor Movement!");
+
+ const ParaPortion* pPPortion = FindParaPortion( rPaM.GetNode() );
+ assert(pPPortion);
+ sal_Int32 nLine = pPPortion->GetLineNumber( rPaM.GetIndex() );
+ const EditLine& rLine = pPPortion->GetLines()[nLine];
+
+ tools::Long nX;
+ if ( pView->pImpEditView->nTravelXPos == TRAVEL_X_DONTKNOW )
+ {
+ nX = GetXPos( pPPortion, &rLine, rPaM.GetIndex() );
+ pView->pImpEditView->nTravelXPos = nX+nOnePixelInRef;
+ }
+ else
+ nX = pView->pImpEditView->nTravelXPos;
+
+ EditPaM aNewPaM( rPaM );
+ if ( nLine ) // same paragraph
+ {
+ const EditLine& rPrevLine = pPPortion->GetLines()[nLine-1];
+ aNewPaM.SetIndex( GetChar( pPPortion, &rPrevLine, nX ) );
+ // If a previous automatically wrapped line, and one has to be exactly
+ // at the end of this line, the cursor lands on the current line at the
+ // beginning. See Problem: Last character of an automatically wrapped
+ // Row = cursor
+ if ( aNewPaM.GetIndex() && ( aNewPaM.GetIndex() == rLine.GetStart() ) )
+ aNewPaM = CursorLeft( aNewPaM );
+ }
+ else // previous paragraph
+ {
+ const ParaPortion* pPrevPortion = GetPrevVisPortion( pPPortion );
+ if ( pPrevPortion )
+ {
+ const EditLine& rLine2 = pPrevPortion->GetLines()[pPrevPortion->GetLines().Count()-1];
+ aNewPaM.SetNode( pPrevPortion->GetNode() );
+ aNewPaM.SetIndex( GetChar( pPrevPortion, &rLine2, nX+nOnePixelInRef ) );
+ }
+ }
+
+ return aNewPaM;
+}
+
+EditPaM ImpEditEngine::CursorDown( const EditPaM& rPaM, EditView const * pView )
+{
+ assert(pView);
+
+ const ParaPortion* pPPortion = FindParaPortion( rPaM.GetNode() );
+ assert(pPPortion);
+ sal_Int32 nLine = pPPortion->GetLineNumber( rPaM.GetIndex() );
+
+ tools::Long nX;
+ if ( pView->pImpEditView->nTravelXPos == TRAVEL_X_DONTKNOW )
+ {
+ const EditLine& rLine = pPPortion->GetLines()[nLine];
+ nX = GetXPos( pPPortion, &rLine, rPaM.GetIndex() );
+ pView->pImpEditView->nTravelXPos = nX+nOnePixelInRef;
+ }
+ else
+ nX = pView->pImpEditView->nTravelXPos;
+
+ EditPaM aNewPaM( rPaM );
+ if ( nLine < pPPortion->GetLines().Count()-1 )
+ {
+ const EditLine& rNextLine = pPPortion->GetLines()[nLine+1];
+ aNewPaM.SetIndex( GetChar( pPPortion, &rNextLine, nX ) );
+ // Special treatment, see CursorUp ...
+ if ( ( aNewPaM.GetIndex() == rNextLine.GetEnd() ) && ( aNewPaM.GetIndex() > rNextLine.GetStart() ) && ( aNewPaM.GetIndex() < pPPortion->GetNode()->Len() ) )
+ aNewPaM = CursorLeft( aNewPaM );
+ }
+ else // next paragraph
+ {
+ const ParaPortion* pNextPortion = GetNextVisPortion( pPPortion );
+ if ( pNextPortion )
+ {
+ const EditLine& rLine = pNextPortion->GetLines()[0];
+ aNewPaM.SetNode( pNextPortion->GetNode() );
+ // Never at the very end when several lines, because then a line
+ // below the cursor appears.
+ aNewPaM.SetIndex( GetChar( pNextPortion, &rLine, nX+nOnePixelInRef ) );
+ if ( ( aNewPaM.GetIndex() == rLine.GetEnd() ) && ( aNewPaM.GetIndex() > rLine.GetStart() ) && ( pNextPortion->GetLines().Count() > 1 ) )
+ aNewPaM = CursorLeft( aNewPaM );
+ }
+ }
+
+ return aNewPaM;
+}
+
+EditPaM ImpEditEngine::CursorStartOfLine( const EditPaM& rPaM )
+{
+ const ParaPortion* pCurPortion = FindParaPortion( rPaM.GetNode() );
+ assert(pCurPortion);
+ sal_Int32 nLine = pCurPortion->GetLineNumber( rPaM.GetIndex() );
+ const EditLine& rLine = pCurPortion->GetLines()[nLine];
+
+ EditPaM aNewPaM( rPaM );
+ aNewPaM.SetIndex( rLine.GetStart() );
+ return aNewPaM;
+}
+
+EditPaM ImpEditEngine::CursorEndOfLine( const EditPaM& rPaM )
+{
+ const ParaPortion* pCurPortion = FindParaPortion( rPaM.GetNode() );
+ assert(pCurPortion);
+ sal_Int32 nLine = pCurPortion->GetLineNumber( rPaM.GetIndex() );
+ const EditLine& rLine = pCurPortion->GetLines()[nLine];
+
+ EditPaM aNewPaM( rPaM );
+ aNewPaM.SetIndex( rLine.GetEnd() );
+ if ( rLine.GetEnd() > rLine.GetStart() )
+ {
+ if ( aNewPaM.GetNode()->IsFeature( aNewPaM.GetIndex() - 1 ) )
+ {
+ // When a soft break, be in front of it!
+ const EditCharAttrib* pNextFeature = aNewPaM.GetNode()->GetCharAttribs().FindFeature( aNewPaM.GetIndex()-1 );
+ if ( pNextFeature && ( pNextFeature->GetItem()->Which() == EE_FEATURE_LINEBR ) )
+ aNewPaM = CursorLeft( aNewPaM );
+ }
+ else if ( ( aNewPaM.GetNode()->GetChar( aNewPaM.GetIndex() - 1 ) == ' ' ) && ( aNewPaM.GetIndex() != aNewPaM.GetNode()->Len() ) )
+ {
+ // For a Blank in an auto wrapped line, it makes sense, to stand
+ // in front of it, since the user wants to be after the word.
+ // If this is changed, special treatment for Pos1 to End!
+ aNewPaM = CursorLeft( aNewPaM );
+ }
+ }
+ return aNewPaM;
+}
+
+EditPaM ImpEditEngine::CursorStartOfParagraph( const EditPaM& rPaM )
+{
+ EditPaM aPaM(rPaM);
+ aPaM.SetIndex(0);
+ return aPaM;
+}
+
+EditPaM ImpEditEngine::CursorEndOfParagraph( const EditPaM& rPaM )
+{
+ EditPaM aPaM(rPaM);
+ aPaM.SetIndex(rPaM.GetNode()->Len());
+ return aPaM;
+}
+
+EditPaM ImpEditEngine::CursorStartOfDoc()
+{
+ EditPaM aPaM( maEditDoc.GetObject( 0 ), 0 );
+ return aPaM;
+}
+
+EditPaM ImpEditEngine::CursorEndOfDoc()
+{
+ ContentNode* pLastNode = maEditDoc.GetObject( maEditDoc.Count()-1 );
+ ParaPortion* pLastPortion = GetParaPortions().SafeGetObject( maEditDoc.Count()-1 );
+ OSL_ENSURE( pLastNode && pLastPortion, "CursorEndOfDoc: Node or Portion not found" );
+ if (!(pLastNode && pLastPortion))
+ return EditPaM();
+
+ if ( !pLastPortion->IsVisible() )
+ {
+ pLastNode = GetPrevVisNode( pLastPortion->GetNode() );
+ OSL_ENSURE( pLastNode, "No visible paragraph?" );
+ if ( !pLastNode )
+ pLastNode = maEditDoc.GetObject( maEditDoc.Count()-1 );
+ }
+
+ EditPaM aPaM( pLastNode, pLastNode->Len() );
+ return aPaM;
+}
+
+EditPaM ImpEditEngine::PageUp( const EditPaM& rPaM, EditView const * pView )
+{
+ tools::Rectangle aRect = PaMtoEditCursor( rPaM );
+ Point aTopLeft = aRect.TopLeft();
+ aTopLeft.AdjustY( -(pView->GetVisArea().GetHeight() *9/10) );
+ aTopLeft.AdjustX(nOnePixelInRef );
+ if ( aTopLeft.Y() < 0 )
+ {
+ aTopLeft.setY( 0 );
+ }
+ return GetPaM( aTopLeft );
+}
+
+EditPaM ImpEditEngine::PageDown( const EditPaM& rPaM, EditView const * pView )
+{
+ tools::Rectangle aRect = PaMtoEditCursor( rPaM );
+ Point aBottomRight = aRect.BottomRight();
+ aBottomRight.AdjustY(pView->GetVisArea().GetHeight() *9/10 );
+ aBottomRight.AdjustX(nOnePixelInRef );
+ tools::Long nHeight = GetTextHeight();
+ if ( aBottomRight.Y() > nHeight )
+ {
+ aBottomRight.setY( nHeight-2 );
+ }
+ return GetPaM( aBottomRight );
+}
+
+EditPaM ImpEditEngine::WordLeft( const EditPaM& rPaM )
+{
+ const sal_Int32 nCurrentPos = rPaM.GetIndex();
+ EditPaM aNewPaM( rPaM );
+ if ( nCurrentPos == 0 )
+ {
+ // Previous paragraph...
+ sal_Int32 nCurPara = maEditDoc.GetPos( aNewPaM.GetNode() );
+ ContentNode* pPrevNode = maEditDoc.GetObject( --nCurPara );
+ if ( pPrevNode )
+ {
+ aNewPaM.SetNode( pPrevNode );
+ aNewPaM.SetIndex( pPrevNode->Len() );
+ }
+ }
+ else
+ {
+ // we need to increase the position by 1 when retrieving the locale
+ // since the attribute for the char left to the cursor position is returned
+ EditPaM aTmpPaM( aNewPaM );
+ if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() )
+ aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
+ lang::Locale aLocale( GetLocale( aTmpPaM ) );
+
+ uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
+ i18n::Boundary aBoundary =
+ _xBI->getWordBoundary(aNewPaM.GetNode()->GetString(), nCurrentPos, aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true);
+ if ( aBoundary.startPos >= nCurrentPos )
+ aBoundary = _xBI->previousWord(
+ aNewPaM.GetNode()->GetString(), nCurrentPos, aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES);
+ aNewPaM.SetIndex( ( aBoundary.startPos != -1 ) ? aBoundary.startPos : 0 );
+ }
+
+ return aNewPaM;
+}
+
+EditPaM ImpEditEngine::WordRight( const EditPaM& rPaM, sal_Int16 nWordType )
+{
+ const sal_Int32 nMax = rPaM.GetNode()->Len();
+ EditPaM aNewPaM( rPaM );
+ if ( aNewPaM.GetIndex() < nMax )
+ {
+ // we need to increase the position by 1 when retrieving the locale
+ // since the attribute for the char left to the cursor position is returned
+ EditPaM aTmpPaM( aNewPaM );
+ aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
+ lang::Locale aLocale( GetLocale( aTmpPaM ) );
+
+ uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
+ i18n::Boundary aBoundary = _xBI->nextWord(
+ aNewPaM.GetNode()->GetString(), aNewPaM.GetIndex(), aLocale, nWordType);
+ aNewPaM.SetIndex( aBoundary.startPos );
+ }
+ // not 'else', maybe the index reached nMax now...
+ if ( aNewPaM.GetIndex() >= nMax )
+ {
+ // Next paragraph ...
+ sal_Int32 nCurPara = maEditDoc.GetPos( aNewPaM.GetNode() );
+ ContentNode* pNextNode = maEditDoc.GetObject( ++nCurPara );
+ if ( pNextNode )
+ {
+ aNewPaM.SetNode( pNextNode );
+ aNewPaM.SetIndex( 0 );
+ }
+ }
+ return aNewPaM;
+}
+
+EditPaM ImpEditEngine::StartOfWord( const EditPaM& rPaM )
+{
+ EditPaM aNewPaM( rPaM );
+
+ // we need to increase the position by 1 when retrieving the locale
+ // since the attribute for the char left to the cursor position is returned
+ EditPaM aTmpPaM( aNewPaM );
+ if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() )
+ aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
+ lang::Locale aLocale( GetLocale( aTmpPaM ) );
+
+ uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
+ // tdf#135761 - since this function is only used when a selection is deleted at the left,
+ // change the search preference of the word boundary from forward to backward.
+ // For further details of a deletion of a selection check ImpEditEngine::DeleteLeftOrRight.
+ i18n::Boundary aBoundary = _xBI->getWordBoundary(
+ rPaM.GetNode()->GetString(), rPaM.GetIndex(), aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, false);
+
+ aNewPaM.SetIndex( aBoundary.startPos );
+ return aNewPaM;
+}
+
+EditPaM ImpEditEngine::EndOfWord( const EditPaM& rPaM )
+{
+ EditPaM aNewPaM( rPaM );
+
+ // we need to increase the position by 1 when retrieving the locale
+ // since the attribute for the char left to the cursor position is returned
+ EditPaM aTmpPaM( aNewPaM );
+ if ( aTmpPaM.GetIndex() < rPaM.GetNode()->Len() )
+ aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
+ lang::Locale aLocale( GetLocale( aTmpPaM ) );
+
+ uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
+ i18n::Boundary aBoundary = _xBI->getWordBoundary(
+ rPaM.GetNode()->GetString(), rPaM.GetIndex(), aLocale, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, true);
+
+ aNewPaM.SetIndex( aBoundary.endPos );
+ return aNewPaM;
+}
+
+EditSelection ImpEditEngine::SelectWord( const EditSelection& rCurSel, sal_Int16 nWordType, bool bAcceptStartOfWord )
+{
+ EditSelection aNewSel( rCurSel );
+ EditPaM aPaM( rCurSel.Max() );
+
+ // we need to increase the position by 1 when retrieving the locale
+ // since the attribute for the char left to the cursor position is returned
+ EditPaM aTmpPaM( aPaM );
+ if ( aTmpPaM.GetIndex() < aPaM.GetNode()->Len() )
+ aTmpPaM.SetIndex( aTmpPaM.GetIndex() + 1 );
+ lang::Locale aLocale( GetLocale( aTmpPaM ) );
+
+ uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
+ sal_Int16 nType = _xBI->getWordType(
+ aPaM.GetNode()->GetString(), aPaM.GetIndex(), aLocale);
+
+ if ( nType == i18n::WordType::ANY_WORD )
+ {
+ i18n::Boundary aBoundary = _xBI->getWordBoundary(
+ aPaM.GetNode()->GetString(), aPaM.GetIndex(), aLocale, nWordType, true);
+
+ // don't select when cursor at end of word
+ if ( ( aBoundary.endPos > aPaM.GetIndex() ) &&
+ ( ( aBoundary.startPos < aPaM.GetIndex() ) || ( bAcceptStartOfWord && ( aBoundary.startPos == aPaM.GetIndex() ) ) ) )
+ {
+ aNewSel.Min().SetIndex( aBoundary.startPos );
+ aNewSel.Max().SetIndex( aBoundary.endPos );
+ }
+ }
+
+ return aNewSel;
+}
+
+EditSelection ImpEditEngine::SelectSentence( const EditSelection& rCurSel )
+ const
+{
+ uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
+ const EditPaM& rPaM = rCurSel.Min();
+ const ContentNode* pNode = rPaM.GetNode();
+ // #i50710# line breaks are marked with 0x01 - the break iterator prefers 0x0a for that
+ const OUString sParagraph = pNode->GetString().replaceAll("\x01", "\x0a");
+ //return Null if search starts at the beginning of the string
+ sal_Int32 nStart = rPaM.GetIndex() ? _xBI->beginOfSentence( sParagraph, rPaM.GetIndex(), GetLocale( rPaM ) ) : 0;
+
+ sal_Int32 nEnd = _xBI->endOfSentence(
+ pNode->GetString(), rPaM.GetIndex(), GetLocale(rPaM));
+
+ EditSelection aNewSel( rCurSel );
+ OSL_ENSURE(pNode->Len() ? (nStart < pNode->Len()) : (nStart == 0), "sentence start index out of range");
+ OSL_ENSURE(nEnd <= pNode->Len(), "sentence end index out of range");
+ aNewSel.Min().SetIndex( nStart );
+ aNewSel.Max().SetIndex( nEnd );
+ return aNewSel;
+}
+
+bool ImpEditEngine::IsInputSequenceCheckingRequired( sal_Unicode nChar, const EditSelection& rCurSel ) const
+{
+ uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
+
+ // get the index that really is first
+ const sal_Int32 nFirstPos = std::min(rCurSel.Min().GetIndex(), rCurSel.Max().GetIndex());
+
+ bool bIsSequenceChecking =
+ SvtCTLOptions::IsCTLFontEnabled() &&
+ SvtCTLOptions::IsCTLSequenceChecking() &&
+ nFirstPos != 0 && /* first char needs not to be checked */
+ _xBI.is() && i18n::ScriptType::COMPLEX == _xBI->getScriptType( OUString( nChar ), 0 );
+
+ return bIsSequenceChecking;
+}
+
+static bool lcl_HasStrongLTR ( std::u16string_view rTxt, sal_Int32 nStart, sal_Int32 nEnd )
+ {
+ for( sal_Int32 nCharIdx = nStart; nCharIdx < nEnd; ++nCharIdx )
+ {
+ const UCharDirection nCharDir = u_charDirection ( rTxt[ nCharIdx ] );
+ if ( nCharDir == U_LEFT_TO_RIGHT ||
+ nCharDir == U_LEFT_TO_RIGHT_EMBEDDING ||
+ nCharDir == U_LEFT_TO_RIGHT_OVERRIDE )
+ return true;
+ }
+ return false;
+ }
+
+
+void ImpEditEngine::InitScriptTypes( sal_Int32 nPara )
+{
+ ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
+ if (!pParaPortion)
+ return;
+
+ ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos;
+ rTypes.clear();
+
+ ContentNode* pNode = pParaPortion->GetNode();
+ if ( !pNode->Len() )
+ return;
+
+ uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
+
+ OUString aText = pNode->GetString();
+
+ // To handle fields put the character from the field in the string,
+ // because endOfScript( ... ) will skip the CH_FEATURE, because this is WEAK
+ const EditCharAttrib* pField = pNode->GetCharAttribs().FindNextAttrib( EE_FEATURE_FIELD, 0 );
+ while ( pField )
+ {
+ const OUString aFldText = static_cast<const EditCharAttribField*>(pField)->GetFieldValue();
+ if ( !aFldText.isEmpty() )
+ {
+ aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(0,1) );
+ short nFldScriptType = _xBI->getScriptType( aFldText, 0 );
+
+ for ( sal_Int32 nCharInField = 1; nCharInField < aFldText.getLength(); nCharInField++ )
+ {
+ short nTmpType = _xBI->getScriptType( aFldText, nCharInField );
+
+ // First char from field wins...
+ if ( nFldScriptType == i18n::ScriptType::WEAK )
+ {
+ nFldScriptType = nTmpType;
+ aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(nCharInField,1) );
+ }
+
+ // ... but if the first one is LATIN, and there are CJK or CTL chars too,
+ // we prefer that ScriptType because we need another font.
+ if ( ( nTmpType == i18n::ScriptType::ASIAN ) || ( nTmpType == i18n::ScriptType::COMPLEX ) )
+ {
+ aText = aText.replaceAt( pField->GetStart(), 1, aFldText.subView(nCharInField,1) );
+ break;
+ }
+ }
+ }
+ // #112831# Last Field might go from 0xffff to 0x0000
+ pField = pField->GetEnd() ? pNode->GetCharAttribs().FindNextAttrib( EE_FEATURE_FIELD, pField->GetEnd() ) : nullptr;
+ }
+
+ sal_Int32 nTextLen = aText.getLength();
+
+ sal_Int32 nPos = 0;
+ short nScriptType = _xBI->getScriptType( aText, nPos );
+ rTypes.emplace_back( nScriptType, nPos, nTextLen );
+ nPos = _xBI->endOfScript( aText, nPos, nScriptType );
+ while ( ( nPos != -1 ) && ( nPos < nTextLen ) )
+ {
+ rTypes.back().nEndPos = nPos;
+
+ nScriptType = _xBI->getScriptType( aText, nPos );
+ tools::Long nEndPos = _xBI->endOfScript( aText, nPos, nScriptType );
+
+ if ( ( nScriptType == i18n::ScriptType::WEAK ) || ( nScriptType == rTypes.back().nScriptType ) )
+ {
+ // Expand last ScriptTypePosInfo, don't create weak or unnecessary portions
+ rTypes.back().nEndPos = nEndPos;
+ }
+ else
+ {
+ auto nPrevPos = nPos;
+ auto nPrevChar = aText.iterateCodePoints(&nPrevPos, -1);
+ if (_xBI->getScriptType(aText, nPrevPos) == i18n::ScriptType::WEAK)
+ {
+ auto nChar = aText.iterateCodePoints(&nPos, 0);
+ auto nType = unicode::getUnicodeType(nChar);
+ if (nType == css::i18n::UnicodeType::NON_SPACING_MARK ||
+ nType == css::i18n::UnicodeType::ENCLOSING_MARK ||
+ nType == css::i18n::UnicodeType::COMBINING_SPACING_MARK ||
+ (nPrevChar == 0x202F /* NNBSP, tdf#112594 */ &&
+ u_getIntPropertyValue(nChar, UCHAR_SCRIPT) == USCRIPT_MONGOLIAN))
+ {
+ rTypes.back().nEndPos = nPos = nPrevPos;
+ break;
+ }
+ }
+ rTypes.emplace_back( nScriptType, nPos, nTextLen );
+ }
+
+ nPos = nEndPos;
+ }
+
+ if ( rTypes[0].nScriptType == i18n::ScriptType::WEAK )
+ rTypes[0].nScriptType = ( rTypes.size() > 1 ) ? rTypes[1].nScriptType : SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetDefaultLanguage() );
+
+ // create writing direction information:
+ if ( pParaPortion->aWritingDirectionInfos.empty() )
+ InitWritingDirections( nPara );
+
+ // i89825: Use CTL font for numbers embedded into an RTL run:
+ WritingDirectionInfos& rDirInfos = pParaPortion->aWritingDirectionInfos;
+ for (const WritingDirectionInfo & rDirInfo : rDirInfos)
+ {
+ const sal_Int32 nStart = rDirInfo.nStartPos;
+ const sal_Int32 nEnd = rDirInfo.nEndPos;
+ const sal_uInt8 nCurrDirType = rDirInfo.nType;
+
+ if ( nCurrDirType % 2 == UBIDI_RTL || // text in RTL run
+ ( nCurrDirType > UBIDI_LTR && !lcl_HasStrongLTR( aText, nStart, nEnd ) ) ) // non-strong text in embedded LTR run
+ {
+ size_t nIdx = 0;
+
+ // Skip entries in ScriptArray which are not inside the RTL run:
+ while ( nIdx < rTypes.size() && rTypes[nIdx].nStartPos < nStart )
+ ++nIdx;
+
+ // Remove any entries *inside* the current run:
+ while (nIdx < rTypes.size() && rTypes[nIdx].nEndPos <= nEnd)
+ {
+ // coverity[use_iterator] - we're protected from a bad iterator by the above condition
+ rTypes.erase(rTypes.begin() + nIdx);
+ }
+
+ // special case:
+ if(nIdx < rTypes.size() && rTypes[nIdx].nStartPos < nStart && rTypes[nIdx].nEndPos > nEnd)
+ {
+ rTypes.insert( rTypes.begin()+nIdx, ScriptTypePosInfo( rTypes[nIdx].nScriptType, nEnd, rTypes[nIdx].nEndPos ) );
+ rTypes[nIdx].nEndPos = nStart;
+ }
+
+ if( nIdx )
+ rTypes[nIdx - 1].nEndPos = nStart;
+
+ rTypes.insert( rTypes.begin()+nIdx, ScriptTypePosInfo( i18n::ScriptType::COMPLEX, nStart, nEnd) );
+ ++nIdx;
+
+ if( nIdx < rTypes.size() )
+ rTypes[nIdx].nStartPos = nEnd;
+ }
+ }
+}
+
+namespace {
+
+struct FindByPos
+{
+ explicit FindByPos(sal_Int32 nPos)
+ : mnPos(nPos)
+ {
+ }
+
+ bool operator()(const ScriptTypePosInfos::value_type& rValue)
+ {
+ return rValue.nStartPos <= mnPos && rValue.nEndPos >= mnPos;
+ }
+
+private:
+ sal_Int32 mnPos;
+};
+
+}
+
+sal_uInt16 ImpEditEngine::GetI18NScriptType( const EditPaM& rPaM, sal_Int32* pEndPos ) const
+{
+ sal_uInt16 nScriptType = 0;
+
+ if ( pEndPos )
+ *pEndPos = rPaM.GetNode()->Len();
+
+ if ( rPaM.GetNode()->Len() )
+ {
+ sal_Int32 nPara = GetEditDoc().GetPos( rPaM.GetNode() );
+ const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
+ if (pParaPortion)
+ {
+ if ( pParaPortion->aScriptInfos.empty() )
+ const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara );
+
+ const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos;
+
+ const sal_Int32 nPos = rPaM.GetIndex();
+ ScriptTypePosInfos::const_iterator itr = std::find_if(rTypes.begin(), rTypes.end(), FindByPos(nPos));
+ if(itr != rTypes.end())
+ {
+ nScriptType = itr->nScriptType;
+ if( pEndPos )
+ *pEndPos = itr->nEndPos;
+ }
+ }
+ }
+ return nScriptType ? nScriptType : SvtLanguageOptions::GetI18NScriptTypeOfLanguage( GetDefaultLanguage() );
+}
+
+SvtScriptType ImpEditEngine::GetItemScriptType( const EditSelection& rSel ) const
+{
+ EditSelection aSel( rSel );
+ aSel.Adjust( maEditDoc );
+
+ SvtScriptType nScriptType = SvtScriptType::NONE;
+
+ sal_Int32 nStartPara = GetEditDoc().GetPos( aSel.Min().GetNode() );
+ sal_Int32 nEndPara = GetEditDoc().GetPos( aSel.Max().GetNode() );
+
+ for ( sal_Int32 nPara = nStartPara; nPara <= nEndPara; nPara++ )
+ {
+ const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
+ if (!pParaPortion)
+ continue;
+
+ if ( pParaPortion->aScriptInfos.empty() )
+ const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara );
+
+ const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos;
+
+ // find all the scripts of this range
+ sal_Int32 nS = ( nPara == nStartPara ) ? aSel.Min().GetIndex() : 0;
+ sal_Int32 nE = ( nPara == nEndPara ) ? aSel.Max().GetIndex() : pParaPortion->GetNode()->Len();
+
+ //no selection, just bare cursor
+ if (nStartPara == nEndPara && nS == nE)
+ {
+ //If we are not at the start of the paragraph we want the properties of the
+ //preceding character. Otherwise get the properties of the next (or what the
+ //next would have if it existed)
+ if (nS != 0)
+ --nS;
+ else
+ ++nE;
+ }
+
+ for (const ScriptTypePosInfo & rType : rTypes)
+ {
+ bool bStartInRange = rType.nStartPos <= nS && nS < rType.nEndPos;
+ bool bEndInRange = rType.nStartPos < nE && nE <= rType.nEndPos;
+
+ if (bStartInRange || bEndInRange)
+ {
+ if ( rType.nScriptType != i18n::ScriptType::WEAK )
+ nScriptType |= SvtLanguageOptions::FromI18NToSvtScriptType( rType.nScriptType );
+ }
+ }
+ }
+ return bool(nScriptType) ? nScriptType : SvtLanguageOptions::GetScriptTypeOfLanguage( GetDefaultLanguage() );
+}
+
+bool ImpEditEngine::IsScriptChange( const EditPaM& rPaM ) const
+{
+ bool bScriptChange = false;
+
+ if ( rPaM.GetNode()->Len() )
+ {
+ sal_Int32 nPara = GetEditDoc().GetPos( rPaM.GetNode() );
+ const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
+ if (pParaPortion)
+ {
+ if ( pParaPortion->aScriptInfos.empty() )
+ const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara );
+
+ const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos;
+ const sal_Int32 nPos = rPaM.GetIndex();
+ for (const ScriptTypePosInfo & rType : rTypes)
+ {
+ if ( rType.nStartPos == nPos )
+ {
+ bScriptChange = true;
+ break;
+ }
+ }
+ }
+ }
+ return bScriptChange;
+}
+
+bool ImpEditEngine::HasScriptType( sal_Int32 nPara, sal_uInt16 nType ) const
+{
+ bool bTypeFound = false;
+
+ const ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
+ if (pParaPortion)
+ {
+ if ( pParaPortion->aScriptInfos.empty() )
+ const_cast<ImpEditEngine*>(this)->InitScriptTypes( nPara );
+
+ const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos;
+ for ( size_t n = rTypes.size(); n && !bTypeFound; )
+ {
+ if ( rTypes[--n].nScriptType == nType )
+ bTypeFound = true;
+ }
+ }
+ return bTypeFound;
+}
+
+void ImpEditEngine::InitWritingDirections( sal_Int32 nPara )
+{
+ ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
+ if (!pParaPortion)
+ return;
+
+ WritingDirectionInfos& rInfos = pParaPortion->aWritingDirectionInfos;
+ rInfos.clear();
+
+ if (pParaPortion->GetNode()->Len() && !mbFuzzing)
+ {
+ const OUString aText = pParaPortion->GetNode()->GetString();
+
+ // Bidi functions from icu 2.0
+
+ UErrorCode nError = U_ZERO_ERROR;
+ UBiDi* pBidi = ubidi_openSized( aText.getLength(), 0, &nError );
+ nError = U_ZERO_ERROR;
+
+ const UBiDiLevel nBidiLevel = IsRightToLeft(nPara) ? 1 /*RTL*/ : 0 /*LTR*/;
+ ubidi_setPara( pBidi, reinterpret_cast<const UChar *>(aText.getStr()), aText.getLength(), nBidiLevel, nullptr, &nError );
+ nError = U_ZERO_ERROR;
+
+ int32_t nCount = ubidi_countRuns( pBidi, &nError );
+
+ /* ubidi_countRuns can return -1 in case of error */
+ if (nCount > 0)
+ {
+ int32_t nStart = 0;
+ int32_t nEnd;
+ UBiDiLevel nCurrDir;
+
+ for (int32_t nIdx = 0; nIdx < nCount; ++nIdx)
+ {
+ ubidi_getLogicalRun( pBidi, nStart, &nEnd, &nCurrDir );
+ rInfos.emplace_back( nCurrDir, nStart, nEnd );
+ nStart = nEnd;
+ }
+ }
+
+ ubidi_close( pBidi );
+ }
+
+ // No infos mean ubidi error, default to LTR
+ if ( rInfos.empty() )
+ rInfos.emplace_back( 0, 0, pParaPortion->GetNode()->Len() );
+
+}
+
+bool ImpEditEngine::IsRightToLeft( sal_Int32 nPara ) const
+{
+ bool bR2L = false;
+ const SvxFrameDirectionItem* pFrameDirItem = nullptr;
+
+ if ( !IsEffectivelyVertical() )
+ {
+ bR2L = GetDefaultHorizontalTextDirection() == EEHorizontalTextDirection::R2L;
+ pFrameDirItem = &GetParaAttrib( nPara, EE_PARA_WRITINGDIR );
+ if ( pFrameDirItem->GetValue() == SvxFrameDirection::Environment )
+ {
+ // #103045# if DefaultHorizontalTextDirection is set, use that value, otherwise pool default.
+ if ( GetDefaultHorizontalTextDirection() != EEHorizontalTextDirection::Default )
+ {
+ pFrameDirItem = nullptr; // bR2L already set to default horizontal text direction
+ }
+ else
+ {
+ // Use pool default
+ pFrameDirItem = &GetEmptyItemSet().Get(EE_PARA_WRITINGDIR);
+ }
+ }
+ }
+
+ if ( pFrameDirItem )
+ bR2L = pFrameDirItem->GetValue() == SvxFrameDirection::Horizontal_RL_TB;
+
+ return bR2L;
+}
+
+bool ImpEditEngine::HasDifferentRTLLevels( const ContentNode* pNode )
+{
+ bool bHasDifferentRTLLevels = false;
+
+ sal_Int32 nPara = GetEditDoc().GetPos( pNode );
+ ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
+ if (pParaPortion)
+ {
+ sal_uInt16 nRTLLevel = IsRightToLeft( nPara ) ? 1 : 0;
+ for ( sal_Int32 n = 0; n < pParaPortion->GetTextPortions().Count(); n++ )
+ {
+ const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[n];
+ if ( rTextPortion.GetRightToLeftLevel() != nRTLLevel )
+ {
+ bHasDifferentRTLLevels = true;
+ break;
+ }
+ }
+ }
+ return bHasDifferentRTLLevels;
+}
+
+
+sal_uInt8 ImpEditEngine::GetRightToLeft( sal_Int32 nPara, sal_Int32 nPos, sal_Int32* pStart, sal_Int32* pEnd )
+{
+ sal_uInt8 nRightToLeft = 0;
+
+ ContentNode* pNode = maEditDoc.GetObject( nPara );
+ if ( pNode && pNode->Len() )
+ {
+ ParaPortion* pParaPortion = GetParaPortions().SafeGetObject( nPara );
+ if (pParaPortion)
+ {
+ if ( pParaPortion->aWritingDirectionInfos.empty() )
+ InitWritingDirections( nPara );
+
+ WritingDirectionInfos& rDirInfos = pParaPortion->aWritingDirectionInfos;
+ for (const WritingDirectionInfo & rDirInfo : rDirInfos)
+ {
+ if ( ( rDirInfo.nStartPos <= nPos ) && ( rDirInfo.nEndPos >= nPos ) )
+ {
+ nRightToLeft = rDirInfo.nType;
+ if ( pStart )
+ *pStart = rDirInfo.nStartPos;
+ if ( pEnd )
+ *pEnd = rDirInfo.nEndPos;
+ break;
+ }
+ }
+ }
+ }
+ return nRightToLeft;
+}
+
+SvxAdjust ImpEditEngine::GetJustification( sal_Int32 nPara ) const
+{
+ SvxAdjust eJustification = SvxAdjust::Left;
+
+ if (!maStatus.IsOutliner())
+ {
+ eJustification = GetParaAttrib( nPara, EE_PARA_JUST ).GetAdjust();
+
+ if ( IsRightToLeft( nPara ) )
+ {
+ if ( eJustification == SvxAdjust::Left )
+ eJustification = SvxAdjust::Right;
+ else if ( eJustification == SvxAdjust::Right )
+ eJustification = SvxAdjust::Left;
+ }
+ }
+ return eJustification;
+}
+
+SvxCellJustifyMethod ImpEditEngine::GetJustifyMethod( sal_Int32 nPara ) const
+{
+ const SvxJustifyMethodItem& rItem = GetParaAttrib(nPara, EE_PARA_JUST_METHOD);
+ return static_cast<SvxCellJustifyMethod>(rItem.GetEnumValue());
+}
+
+SvxCellVerJustify ImpEditEngine::GetVerJustification( sal_Int32 nPara ) const
+{
+ const SvxVerJustifyItem& rItem = GetParaAttrib(nPara, EE_PARA_VER_JUST);
+ return static_cast<SvxCellVerJustify>(rItem.GetEnumValue());
+}
+
+// Text changes
+void ImpEditEngine::ImpRemoveChars( const EditPaM& rPaM, sal_Int32 nChars )
+{
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ const OUString aStr( rPaM.GetNode()->Copy( rPaM.GetIndex(), nChars ) );
+
+ // Check whether attributes are deleted or changed:
+ const sal_Int32 nStart = rPaM.GetIndex();
+ const sal_Int32 nEnd = nStart + nChars;
+ const CharAttribList::AttribsType& rAttribs = rPaM.GetNode()->GetCharAttribs().GetAttribs();
+ for (const auto & rAttrib : rAttribs)
+ {
+ const EditCharAttrib& rAttr = *rAttrib;
+ if (rAttr.GetEnd() >= nStart && rAttr.GetStart() < nEnd)
+ {
+ EditSelection aSel( rPaM );
+ aSel.Max().SetIndex( aSel.Max().GetIndex() + nChars );
+ InsertUndo( CreateAttribUndo( aSel, GetEmptyItemSet() ) );
+ break; // for
+ }
+ }
+ InsertUndo(std::make_unique<EditUndoRemoveChars>(pEditEngine, CreateEPaM(rPaM), aStr));
+ }
+
+ maEditDoc.RemoveChars( rPaM, nChars );
+}
+
+EditSelection ImpEditEngine::ImpMoveParagraphs( Range aOldPositions, sal_Int32 nNewPos )
+{
+ aOldPositions.Normalize();
+ bool bValidAction = ( static_cast<tools::Long>(nNewPos) < aOldPositions.Min() ) || ( static_cast<tools::Long>(nNewPos) > aOldPositions.Max() );
+ OSL_ENSURE( bValidAction, "Move in itself?" );
+ OSL_ENSURE( aOldPositions.Max() <= static_cast<tools::Long>(GetParaPortions().Count()), "totally over it: MoveParagraphs" );
+
+ EditSelection aSelection;
+
+ if ( !bValidAction )
+ {
+ aSelection = maEditDoc.GetStartPaM();
+ return aSelection;
+ }
+
+ sal_Int32 nParaCount = GetParaPortions().Count();
+
+ if ( nNewPos >= nParaCount )
+ nNewPos = nParaCount;
+
+ // Height may change when moving first or last Paragraph
+ ParaPortion* pRecalc1 = nullptr;
+ ParaPortion* pRecalc2 = nullptr;
+ ParaPortion* pRecalc3 = nullptr;
+ ParaPortion* pRecalc4 = nullptr;
+
+ if ( nNewPos == 0 ) // Move to Start
+ {
+ pRecalc1 = GetParaPortions()[0];
+ pRecalc2 = GetParaPortions()[aOldPositions.Min()];
+
+ }
+ else if ( nNewPos == nParaCount )
+ {
+ pRecalc1 = GetParaPortions()[nParaCount-1];
+ pRecalc2 = GetParaPortions()[aOldPositions.Max()];
+ }
+
+ if ( aOldPositions.Min() == 0 ) // Move from Start
+ {
+ pRecalc3 = GetParaPortions()[0];
+ pRecalc4 = GetParaPortions()[aOldPositions.Max()+1];
+ }
+ else if ( aOldPositions.Max() == (nParaCount-1) )
+ {
+ pRecalc3 = GetParaPortions()[aOldPositions.Max()];
+ pRecalc4 = GetParaPortions()[aOldPositions.Min()-1];
+ }
+
+ MoveParagraphsInfo aMoveParagraphsInfo( aOldPositions.Min(), aOldPositions.Max(), nNewPos );
+ aBeginMovingParagraphsHdl.Call( aMoveParagraphsInfo );
+
+ if ( IsUndoEnabled() && !IsInUndo())
+ InsertUndo(std::make_unique<EditUndoMoveParagraphs>(pEditEngine, aOldPositions, nNewPos));
+
+ // do not lose sight of the Position !
+ ParaPortion* pDestPortion = GetParaPortions().SafeGetObject( nNewPos );
+
+ ParaPortionList aTmpPortionList;
+ for (tools::Long i = aOldPositions.Min(); i <= aOldPositions.Max(); i++ )
+ {
+ // always aOldPositions.Min(), since Remove().
+ std::unique_ptr<ParaPortion> pTmpPortion = GetParaPortions().Release(aOldPositions.Min());
+ maEditDoc.Release( aOldPositions.Min() );
+ aTmpPortionList.Append(std::move(pTmpPortion));
+ }
+
+ sal_Int32 nRealNewPos = pDestPortion ? GetParaPortions().GetPos( pDestPortion ) : GetParaPortions().Count();
+ assert( nRealNewPos != EE_PARA_NOT_FOUND && "ImpMoveParagraphs: Invalid Position!" );
+
+ sal_Int32 i = 0;
+ while( aTmpPortionList.Count() > 0 )
+ {
+ std::unique_ptr<ParaPortion> pTmpPortion = aTmpPortionList.Release(0);
+ if ( i == 0 )
+ aSelection.Min().SetNode( pTmpPortion->GetNode() );
+
+ aSelection.Max().SetNode( pTmpPortion->GetNode() );
+ aSelection.Max().SetIndex( pTmpPortion->GetNode()->Len() );
+
+ ContentNode* pN = pTmpPortion->GetNode();
+ maEditDoc.Insert(nRealNewPos+i, pN);
+
+ GetParaPortions().Insert(nRealNewPos+i, std::move(pTmpPortion));
+ ++i;
+ }
+
+ aEndMovingParagraphsHdl.Call( aMoveParagraphsInfo );
+
+ if ( GetNotifyHdl().IsSet() )
+ {
+ EENotify aNotify( EE_NOTIFY_PARAGRAPHSMOVED );
+ aNotify.nParagraph = nNewPos;
+ aNotify.nParam1 = aOldPositions.Min();
+ aNotify.nParam2 = aOldPositions.Max();
+ GetNotifyHdl().Call( aNotify );
+ }
+
+ maEditDoc.SetModified( true );
+
+ if ( pRecalc1 )
+ CalcHeight( pRecalc1 );
+ if ( pRecalc2 )
+ CalcHeight( pRecalc2 );
+ if ( pRecalc3 )
+ CalcHeight( pRecalc3 );
+ if ( pRecalc4 )
+ CalcHeight( pRecalc4 );
+
+#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
+ ParaPortionList::DbgCheck(GetParaPortions(), maEditDoc);
+#endif
+ return aSelection;
+}
+
+
+EditPaM ImpEditEngine::ImpConnectParagraphs( ContentNode* pLeft, ContentNode* pRight, bool bBackward )
+{
+ OSL_ENSURE( pLeft != pRight, "Join together the same paragraph ?" );
+ OSL_ENSURE( maEditDoc.GetPos( pLeft ) != EE_PARA_NOT_FOUND, "Inserted node not found (1)" );
+ OSL_ENSURE( maEditDoc.GetPos( pRight ) != EE_PARA_NOT_FOUND, "Inserted node not found (2)" );
+
+ // #i120020# it is possible that left and right are *not* in the desired order (left/right)
+ // so correct it. This correction is needed, else an invalid SfxLinkUndoAction will be
+ // created from ConnectParagraphs below. Assert this situation, it should be corrected by the
+ // caller.
+ if (maEditDoc.GetPos( pLeft ) > maEditDoc.GetPos( pRight ))
+ {
+ OSL_ENSURE(false, "ImpConnectParagraphs with wrong order of pLeft/pRight nodes (!)");
+ std::swap(pLeft, pRight);
+ }
+
+ sal_Int32 nParagraphTobeDeleted = maEditDoc.GetPos( pRight );
+ aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pRight, nParagraphTobeDeleted ));
+
+ GetEditEnginePtr()->ParagraphConnected( maEditDoc.GetPos( pLeft ), maEditDoc.GetPos( pRight ) );
+
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ InsertUndo( std::make_unique<EditUndoConnectParas>(pEditEngine,
+ maEditDoc.GetPos( pLeft ), pLeft->Len(),
+ pLeft->GetContentAttribs().GetItems(), pRight->GetContentAttribs().GetItems(),
+ pLeft->GetStyleSheet(), pRight->GetStyleSheet(), bBackward ) );
+ }
+
+ if ( bBackward )
+ {
+ pLeft->SetStyleSheet( pRight->GetStyleSheet() );
+ // it feels wrong to set pLeft's attribs if pRight is empty, tdf#128046
+ if ( pRight->Len() )
+ pLeft->GetContentAttribs().GetItems().Set( pRight->GetContentAttribs().GetItems() );
+ pLeft->GetCharAttribs().GetDefFont() = pRight->GetCharAttribs().GetDefFont();
+ }
+
+ ParaAttribsChanged( pLeft, true );
+
+ // First search for Portions since pRight is gone after ConnectParagraphs.
+ ParaPortion* pLeftPortion = FindParaPortion( pLeft );
+ assert(pLeftPortion);
+
+ if ( GetStatus().DoOnlineSpelling() )
+ {
+ sal_Int32 nEnd = pLeft->Len();
+ sal_Int32 nInv = nEnd ? nEnd-1 : nEnd;
+ pLeft->GetWrongList()->ClearWrongs( nInv, static_cast<size_t>(-1), pLeft ); // Possibly remove one
+ pLeft->GetWrongList()->SetInvalidRange(nInv, nEnd+1);
+ // Take over misspelled words
+ WrongList* pRWrongs = pRight->GetWrongList();
+ for (auto & elem : *pRWrongs)
+ {
+ if (elem.mnStart != 0) // Not a subsequent
+ {
+ elem.mnStart = elem.mnStart + nEnd;
+ elem.mnEnd = elem.mnEnd + nEnd;
+ pLeft->GetWrongList()->push_back(elem);
+ }
+ }
+ }
+
+ if ( IsCallParaInsertedOrDeleted() )
+ GetEditEnginePtr()->ParagraphDeleted( nParagraphTobeDeleted );
+
+ EditPaM aPaM = maEditDoc.ConnectParagraphs( pLeft, pRight );
+ GetParaPortions().Remove( nParagraphTobeDeleted );
+
+ pLeftPortion->MarkSelectionInvalid( aPaM.GetIndex() );
+
+ // the right node is deleted by EditDoc:ConnectParagraphs().
+ if ( GetTextRanger() )
+ {
+ // By joining together the two, the left is although reformatted,
+ // however if its height does not change then the formatting receives
+ // the change of the total text height too late...
+ for ( sal_Int32 n = nParagraphTobeDeleted; n < GetParaPortions().Count(); n++ )
+ {
+ ParaPortion* pPP = GetParaPortions()[n];
+ pPP->MarkSelectionInvalid( 0 );
+ pPP->GetLines().Reset();
+ }
+ }
+
+ TextModified();
+
+ return aPaM;
+}
+
+EditPaM ImpEditEngine::DeleteLeftOrRight( const EditSelection& rSel, sal_uInt8 nMode, DeleteMode nDelMode )
+{
+ OSL_ENSURE( !rSel.DbgIsBuggy( maEditDoc ), "Index out of range in DeleteLeftOrRight" );
+
+ if ( rSel.HasRange() ) // only then Delete Selection
+ return ImpDeleteSelection( rSel );
+
+ EditPaM aCurPos( rSel.Max() );
+ EditPaM aDelStart( aCurPos );
+ EditPaM aDelEnd( aCurPos );
+ if ( nMode == DEL_LEFT )
+ {
+ if ( nDelMode == DeleteMode::Simple )
+ {
+ sal_uInt16 nCharMode = i18n::CharacterIteratorMode::SKIPCHARACTER;
+ // If we are deleting a variation selector, we want to delete the
+ // whole sequence (cell).
+ sal_Int32 nIndex = aCurPos.GetIndex();
+ if (nIndex > 0)
+ {
+ const OUString& rString = aCurPos.GetNode()->GetString();
+ sal_Int32 nCode = rString.iterateCodePoints(&nIndex, -1);
+ if (unicode::isVariationSelector(nCode))
+ nCharMode = i18n::CharacterIteratorMode::SKIPCELL;
+ }
+ aDelStart = CursorLeft(aCurPos, nCharMode);
+ }
+ else if ( nDelMode == DeleteMode::RestOfWord )
+ {
+ aDelStart = StartOfWord( aCurPos );
+ if ( aDelStart.GetIndex() == aCurPos.GetIndex() )
+ aDelStart = WordLeft( aCurPos );
+ }
+ else // DELMODE_RESTOFCONTENT
+ {
+ aDelStart.SetIndex( 0 );
+ if ( aDelStart == aCurPos )
+ {
+ // Complete paragraph previous
+ ContentNode* pPrev = GetPrevVisNode( aCurPos.GetNode() );
+ if ( pPrev )
+ aDelStart = EditPaM( pPrev, 0 );
+ }
+ }
+ }
+ else
+ {
+ if ( nDelMode == DeleteMode::Simple )
+ {
+ aDelEnd = CursorRight( aCurPos );
+ }
+ else if ( nDelMode == DeleteMode::RestOfWord )
+ {
+ aDelEnd = EndOfWord( aCurPos );
+ if (aDelEnd.GetIndex() == aCurPos.GetIndex())
+ {
+ const sal_Int32 nLen(aCurPos.GetNode()->Len());
+ // end of para?
+ if (aDelEnd.GetIndex() == nLen)
+ {
+ ContentNode* pNext = GetNextVisNode( aCurPos.GetNode() );
+ if ( pNext )
+ aDelEnd = EditPaM( pNext, 0 );
+ }
+ else // there's still something to delete on the right
+ {
+ aDelEnd = EndOfWord( WordRight( aCurPos ) );
+ }
+ }
+ }
+ else // DELMODE_RESTOFCONTENT
+ {
+ aDelEnd.SetIndex( aCurPos.GetNode()->Len() );
+ if ( aDelEnd == aCurPos )
+ {
+ // Complete paragraph next
+ ContentNode* pNext = GetNextVisNode( aCurPos.GetNode() );
+ if ( pNext )
+ aDelEnd = EditPaM( pNext, pNext->Len() );
+ }
+ }
+ }
+
+ // ConnectParagraphs not enough for different Nodes when
+ // DeleteMode::RestOfContent.
+ if ( ( nDelMode == DeleteMode::RestOfContent ) || ( aDelStart.GetNode() == aDelEnd.GetNode() ) )
+ return ImpDeleteSelection( EditSelection( aDelStart, aDelEnd ) );
+
+ return ImpConnectParagraphs(aDelStart.GetNode(), aDelEnd.GetNode());
+}
+
+EditPaM ImpEditEngine::ImpDeleteSelection(const EditSelection& rCurSel)
+{
+ if ( !rCurSel.HasRange() )
+ return rCurSel.Min();
+
+ EditSelection aCurSel(rCurSel);
+ aCurSel.Adjust( maEditDoc );
+ EditPaM aStartPaM(aCurSel.Min());
+ EditPaM aEndPaM(aCurSel.Max());
+
+ if( nullptr != aStartPaM.GetNode() )
+ aStartPaM.GetNode()->checkAndDeleteEmptyAttribs(); // only so that newly set Attributes disappear...
+ if( nullptr != aEndPaM.GetNode() )
+ aEndPaM.GetNode()->checkAndDeleteEmptyAttribs(); // only so that newly set Attributes disappear...
+
+ OSL_ENSURE( aStartPaM.GetIndex() <= aStartPaM.GetNode()->Len(), "Index out of range in ImpDeleteSelection" );
+ OSL_ENSURE( aEndPaM.GetIndex() <= aEndPaM.GetNode()->Len(), "Index out of range in ImpDeleteSelection" );
+
+ sal_Int32 nStartNode = maEditDoc.GetPos( aStartPaM.GetNode() );
+ sal_Int32 nEndNode = maEditDoc.GetPos( aEndPaM.GetNode() );
+
+ OSL_ENSURE( nEndNode != EE_PARA_NOT_FOUND, "Start > End ?!" );
+ OSL_ENSURE( nStartNode <= nEndNode, "Start > End ?!" );
+
+ // Remove all nodes in between...
+ for ( sal_Int32 z = nStartNode+1; z < nEndNode; z++ )
+ {
+ // Always nStartNode+1, due to Remove()!
+ ImpRemoveParagraph( nStartNode+1 );
+ }
+
+ if ( aStartPaM.GetNode() != aEndPaM.GetNode() )
+ {
+ // The Rest of the StartNodes...
+ ImpRemoveChars( aStartPaM, aStartPaM.GetNode()->Len() - aStartPaM.GetIndex() );
+ ParaPortion* pPortion = FindParaPortion( aStartPaM.GetNode() );
+ assert(pPortion);
+ pPortion->MarkSelectionInvalid( aStartPaM.GetIndex() );
+
+ // The beginning of the EndNodes...
+ const sal_Int32 nChars = aEndPaM.GetIndex();
+ aEndPaM.SetIndex( 0 );
+ ImpRemoveChars( aEndPaM, nChars );
+ pPortion = FindParaPortion( aEndPaM.GetNode() );
+ assert(pPortion);
+ pPortion->MarkSelectionInvalid( 0 );
+ // Join together...
+ aStartPaM = ImpConnectParagraphs( aStartPaM.GetNode(), aEndPaM.GetNode() );
+ }
+ else
+ {
+ ImpRemoveChars( aStartPaM, aEndPaM.GetIndex() - aStartPaM.GetIndex() );
+ ParaPortion* pPortion = FindParaPortion( aStartPaM.GetNode() );
+ assert(pPortion);
+ pPortion->MarkInvalid( aEndPaM.GetIndex(), aStartPaM.GetIndex() - aEndPaM.GetIndex() );
+ }
+
+ UpdateSelections();
+ TextModified();
+ return aStartPaM;
+}
+
+void ImpEditEngine::ImpRemoveParagraph( sal_Int32 nPara )
+{
+ ContentNode* pNode = maEditDoc.GetObject( nPara );
+ ContentNode* pNextNode = maEditDoc.GetObject( nPara+1 );
+
+ assert(pNode);
+
+ aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pNode, nPara ));
+
+ // The node is managed by the undo and possibly destroyed!
+ maEditDoc.Release( nPara );
+ GetParaPortions().Remove( nPara );
+
+ if ( IsCallParaInsertedOrDeleted() )
+ {
+ GetEditEnginePtr()->ParagraphDeleted( nPara );
+ }
+
+ // Extra-Space may be determined again in the following. For
+ // ParaAttribsChanged the paragraph is unfortunately formatted again,
+ // however this method should not be time critical!
+ if ( pNextNode )
+ ParaAttribsChanged( pNextNode );
+
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo(std::make_unique<EditUndoDelContent>(pEditEngine, pNode, nPara));
+ else
+ {
+ if ( pNode->GetStyleSheet() )
+ EndListening( *pNode->GetStyleSheet() );
+ delete pNode;
+ }
+}
+
+EditPaM ImpEditEngine::AutoCorrect( const EditSelection& rCurSel, sal_Unicode c,
+ bool bOverwrite, vcl::Window const * pFrameWin )
+{
+ // i.e. Calc has special needs regarding a leading single quotation mark
+ // when starting cell input.
+ if (c == '\'' && !IsReplaceLeadingSingleQuotationMark() &&
+ rCurSel.Min() == rCurSel.Max() && rCurSel.Max().GetIndex() == 0)
+ {
+ return InsertTextUserInput( rCurSel, c, bOverwrite );
+ }
+
+ EditSelection aSel( rCurSel );
+ SvxAutoCorrect* pAutoCorrect = SvxAutoCorrCfg::Get().GetAutoCorrect();
+ if ( pAutoCorrect )
+ {
+ if ( aSel.HasRange() )
+ aSel = ImpDeleteSelection( rCurSel );
+
+ // #i78661 allow application to turn off capitalization of
+ // start sentence explicitly.
+ // (This is done by setting IsFirstWordCapitalization to sal_False.)
+ bool bOldCapitalStartSentence = pAutoCorrect->IsAutoCorrFlag( ACFlags::CapitalStartSentence );
+ if (!IsFirstWordCapitalization())
+ {
+ ESelection aESel( CreateESel(aSel) );
+ EditSelection aFirstWordSel;
+ EditSelection aSecondWordSel;
+ if (aESel.nEndPara == 0) // is this the first para?
+ {
+ // select first word...
+ // start by checking if para starts with word.
+ aFirstWordSel = SelectWord( CreateSel(ESelection()) );
+ if (aFirstWordSel.Min().GetIndex() == 0 && aFirstWordSel.Max().GetIndex() == 0)
+ {
+ // para does not start with word -> select next/first word
+ EditPaM aRightWord( WordRight( aFirstWordSel.Max() ) );
+ aFirstWordSel = SelectWord( EditSelection( aRightWord ) );
+ }
+
+ // select second word
+ // (sometimes aSel might not point to the end of the first word
+ // but to some following char like '.'. ':', ...
+ // In those cases we need aSecondWordSel to see if aSel
+ // will actually effect the first word.)
+ EditPaM aRight2Word( WordRight( aFirstWordSel.Max() ) );
+ aSecondWordSel = SelectWord( EditSelection( aRight2Word ) );
+ }
+ bool bIsFirstWordInFirstPara = aESel.nEndPara == 0 &&
+ aFirstWordSel.Max().GetIndex() <= aSel.Max().GetIndex() &&
+ aSel.Max().GetIndex() <= aSecondWordSel.Min().GetIndex();
+
+ if (bIsFirstWordInFirstPara)
+ pAutoCorrect->SetAutoCorrFlag( ACFlags::CapitalStartSentence, IsFirstWordCapitalization() );
+ }
+
+ ContentNode* pNode = aSel.Max().GetNode();
+ const sal_Int32 nIndex = aSel.Max().GetIndex();
+ EdtAutoCorrDoc aAuto(pEditEngine, pNode, nIndex, c);
+ // FIXME: this _must_ be called with reference to the actual node text!
+ OUString const& rNodeString(pNode->GetString());
+ pAutoCorrect->DoAutoCorrect(
+ aAuto, rNodeString, nIndex, c, !bOverwrite, mbNbspRunNext, pFrameWin );
+ aSel.Max().SetIndex( aAuto.GetCursor() );
+
+ // #i78661 since the SvxAutoCorrect object used here is
+ // shared we need to reset the value to its original state.
+ pAutoCorrect->SetAutoCorrFlag( ACFlags::CapitalStartSentence, bOldCapitalStartSentence );
+ }
+ return aSel.Max();
+}
+
+
+EditPaM ImpEditEngine::InsertTextUserInput( const EditSelection& rCurSel,
+ sal_Unicode c, bool bOverwrite )
+{
+ OSL_ENSURE( c != '\t', "Tab for InsertText ?" );
+ OSL_ENSURE( c != '\n', "Word wrapping for InsertText ?");
+
+ EditPaM aPaM( rCurSel.Min() );
+
+ bool bDoOverwrite = bOverwrite &&
+ ( aPaM.GetIndex() < aPaM.GetNode()->Len() );
+
+ bool bUndoAction = ( rCurSel.HasRange() || bDoOverwrite );
+
+ if ( bUndoAction )
+ UndoActionStart( EDITUNDO_INSERT );
+
+ if ( rCurSel.HasRange() )
+ {
+ aPaM = ImpDeleteSelection( rCurSel );
+ }
+ else if ( bDoOverwrite )
+ {
+ // If selected, then do not also overwrite a character!
+ EditSelection aTmpSel( aPaM );
+ aTmpSel.Max().SetIndex( aTmpSel.Max().GetIndex()+1 );
+ OSL_ENSURE( !aTmpSel.DbgIsBuggy( maEditDoc ), "Overwrite: Wrong selection! ");
+ ImpDeleteSelection( aTmpSel );
+ }
+
+ if ( aPaM.GetNode()->Len() < MAXCHARSINPARA )
+ {
+ if (IsInputSequenceCheckingRequired( c, rCurSel ))
+ {
+ uno::Reference < i18n::XExtendedInputSequenceChecker > _xISC( ImplGetInputSequenceChecker() );
+
+ if (_xISC)
+ {
+ const sal_Int32 nTmpPos = aPaM.GetIndex();
+ sal_Int16 nCheckMode = SvtCTLOptions::IsCTLSequenceCheckingRestricted() ?
+ i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC;
+
+ // the text that needs to be checked is only the one
+ // before the current cursor position
+ const OUString aOldText( aPaM.GetNode()->Copy(0, nTmpPos) );
+ OUString aNewText( aOldText );
+ if (SvtCTLOptions::IsCTLSequenceCheckingTypeAndReplace())
+ {
+ _xISC->correctInputSequence(aNewText, nTmpPos - 1, c, nCheckMode);
+
+ // find position of first character that has changed
+ sal_Int32 nOldLen = aOldText.getLength();
+ sal_Int32 nNewLen = aNewText.getLength();
+ const sal_Unicode *pOldTxt = aOldText.getStr();
+ const sal_Unicode *pNewTxt = aNewText.getStr();
+ sal_Int32 nChgPos = 0;
+ while ( nChgPos < nOldLen && nChgPos < nNewLen &&
+ pOldTxt[nChgPos] == pNewTxt[nChgPos] )
+ ++nChgPos;
+
+ const OUString aChgText( aNewText.copy( nChgPos ) );
+
+ // select text from first pos to be changed to current pos
+ EditSelection aSel( EditPaM( aPaM.GetNode(), nChgPos ), aPaM );
+
+ if (!aChgText.isEmpty())
+ return InsertText( aSel, aChgText ); // implicitly handles undo
+ else
+ return aPaM;
+ }
+ else
+ {
+ // should the character be ignored (i.e. not get inserted) ?
+ if (!_xISC->checkInputSequence( aOldText, nTmpPos - 1, c, nCheckMode ))
+ return aPaM; // nothing to be done -> no need for undo
+ }
+ }
+
+ // at this point now we will insert the character 'normally' some lines below...
+ }
+
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ std::unique_ptr<EditUndoInsertChars> pNewUndo(new EditUndoInsertChars(pEditEngine, CreateEPaM(aPaM), OUString(c)));
+ bool bTryMerge = !bDoOverwrite && ( c != ' ' );
+ InsertUndo( std::move(pNewUndo), bTryMerge );
+ }
+
+ maEditDoc.InsertText( aPaM, OUStringChar(c) );
+ ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() );
+ assert(pPortion);
+ pPortion->MarkInvalid( aPaM.GetIndex(), 1 );
+ aPaM.SetIndex( aPaM.GetIndex()+1 ); // does not do EditDoc-Method anymore
+ }
+
+ TextModified();
+
+ if ( bUndoAction )
+ UndoActionEnd();
+
+ return aPaM;
+}
+
+EditPaM ImpEditEngine::ImpInsertText(const EditSelection& aCurSel, const OUString& rStr)
+{
+ UndoActionStart( EDITUNDO_INSERT );
+
+ EditPaM aPaM;
+ if ( aCurSel.HasRange() )
+ aPaM = ImpDeleteSelection( aCurSel );
+ else
+ aPaM = aCurSel.Max();
+
+ EditPaM aCurPaM( aPaM ); // for the Invalidate
+
+ // get word boundaries in order to clear possible WrongList entries
+ // and invalidate all the necessary text (everything after and including the
+ // start of the word)
+ // #i107201# do the expensive SelectWord call only if online spelling is active
+ EditSelection aCurWord;
+ if ( GetStatus().DoOnlineSpelling() )
+ aCurWord = SelectWord( aCurPaM, i18n::WordType::DICTIONARY_WORD );
+
+ OUString aText(convertLineEnd(rStr, LINEEND_LF));
+ if (mbFuzzing) //tab expansion performance in editeng is appalling
+ aText = aText.replaceAll("\t","-");
+ SfxVoidItem aTabItem( EE_FEATURE_TAB );
+
+ // Converts to linesep = \n
+ // Token LINE_SEP query,
+ // since the MAC-Compiler makes something else from \n !
+
+ sal_Int32 nStart = 0;
+ while ( nStart < aText.getLength() )
+ {
+ sal_Int32 nEnd = !maStatus.IsSingleLine() ?
+ aText.indexOf( LINE_SEP, nStart ) : -1;
+ if ( nEnd == -1 )
+ nEnd = aText.getLength(); // not dereference!
+
+ // Start == End => empty line
+ if ( nEnd > nStart )
+ {
+ OUString aLine = aText.copy( nStart, nEnd-nStart );
+ sal_Int32 nExistingChars = aPaM.GetNode()->Len();
+ sal_Int32 nChars = nExistingChars + aLine.getLength();
+ if (nChars > MAXCHARSINPARA)
+ {
+ sal_Int32 nMaxNewChars = std::max<sal_Int32>(0, MAXCHARSINPARA - nExistingChars);
+ // Wherever we break, it may be wrong. However, try to find the
+ // previous non-alnum/non-letter character. Note this is only
+ // in the to be appended data, otherwise already existing
+ // characters would have to be moved and PaM to be updated.
+ // Restrict to 2*42, if not found by then assume other data or
+ // language-script uses only letters or idiographs.
+ sal_Int32 nPos = nMaxNewChars;
+ while (nPos-- > 0 && (nMaxNewChars - nPos) <= 84)
+ {
+ auto nNextPos = nPos;
+ const auto c = aLine.iterateCodePoints(&nNextPos);
+ switch (unicode::getUnicodeType(c))
+ {
+ case css::i18n::UnicodeType::UPPERCASE_LETTER:
+ case css::i18n::UnicodeType::LOWERCASE_LETTER:
+ case css::i18n::UnicodeType::TITLECASE_LETTER:
+ case css::i18n::UnicodeType::MODIFIER_LETTER:
+ case css::i18n::UnicodeType::OTHER_LETTER:
+ case css::i18n::UnicodeType::DECIMAL_DIGIT_NUMBER:
+ case css::i18n::UnicodeType::LETTER_NUMBER:
+ case css::i18n::UnicodeType::OTHER_NUMBER:
+ case css::i18n::UnicodeType::CURRENCY_SYMBOL:
+ break;
+ default:
+ {
+ // Ignore NO-BREAK spaces, NBSP, NNBSP, ZWNBSP.
+ if (c == 0x00A0 || c == 0x202F || c == 0xFEFF)
+ break;
+ const auto n = aLine.iterateCodePoints(&nNextPos, 0);
+ if (c == '-' && nNextPos < nMaxNewChars)
+ {
+ // Keep HYPHEN-MINUS with a number to the right.
+ const sal_Int16 t = unicode::getUnicodeType(n);
+ if ( t == css::i18n::UnicodeType::DECIMAL_DIGIT_NUMBER ||
+ t == css::i18n::UnicodeType::LETTER_NUMBER ||
+ t == css::i18n::UnicodeType::OTHER_NUMBER)
+ nMaxNewChars = nPos; // line break before
+ else
+ nMaxNewChars = nNextPos; // line break after
+ }
+ else
+ {
+ nMaxNewChars = nNextPos; // line break after
+ }
+ nPos = 0; // will break loop
+ }
+ }
+ }
+ // Remaining characters end up in the next paragraph. Note that
+ // new nStart will be nEnd+1 below so decrement by one more.
+ nEnd -= (aLine.getLength() - nMaxNewChars + 1);
+ aLine = aLine.copy( 0, nMaxNewChars ); // Delete the Rest...
+ }
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo(std::make_unique<EditUndoInsertChars>(pEditEngine, CreateEPaM(aPaM), aLine));
+ // Tabs ?
+ if ( aLine.indexOf( '\t' ) == -1 )
+ aPaM = maEditDoc.InsertText( aPaM, aLine );
+ else
+ {
+ sal_Int32 nStart2 = 0;
+ while ( nStart2 < aLine.getLength() )
+ {
+ sal_Int32 nEnd2 = aLine.indexOf( "\t", nStart2 );
+ if ( nEnd2 == -1 )
+ nEnd2 = aLine.getLength(); // not dereference!
+
+ if ( nEnd2 > nStart2 )
+ aPaM = maEditDoc.InsertText( aPaM, aLine.subView( nStart2, nEnd2-nStart2 ) );
+ if ( nEnd2 < aLine.getLength() )
+ {
+ aPaM = maEditDoc.InsertFeature( aPaM, aTabItem );
+ }
+ nStart2 = nEnd2+1;
+ }
+ }
+ ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() );
+ assert(pPortion);
+
+ if ( GetStatus().DoOnlineSpelling() )
+ {
+ // now remove the Wrongs (red spell check marks) from both words...
+ WrongList *pWrongs = aCurPaM.GetNode()->GetWrongList();
+ if (pWrongs && !pWrongs->empty())
+ pWrongs->ClearWrongs( aCurWord.Min().GetIndex(), aPaM.GetIndex(), aPaM.GetNode() );
+ // ... and mark both words as 'to be checked again'
+ pPortion->MarkInvalid( aCurWord.Min().GetIndex(), aLine.getLength() );
+ }
+ else
+ pPortion->MarkInvalid( aCurPaM.GetIndex(), aLine.getLength() );
+ }
+ if ( nEnd < aText.getLength() )
+ aPaM = ImpInsertParaBreak( aPaM );
+
+ nStart = nEnd+1;
+ }
+
+ UndoActionEnd();
+
+ TextModified();
+ return aPaM;
+}
+
+EditPaM ImpEditEngine::ImpFastInsertText( EditPaM aPaM, const OUString& rStr )
+{
+ OSL_ENSURE( rStr.indexOf( 0x0A ) == -1, "FastInsertText: Newline not allowed! ");
+ OSL_ENSURE( rStr.indexOf( 0x0D ) == -1, "FastInsertText: Newline not allowed! ");
+ OSL_ENSURE( rStr.indexOf( '\t' ) == -1, "FastInsertText: Newline not allowed! ");
+
+ if ( ( aPaM.GetNode()->Len() + rStr.getLength() ) < MAXCHARSINPARA )
+ {
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo(std::make_unique<EditUndoInsertChars>(pEditEngine, CreateEPaM(aPaM), rStr));
+
+ aPaM = maEditDoc.InsertText( aPaM, rStr );
+ TextModified();
+ }
+ else
+ {
+ aPaM = ImpInsertText( aPaM, rStr );
+ }
+
+ return aPaM;
+}
+
+EditPaM ImpEditEngine::ImpInsertFeature(const EditSelection& rCurSel, const SfxPoolItem& rItem)
+{
+ EditPaM aPaM;
+ if ( rCurSel.HasRange() )
+ aPaM = ImpDeleteSelection( rCurSel );
+ else
+ aPaM = rCurSel.Max();
+
+ if ( aPaM.GetIndex() >= SAL_MAX_INT32-1 )
+ return aPaM;
+
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo(std::make_unique<EditUndoInsertFeature>(pEditEngine, CreateEPaM(aPaM), rItem));
+ aPaM = maEditDoc.InsertFeature( aPaM, rItem );
+ UpdateFields();
+
+ ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() );
+ assert(pPortion);
+ pPortion->MarkInvalid( aPaM.GetIndex()-1, 1 );
+
+ TextModified();
+
+ return aPaM;
+}
+
+EditPaM ImpEditEngine::ImpInsertParaBreak( const EditSelection& rCurSel )
+{
+ EditPaM aPaM;
+ if ( rCurSel.HasRange() )
+ aPaM = ImpDeleteSelection( rCurSel );
+ else
+ aPaM = rCurSel.Max();
+
+ return ImpInsertParaBreak( aPaM );
+}
+
+EditPaM ImpEditEngine::ImpInsertParaBreak( EditPaM& rPaM, bool bKeepEndingAttribs )
+{
+ if ( maEditDoc.Count() >= EE_PARA_MAX_COUNT )
+ {
+ SAL_WARN( "editeng", "ImpEditEngine::ImpInsertParaBreak - can't process more than "
+ << EE_PARA_MAX_COUNT << " paragraphs!");
+ return rPaM;
+ }
+
+ if ( IsUndoEnabled() && !IsInUndo() )
+ InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, maEditDoc.GetPos(rPaM.GetNode()), rPaM.GetIndex()));
+
+ EditPaM aPaM( maEditDoc.InsertParaBreak( rPaM, bKeepEndingAttribs ) );
+ if (auto pStyle = aPaM.GetNode()->GetStyleSheet())
+ StartListening(*pStyle, DuplicateHandling::Allow);
+
+ if ( GetStatus().DoOnlineSpelling() )
+ {
+ sal_Int32 nEnd = rPaM.GetNode()->Len();
+ aPaM.GetNode()->CreateWrongList();
+ WrongList* pLWrongs = rPaM.GetNode()->GetWrongList();
+ WrongList* pRWrongs = aPaM.GetNode()->GetWrongList();
+ // take over misspelled words:
+ for (auto & elem : *pLWrongs)
+ {
+ // Correct only if really a word gets overlapped in the process of
+ // Spell checking
+ if (elem.mnStart > o3tl::make_unsigned(nEnd))
+ {
+ pRWrongs->push_back(elem);
+ editeng::MisspellRange& rRWrong = pRWrongs->back();
+ rRWrong.mnStart = rRWrong.mnStart - nEnd;
+ rRWrong.mnEnd = rRWrong.mnEnd - nEnd;
+ }
+ else if (elem.mnStart < o3tl::make_unsigned(nEnd) && elem.mnEnd > o3tl::make_unsigned(nEnd))
+ elem.mnEnd = nEnd;
+ }
+ sal_Int32 nInv = nEnd ? nEnd-1 : nEnd;
+ if ( nEnd )
+ pLWrongs->SetInvalidRange(nInv, nEnd);
+ else
+ pLWrongs->SetValid();
+ pRWrongs->SetValid();
+ pRWrongs->SetInvalidRange(0, 1); // Only test the first word
+ }
+
+ ParaPortion* pPortion = FindParaPortion( rPaM.GetNode() );
+ assert(pPortion);
+ pPortion->MarkInvalid( rPaM.GetIndex(), 0 );
+
+ // Optimization: Do not place unnecessarily many getPos to Listen!
+ // Here, as in undo, but also in all other methods.
+ sal_Int32 nPos = GetParaPortions().GetPos( pPortion );
+ ParaPortion* pNewPortion = new ParaPortion( aPaM.GetNode() );
+ GetParaPortions().Insert(nPos+1, std::unique_ptr<ParaPortion>(pNewPortion));
+ ParaAttribsChanged( pNewPortion->GetNode() );
+ if ( IsCallParaInsertedOrDeleted() )
+ GetEditEnginePtr()->ParagraphInserted( nPos+1 );
+
+ if( nullptr != rPaM.GetNode() )
+ rPaM.GetNode()->checkAndDeleteEmptyAttribs(); // if empty Attributes have emerged.
+
+ TextModified();
+ return aPaM;
+}
+
+EditPaM ImpEditEngine::ImpFastInsertParagraph( sal_Int32 nPara )
+{
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ if ( nPara )
+ {
+ assert(maEditDoc.GetObject(nPara - 1));
+ InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, nPara-1, maEditDoc.GetObject( nPara-1 )->Len()));
+ }
+ else
+ InsertUndo(std::make_unique<EditUndoSplitPara>(pEditEngine, 0, 0));
+ }
+
+ ContentNode* pNode = new ContentNode( maEditDoc.GetItemPool() );
+ // If flat mode, then later no Font is set:
+ pNode->GetCharAttribs().GetDefFont() = maEditDoc.GetDefFont();
+
+ if ( GetStatus().DoOnlineSpelling() )
+ pNode->CreateWrongList();
+
+ maEditDoc.Insert(nPara, pNode);
+
+ GetParaPortions().Insert(nPara, std::make_unique<ParaPortion>( pNode ));
+ if ( IsCallParaInsertedOrDeleted() )
+ GetEditEnginePtr()->ParagraphInserted( nPara );
+
+ return EditPaM( pNode, 0 );
+}
+
+EditPaM ImpEditEngine::InsertParaBreak(const EditSelection& rCurSel)
+{
+ EditPaM aPaM(ImpInsertParaBreak(rCurSel));
+ if ( maStatus.DoAutoIndenting() )
+ {
+ sal_Int32 nPara = maEditDoc.GetPos( aPaM.GetNode() );
+ OSL_ENSURE( nPara > 0, "AutoIndenting: Error!" );
+ const OUString aPrevParaText( GetEditDoc().GetParaAsString( nPara-1 ) );
+ sal_Int32 n = 0;
+ while ( ( n < aPrevParaText.getLength() ) &&
+ ( ( aPrevParaText[n] == ' ' ) || ( aPrevParaText[n] == '\t' ) ) )
+ {
+ if ( aPrevParaText[n] == '\t' )
+ aPaM = ImpInsertFeature( aPaM, SfxVoidItem( EE_FEATURE_TAB ) );
+ else
+ aPaM = ImpInsertText( aPaM, OUString(aPrevParaText[n]) );
+ n++;
+ }
+
+ }
+ return aPaM;
+}
+
+EditPaM ImpEditEngine::InsertTab(const EditSelection& rCurSel)
+{
+ EditPaM aPaM( ImpInsertFeature(rCurSel, SfxVoidItem(EE_FEATURE_TAB )));
+ return aPaM;
+}
+
+EditPaM ImpEditEngine::InsertField(const EditSelection& rCurSel, const SvxFieldItem& rFld)
+{
+ return ImpInsertFeature(rCurSel, rFld);
+}
+
+bool ImpEditEngine::UpdateFields()
+{
+ bool bChanges = false;
+ sal_Int32 nParas = GetEditDoc().Count();
+ for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ )
+ {
+ bool bChangesInPara = false;
+ ContentNode* pNode = GetEditDoc().GetObject( nPara );
+ assert(pNode);
+ CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs();
+ for (std::unique_ptr<EditCharAttrib> & rAttrib : rAttribs)
+ {
+ EditCharAttrib& rAttr = *rAttrib;
+ if (rAttr.Which() == EE_FEATURE_FIELD)
+ {
+ EditCharAttribField& rField = static_cast<EditCharAttribField&>(rAttr);
+ EditCharAttribField aCurrent(rField);
+ rField.Reset();
+
+ if (!maStatus.MarkNonUrlFields() && !maStatus.MarkUrlFields())
+ ; // nothing marked
+ else if (maStatus.MarkNonUrlFields() && maStatus.MarkUrlFields())
+ rField.GetFieldColor() = GetColorConfig().GetColorValue(svtools::WRITERFIELDSHADINGS).nColor;
+ else
+ {
+ bool bURL = false;
+ if (const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(rField.GetItem()))
+ {
+ if (const SvxFieldData* pFieldData = pFieldItem->GetField())
+ bURL = (dynamic_cast<const SvxURLField* >(pFieldData) != nullptr);
+ }
+ if ((bURL && maStatus.MarkUrlFields()) || (!bURL && maStatus.MarkNonUrlFields()))
+ rField.GetFieldColor() = GetColorConfig().GetColorValue( svtools::WRITERFIELDSHADINGS ).nColor;
+ }
+
+ const OUString aFldValue =
+ GetEditEnginePtr()->CalcFieldValue(
+ static_cast<const SvxFieldItem&>(*rField.GetItem()),
+ nPara, rField.GetStart(), rField.GetTextColor(), rField.GetFieldColor(), rField.GetFldLineStyle() );
+
+ rField.SetFieldValue(aFldValue);
+ if (rField != aCurrent)
+ {
+ bChanges = true;
+ bChangesInPara = true;
+ }
+ }
+ }
+ if ( bChangesInPara )
+ {
+ // If possible be more precise when invalidate.
+ ParaPortion* pPortion = GetParaPortions()[nPara];
+ assert(pPortion);
+ pPortion->MarkSelectionInvalid( 0 );
+ }
+ }
+ return bChanges;
+}
+
+EditPaM ImpEditEngine::InsertLineBreak(const EditSelection& aCurSel)
+{
+ EditPaM aPaM( ImpInsertFeature( aCurSel, SfxVoidItem( EE_FEATURE_LINEBR ) ) );
+ return aPaM;
+}
+
+
+// Helper functions
+
+tools::Rectangle ImpEditEngine::GetEditCursor(const ParaPortion* pPortion, const EditLine* pLine,
+ sal_Int32 nIndex, GetCursorFlags nFlags)
+{
+ assert(pPortion && pLine);
+ // nIndex might be not in the line
+ // Search within the line...
+ tools::Long nX;
+
+ if ((nIndex == pLine->GetStart()) && (nFlags & GetCursorFlags::StartOfLine))
+ {
+ Range aXRange = GetLineXPosStartEnd(pPortion, pLine);
+ nX = !IsRightToLeft(GetEditDoc().GetPos(pPortion->GetNode())) ? aXRange.Min()
+ : aXRange.Max();
+ }
+ else if ((nIndex == pLine->GetEnd()) && (nFlags & GetCursorFlags::EndOfLine))
+ {
+ Range aXRange = GetLineXPosStartEnd(pPortion, pLine);
+ nX = !IsRightToLeft(GetEditDoc().GetPos(pPortion->GetNode())) ? aXRange.Max()
+ : aXRange.Min();
+ }
+ else
+ {
+ nX = GetXPos(pPortion, pLine, nIndex, bool(nFlags & GetCursorFlags::PreferPortionStart));
+ }
+
+ tools::Rectangle aEditCursor;
+ aEditCursor.SetLeft(nX);
+ aEditCursor.SetRight(nX);
+
+ aEditCursor.SetBottom(pLine->GetHeight() - 1);
+ if (nFlags & GetCursorFlags::TextOnly)
+ aEditCursor.SetTop(aEditCursor.Bottom() - pLine->GetTxtHeight() + 1);
+ else
+ aEditCursor.SetTop(aEditCursor.Bottom()
+ - std::min(pLine->GetTxtHeight(), pLine->GetHeight()) + 1);
+ return aEditCursor;
+}
+
+tools::Rectangle ImpEditEngine::PaMtoEditCursor( EditPaM aPaM, GetCursorFlags nFlags )
+{
+ assert( IsUpdateLayout() && "Must not be reached when Update=FALSE: PaMtoEditCursor" );
+
+ tools::Rectangle aEditCursor;
+ const sal_Int32 nIndex = aPaM.GetIndex();
+ const ParaPortion* pPortion = nullptr;
+ const EditLine* pLastLine = nullptr;
+ tools::Rectangle aLineArea;
+
+ auto FindPortionLineAndArea
+ = [&, bEOL(bool(nFlags & GetCursorFlags::EndOfLine))](const LineAreaInfo& rInfo) {
+ if (!rInfo.pLine) // start of ParaPortion
+ {
+ ContentNode* pNode = rInfo.rPortion.GetNode();
+ OSL_ENSURE(pNode, "Invalid Node in Portion!");
+ if (pNode != aPaM.GetNode())
+ return CallbackResult::SkipThisPortion;
+ pPortion = &rInfo.rPortion;
+ }
+ else // guaranteed that this is the correct ParaPortion
+ {
+ pLastLine = rInfo.pLine;
+ aLineArea = rInfo.aArea;
+ if ((rInfo.pLine->GetStart() == nIndex) || (rInfo.pLine->IsIn(nIndex, bEOL)))
+ return CallbackResult::Stop;
+ }
+ return CallbackResult::Continue;
+ };
+ IterateLineAreas(FindPortionLineAndArea, IterFlag::none);
+
+ if (pLastLine)
+ {
+ aEditCursor = GetEditCursor(pPortion, pLastLine, nIndex, nFlags);
+ aEditCursor.Move(getTopLeftDocOffset(aLineArea));
+ }
+ else
+ OSL_FAIL("Line not found!");
+
+ return aEditCursor;
+}
+
+void ImpEditEngine::IterateLineAreas(const IterateLinesAreasFunc& f, IterFlag eOptions)
+{
+ const Point aOrigin(0, 0);
+ Point aLineStart(aOrigin);
+ const tools::Long nVertLineSpacing = CalcVertLineSpacing(aLineStart);
+ const tools::Long nColumnWidth = GetColumnWidth(maPaperSize);
+ sal_Int16 nColumn = 0;
+ for (sal_Int32 n = 0, nPortions = GetParaPortions().Count(); n < nPortions; ++n)
+ {
+ ParaPortion* pPortion = GetParaPortions()[n];
+ bool bSkipThis = true;
+ if (pPortion->IsVisible())
+ {
+ // when typing idle formatting, asynchronous Paint. Invisible Portions may be invalid.
+ if (pPortion->IsInvalid())
+ return;
+
+ LineAreaInfo aInfo{
+ *pPortion, // pPortion
+ nullptr, // pLine
+ 0, // nHeightNeededToNotWrap
+ { aLineStart, Size{ nColumnWidth, pPortion->GetFirstLineOffset() } }, // aArea
+ n, // nPortion
+ 0, // nLine
+ nColumn // nColumn
+ };
+ auto eResult = f(aInfo);
+ if (eResult == CallbackResult::Stop)
+ return;
+ bSkipThis = eResult == CallbackResult::SkipThisPortion;
+
+ sal_uInt16 nSBL = 0;
+ if (!maStatus.IsOutliner())
+ {
+ const SvxLineSpacingItem& rLSItem
+ = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL);
+ nSBL = (rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix)
+ ? scaleYSpacingValue(rLSItem.GetInterLineSpace())
+ : 0;
+ }
+
+ adjustYDirectionAware(aLineStart, pPortion->GetFirstLineOffset());
+ for (sal_Int32 nLine = 0, nLines = pPortion->GetLines().Count(); nLine < nLines; nLine++)
+ {
+ EditLine& rLine = pPortion->GetLines()[nLine];
+ tools::Long nLineHeight = rLine.GetHeight();
+ if (nLine != nLines - 1)
+ nLineHeight += nVertLineSpacing;
+ MoveToNextLine(aLineStart, nLineHeight, nColumn, aOrigin,
+ &aInfo.nHeightNeededToNotWrap);
+ const bool bInclILS = eOptions & IterFlag::inclILS;
+ if (bInclILS && (nLine != nLines - 1) && !maStatus.IsOutliner())
+ {
+ adjustYDirectionAware(aLineStart, nSBL);
+ nLineHeight += nSBL;
+ }
+
+ if (!bSkipThis)
+ {
+ Point aOtherCorner(aLineStart);
+ adjustXDirectionAware(aOtherCorner, nColumnWidth);
+ adjustYDirectionAware(aOtherCorner, -nLineHeight);
+
+ // Calls to f() for each line
+ aInfo.nColumn = nColumn;
+ aInfo.pLine = &rLine;
+ aInfo.nLine = nLine;
+ aInfo.aArea = tools::Rectangle::Normalize(aLineStart, aOtherCorner);
+ eResult = f(aInfo);
+ if (eResult == CallbackResult::Stop)
+ return;
+ bSkipThis = eResult == CallbackResult::SkipThisPortion;
+ }
+
+ if (!bInclILS && (nLine != nLines - 1) && !maStatus.IsOutliner())
+ adjustYDirectionAware(aLineStart, nSBL);
+ }
+ if (!maStatus.IsOutliner())
+ {
+ const SvxULSpaceItem& rULItem
+ = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE);
+ tools::Long nUL = scaleYSpacingValue(rULItem.GetLower());
+ adjustYDirectionAware(aLineStart, nUL);
+ }
+ }
+ // Invisible ParaPortion has no height (see ParaPortion::GetHeight), don't handle it
+ }
+}
+
+std::tuple<const ParaPortion*, const EditLine*, tools::Long>
+ImpEditEngine::GetPortionAndLine(Point aDocPos)
+{
+ // First find the column from the point
+ sal_Int32 nClickColumn = 0;
+ for (tools::Long nColumnStart = 0, nColumnWidth = GetColumnWidth(maPaperSize);;
+ nColumnStart += mnColumnSpacing + nColumnWidth, ++nClickColumn)
+ {
+ if (aDocPos.X() <= nColumnStart + nColumnWidth + mnColumnSpacing / 2)
+ break;
+ if (nClickColumn >= mnColumns - 1)
+ break;
+ }
+
+ const ParaPortion* pLastPortion = nullptr;
+ const EditLine* pLastLine = nullptr;
+ tools::Long nLineStartX = 0;
+ Point aPos;
+ adjustYDirectionAware(aPos, aDocPos.Y());
+
+ auto FindLastMatchingPortionAndLine = [&](const LineAreaInfo& rInfo) {
+ if (rInfo.pLine) // Only handle lines, not ParaPortion starts
+ {
+ if (rInfo.nColumn > nClickColumn)
+ return CallbackResult::Stop;
+ pLastPortion = &rInfo.rPortion; // Candidate paragraph
+ pLastLine = rInfo.pLine; // Last visible line not later than click position
+ nLineStartX = getTopLeftDocOffset(rInfo.aArea).Width();
+ if (rInfo.nColumn == nClickColumn && getYOverflowDirectionAware(aPos, rInfo.aArea) == 0)
+ return CallbackResult::Stop; // Found it
+ }
+ return CallbackResult::Continue;
+ };
+ IterateLineAreas(FindLastMatchingPortionAndLine, IterFlag::inclILS);
+
+ return { pLastPortion, pLastLine, nLineStartX };
+}
+
+EditPaM ImpEditEngine::GetPaM( Point aDocPos, bool bSmart )
+{
+ assert( IsUpdateLayout() && "Must not be reached when Update=FALSE: GetPaM" );
+
+ if (const auto& [pPortion, pLine, nLineStartX] = GetPortionAndLine(aDocPos); pPortion)
+ {
+ sal_Int32 nCurIndex
+ = GetChar(pPortion, pLine, aDocPos.X() - nLineStartX, bSmart);
+ EditPaM aPaM(pPortion->GetNode(), nCurIndex);
+
+ if (nCurIndex && (nCurIndex == pLine->GetEnd())
+ && (pLine != &pPortion->GetLines()[pPortion->GetLines().Count() - 1]))
+ {
+ aPaM = CursorLeft(aPaM);
+ }
+
+ return aPaM;
+ }
+ return {};
+}
+
+bool ImpEditEngine::IsTextPos(const Point& rDocPos, sal_uInt16 nBorder)
+{
+ if (const auto& [pPortion, pLine, nLineStartX] = GetPortionAndLine(rDocPos); pPortion)
+ {
+ Range aLineXPosStartEnd = GetLineXPosStartEnd(pPortion, pLine);
+ if ((rDocPos.X() >= nLineStartX + aLineXPosStartEnd.Min() - nBorder)
+ && (rDocPos.X() <= nLineStartX + aLineXPosStartEnd.Max() + nBorder))
+ return true;
+ }
+ return false;
+}
+
+sal_uInt32 ImpEditEngine::GetTextHeight() const
+{
+ assert( IsUpdateLayout() && "Should not be used for Update=FALSE: GetTextHeight" );
+ OSL_ENSURE( IsFormatted() || IsFormatting(), "GetTextHeight: Not formatted" );
+ return nCurTextHeight;
+}
+
+sal_uInt32 ImpEditEngine::CalcTextWidth( bool bIgnoreExtraSpace )
+{
+ // If still not formatted and not in the process.
+ // Will be brought in the formatting for AutoPageSize.
+ if ( !IsFormatted() && !IsFormatting() )
+ FormatDoc();
+
+ sal_uInt32 nMaxWidth = 0;
+
+ // Over all the paragraphs ...
+
+ sal_Int32 nParas = GetParaPortions().Count();
+ for ( sal_Int32 nPara = 0; nPara < nParas; nPara++ )
+ {
+ nMaxWidth = std::max(nMaxWidth, CalcParaWidth(nPara, bIgnoreExtraSpace));
+ }
+
+ return nMaxWidth;
+}
+
+sal_uInt32 ImpEditEngine::CalcParaWidth( sal_Int32 nPara, bool bIgnoreExtraSpace )
+{
+ // If still not formatted and not in the process.
+ // Will be brought in the formatting for AutoPageSize.
+ if ( !IsFormatted() && !IsFormatting() )
+ FormatDoc();
+
+ tools::Long nMaxWidth = 0;
+
+ // Over all the paragraphs ...
+
+ OSL_ENSURE( 0 <= nPara && nPara < GetParaPortions().Count(), "CalcParaWidth: Out of range" );
+ ParaPortion* pPortion = GetParaPortions()[nPara];
+ if ( pPortion && pPortion->IsVisible() )
+ {
+ const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pPortion->GetNode() );
+ sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pPortion->GetNode() );
+
+
+ // On the lines of the paragraph ...
+
+ sal_Int32 nLines = pPortion->GetLines().Count();
+ for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ )
+ {
+ EditLine& rLine = pPortion->GetLines()[nLine];
+ // nCurWidth = pLine->GetStartPosX();
+ // For Center- or Right- alignment it depends on the paper
+ // width, here not preferred. I general, it is best not leave it
+ // to StartPosX, also the right indents have to be taken into
+ // account!
+ tools::Long nCurWidth = scaleXSpacingValue(rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth);
+ if ( nLine == 0 )
+ {
+ tools::Long nFI = scaleXSpacingValue(rLRItem.GetTextFirstLineOffset());
+ nCurWidth -= nFI;
+ if ( pPortion->GetBulletX() > nCurWidth )
+ {
+ nCurWidth += nFI; // LI?
+ if ( pPortion->GetBulletX() > nCurWidth )
+ nCurWidth = pPortion->GetBulletX();
+ }
+ }
+ nCurWidth += scaleXSpacingValue(rLRItem.GetRight());
+ nCurWidth += CalcLineWidth( pPortion, &rLine, bIgnoreExtraSpace );
+ if ( nCurWidth > nMaxWidth )
+ {
+ nMaxWidth = nCurWidth;
+ }
+ }
+ }
+
+ nMaxWidth++; // widen it, because in CreateLines for >= is wrapped.
+ return static_cast<sal_uInt32>(nMaxWidth);
+}
+
+sal_uInt32 ImpEditEngine::CalcLineWidth( ParaPortion* pPortion, EditLine* pLine, bool bIgnoreExtraSpace )
+{
+ sal_Int32 nPara = GetEditDoc().GetPos( pPortion->GetNode() );
+
+ // #114278# Saving both layout mode and language (since I'm
+ // potentially changing both)
+ GetRefDevice()->Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE );
+
+ ImplInitLayoutMode(*GetRefDevice(), nPara, -1);
+
+ SvxAdjust eJustification = GetJustification( nPara );
+
+ // Calculation of the width without the Indents ...
+ sal_uInt32 nWidth = 0;
+ sal_Int32 nPos = pLine->GetStart();
+ for ( sal_Int32 nTP = pLine->GetStartPortion(); nTP <= pLine->GetEndPortion(); nTP++ )
+ {
+ const TextPortion& rTextPortion = pPortion->GetTextPortions()[nTP];
+ switch ( rTextPortion.GetKind() )
+ {
+ case PortionKind::FIELD:
+ case PortionKind::HYPHENATOR:
+ case PortionKind::TAB:
+ {
+ nWidth += rTextPortion.GetSize().Width();
+ }
+ break;
+ case PortionKind::TEXT:
+ {
+ if ( ( eJustification != SvxAdjust::Block ) || ( !bIgnoreExtraSpace ) )
+ {
+ nWidth += rTextPortion.GetSize().Width();
+ }
+ else
+ {
+ SvxFont aTmpFont( pPortion->GetNode()->GetCharAttribs().GetDefFont() );
+ SeekCursor( pPortion->GetNode(), nPos+1, aTmpFont );
+ aTmpFont.SetPhysFont(*GetRefDevice());
+ ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());
+ nWidth += aTmpFont.QuickGetTextSize( GetRefDevice(),
+ pPortion->GetNode()->GetString(), nPos, rTextPortion.GetLen(), nullptr ).Width();
+ }
+ }
+ break;
+ case PortionKind::LINEBREAK: break;
+ }
+ nPos = nPos + rTextPortion.GetLen();
+ }
+
+ GetRefDevice()->Pop();
+
+ return nWidth;
+}
+
+sal_uInt32 ImpEditEngine::GetTextHeightNTP() const
+{
+ assert( IsUpdateLayout() && "Should not be used for Update=FALSE: GetTextHeight" );
+ DBG_ASSERT( IsFormatted() || IsFormatting(), "GetTextHeight: Not formatted" );
+ return nCurTextHeightNTP;
+}
+
+tools::Long ImpEditEngine::Calc1ColumnTextHeight(tools::Long* pHeightNTP)
+{
+ tools::Long nHeight = 0;
+ if (pHeightNTP)
+ *pHeightNTP = 0;
+ // Pretend that we have ~infinite height to get total height
+ comphelper::ValueRestorationGuard aGuard(nCurTextHeight,
+ std::numeric_limits<tools::Long>::max());
+
+ IterateLinesAreasFunc FindLastLineBottom = [&](const LineAreaInfo& rInfo) {
+ if (rInfo.pLine)
+ {
+ // bottom coordinate does not belong to area, so no need to do +1
+ nHeight = getBottomDocOffset(rInfo.aArea);
+ if (pHeightNTP && !rInfo.rPortion.IsEmpty())
+ *pHeightNTP = nHeight;
+ }
+ return CallbackResult::Continue;
+ };
+ IterateLineAreas(FindLastLineBottom, IterFlag::none);
+ return nHeight;
+}
+
+tools::Long ImpEditEngine::CalcTextHeight(tools::Long* pHeightNTP)
+{
+ assert( IsUpdateLayout() && "Should not be used when Update=FALSE: CalcTextHeight" );
+
+ if (mnColumns <= 1)
+ return Calc1ColumnTextHeight(pHeightNTP); // All text fits into a single column - done!
+
+ // The final column height can be smaller than total height divided by number of columns (taking
+ // into account first line offset and interline spacing, that aren't considered in positioning
+ // after the wrap). The wrap should only happen after the minimal height is exceeded.
+ tools::Long nTentativeColHeight = mnMinColumnWrapHeight;
+ tools::Long nWantedIncrease = 0;
+ tools::Long nCurrentTextHeight;
+
+ // This does the necessary column balancing for the case when the text does not fit min height.
+ // When the height of column (taken from nCurTextHeight) is too small, the last column will
+ // overflow, so the resulting height of the text will exceed the set column height. Increasing
+ // the column height step by step by the minimal value that allows one of columns to accommodate
+ // one line more, we finally get to the point where all the text fits. At each iteration, the
+ // height is only increased, so it's impossible to have infinite layout loops. The found value
+ // is the global minimum.
+ //
+ // E.g., given the following four line heights:
+ // Line 1: 10;
+ // Line 2: 12;
+ // Line 3: 10;
+ // Line 4: 10;
+ // number of columns 3, and the minimal paper height of 5, the iterations would be:
+ // * Tentative column height is set to 5
+ // <ITERATION 1>
+ // * Line 1 is attempted to go to column 0. Overflow is 5 => moved to column 1.
+ // * Line 2 is attempted to go to column 1 after Line 1; overflow is 17 => moved to column 2.
+ // * Line 3 is attempted to go to column 2 after Line 2; overflow is 17, stays in max column 2.
+ // * Line 4 goes to column 2 after Line 3.
+ // * Final iteration columns are: {empty}, {Line 1}, {Line 2, Line 3, Line 4}
+ // * Total text height is max({0, 10, 32}) == 32 > Tentative column height 5 => NEXT ITERATION
+ // * Minimal height increase that allows at least one column to accommodate one more line is
+ // min({5, 17, 17}) = 5.
+ // * Tentative column height is set to 5 + 5 = 10.
+ // <ITERATION 2>
+ // * Line 1 goes to column 0, no overflow.
+ // * Line 2 is attempted to go to column 0 after Line 1; overflow is 12 => moved to column 1.
+ // * Line 3 is attempted to go to column 1 after Line 2; overflow is 12 => moved to column 2.
+ // * Line 4 is attempted to go to column 2 after Line 3; overflow is 10, stays in max column 2.
+ // * Final iteration columns are: {Line 1}, {Line 2}, {Line 3, Line 4}
+ // * Total text height is max({10, 12, 20}) == 20 > Tentative column height 10 => NEXT ITERATION
+ // * Minimal height increase that allows at least one column to accommodate one more line is
+ // min({12, 12, 10}) = 10.
+ // * Tentative column height is set to 10 + 10 == 20.
+ // <ITERATION 3>
+ // * Line 1 goes to column 0, no overflow.
+ // * Line 2 is attempted to go to column 0 after Line 1; overflow is 2 => moved to column 1.
+ // * Line 3 is attempted to go to column 1 after Line 2; overflow is 2 => moved to column 2.
+ // * Line 4 is attempted to go to column 2 after Line 3; no overflow.
+ // * Final iteration columns are: {Line 1}, {Line 2}, {Line 3, Line 4}
+ // * Total text height is max({10, 12, 20}) == 20 == Tentative column height 20 => END.
+ do
+ {
+ nTentativeColHeight += nWantedIncrease;
+ nWantedIncrease = std::numeric_limits<tools::Long>::max();
+ nCurrentTextHeight = 0;
+ if (pHeightNTP)
+ *pHeightNTP = 0;
+ auto GetHeightAndWantedIncrease = [&, minHeight = tools::Long(0), lastCol = sal_Int16(0)](
+ const LineAreaInfo& rInfo) mutable {
+ if (rInfo.pLine)
+ {
+ if (lastCol != rInfo.nColumn)
+ {
+ minHeight = std::max(nCurrentTextHeight,
+ minHeight); // total height can't be less than previous columns
+ nWantedIncrease = std::min(rInfo.nHeightNeededToNotWrap, nWantedIncrease);
+ lastCol = rInfo.nColumn;
+ }
+ // bottom coordinate does not belong to area, so no need to do +1
+ nCurrentTextHeight = std::max(getBottomDocOffset(rInfo.aArea), minHeight);
+ if (pHeightNTP)
+ {
+ if (rInfo.rPortion.IsEmpty())
+ *pHeightNTP = std::max(*pHeightNTP, minHeight);
+ else
+ *pHeightNTP = nCurrentTextHeight;
+ }
+ }
+ return CallbackResult::Continue;
+ };
+ comphelper::ValueRestorationGuard aGuard(nCurTextHeight, nTentativeColHeight);
+ IterateLineAreas(GetHeightAndWantedIncrease, IterFlag::none);
+ } while (nCurrentTextHeight > nTentativeColHeight && nWantedIncrease > 0
+ && nWantedIncrease != std::numeric_limits<tools::Long>::max());
+ return nCurrentTextHeight;
+}
+
+sal_Int32 ImpEditEngine::GetLineCount( sal_Int32 nParagraph ) const
+{
+ OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" );
+ const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
+ OSL_ENSURE( pPPortion, "Paragraph not found: GetLineCount" );
+ if ( pPPortion )
+ return pPPortion->GetLines().Count();
+
+ return -1;
+}
+
+sal_Int32 ImpEditEngine::GetLineLen( sal_Int32 nParagraph, sal_Int32 nLine ) const
+{
+ OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineLen: Out of range" );
+ const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
+ OSL_ENSURE( pPPortion, "Paragraph not found: GetLineLen" );
+ if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) )
+ {
+ const EditLine& rLine = pPPortion->GetLines()[nLine];
+ return rLine.GetLen();
+ }
+
+ return -1;
+}
+
+void ImpEditEngine::GetLineBoundaries( /*out*/sal_Int32 &rStart, /*out*/sal_Int32 &rEnd, sal_Int32 nParagraph, sal_Int32 nLine ) const
+{
+ OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" );
+ const ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
+ OSL_ENSURE( pPPortion, "Paragraph not found: GetLineBoundaries" );
+ rStart = rEnd = -1; // default values in case of error
+ if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) )
+ {
+ const EditLine& rLine = pPPortion->GetLines()[nLine];
+ rStart = rLine.GetStart();
+ rEnd = rLine.GetEnd();
+ }
+}
+
+sal_Int32 ImpEditEngine::GetLineNumberAtIndex( sal_Int32 nPara, sal_Int32 nIndex ) const
+{
+ sal_Int32 nLineNo = -1;
+ const ContentNode* pNode = GetEditDoc().GetObject( nPara );
+ OSL_ENSURE( pNode, "GetLineNumberAtIndex: invalid paragraph index" );
+ if (pNode)
+ {
+ // we explicitly allow for the index to point at the character right behind the text
+ const bool bValidIndex = /*0 <= nIndex &&*/ nIndex <= pNode->Len();
+ OSL_ENSURE( bValidIndex, "GetLineNumberAtIndex: invalid index" );
+ const sal_Int32 nLineCount = GetLineCount( nPara );
+ if (nIndex == pNode->Len())
+ nLineNo = nLineCount > 0 ? nLineCount - 1 : 0;
+ else if (bValidIndex) // nIndex < pNode->Len()
+ {
+ sal_Int32 nStart = -1, nEnd = -1;
+ for (sal_Int32 i = 0; i < nLineCount && nLineNo == -1; ++i)
+ {
+ GetLineBoundaries( nStart, nEnd, nPara, i );
+ if (nStart >= 0 && nStart <= nIndex && nEnd >= 0 && nIndex < nEnd)
+ nLineNo = i;
+ }
+ }
+ }
+ return nLineNo;
+}
+
+sal_uInt16 ImpEditEngine::GetLineHeight( sal_Int32 nParagraph, sal_Int32 nLine )
+{
+ OSL_ENSURE( 0 <= nParagraph && nParagraph < GetParaPortions().Count(), "GetLineCount: Out of range" );
+ ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
+ OSL_ENSURE( pPPortion, "Paragraph not found: GetLineHeight" );
+ if ( pPPortion && ( nLine < pPPortion->GetLines().Count() ) )
+ {
+ const EditLine& rLine = pPPortion->GetLines()[nLine];
+ return rLine.GetHeight();
+ }
+
+ return 0xFFFF;
+}
+
+sal_uInt32 ImpEditEngine::GetParaHeight( sal_Int32 nParagraph )
+{
+ sal_uInt32 nHeight = 0;
+
+ ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
+ OSL_ENSURE( pPPortion, "Paragraph not found: GetParaHeight" );
+
+ if ( pPPortion )
+ nHeight = pPPortion->GetHeight();
+
+ return nHeight;
+}
+
+void ImpEditEngine::UpdateSelections()
+{
+ // Check whether one of the selections is at a deleted node...
+ // If the node is valid, the index has yet to be examined!
+ for (EditView* pView : aEditViews)
+ {
+ EditSelection aCurSel( pView->pImpEditView->GetEditSelection() );
+ bool bChanged = false;
+ for (const std::unique_ptr<DeletedNodeInfo> & aDeletedNode : aDeletedNodes)
+ {
+ const DeletedNodeInfo& rInf = *aDeletedNode;
+ if ( ( aCurSel.Min().GetNode() == rInf.GetNode() ) ||
+ ( aCurSel.Max().GetNode() == rInf.GetNode() ) )
+ {
+ // Use ParaPortions, as now also hidden paragraphs have to be
+ // taken into account!
+ sal_Int32 nPara = rInf.GetPosition();
+ if (!GetParaPortions().SafeGetObject(nPara)) // Last paragraph
+ {
+ nPara = GetParaPortions().Count()-1;
+ }
+ assert(GetParaPortions()[nPara] && "Empty Document in UpdateSelections ?");
+ // Do not end up from a hidden paragraph:
+ sal_Int32 nCurPara = nPara;
+ sal_Int32 nLastPara = GetParaPortions().Count()-1;
+ while ( nPara <= nLastPara && !GetParaPortions()[nPara]->IsVisible() )
+ nPara++;
+ if ( nPara > nLastPara ) // then also backwards ...
+ {
+ nPara = nCurPara;
+ while ( nPara && !GetParaPortions()[nPara]->IsVisible() )
+ nPara--;
+ }
+ OSL_ENSURE( GetParaPortions()[nPara]->IsVisible(), "No visible paragraph found: UpdateSelections" );
+
+ ParaPortion* pParaPortion = GetParaPortions()[nPara];
+ EditSelection aTmpSelection( EditPaM( pParaPortion->GetNode(), 0 ) );
+ pView->pImpEditView->SetEditSelection( aTmpSelection );
+ bChanged=true;
+ break; // for loop
+ }
+ }
+ if ( !bChanged )
+ {
+ // Check Index if node shrunk.
+ if ( aCurSel.Min().GetIndex() > aCurSel.Min().GetNode()->Len() )
+ {
+ aCurSel.Min().SetIndex( aCurSel.Min().GetNode()->Len() );
+ pView->pImpEditView->SetEditSelection( aCurSel );
+ }
+ if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() )
+ {
+ aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() );
+ pView->pImpEditView->SetEditSelection( aCurSel );
+ }
+ }
+ }
+ aDeletedNodes.clear();
+}
+
+EditSelection ImpEditEngine::ConvertSelection(
+ sal_Int32 nStartPara, sal_Int32 nStartPos, sal_Int32 nEndPara, sal_Int32 nEndPos )
+{
+ EditSelection aNewSelection;
+
+ // Start...
+ ContentNode* pNode = maEditDoc.GetObject( nStartPara );
+ sal_Int32 nIndex = nStartPos;
+ if ( !pNode )
+ {
+ pNode = maEditDoc[ maEditDoc.Count()-1 ];
+ nIndex = pNode->Len();
+ }
+ else if ( nIndex > pNode->Len() )
+ nIndex = pNode->Len();
+
+ aNewSelection.Min().SetNode( pNode );
+ aNewSelection.Min().SetIndex( nIndex );
+
+ // End...
+ pNode = maEditDoc.GetObject( nEndPara );
+ nIndex = nEndPos;
+ if ( !pNode )
+ {
+ pNode = maEditDoc[ maEditDoc.Count()-1 ];
+ nIndex = pNode->Len();
+ }
+ else if ( nIndex > pNode->Len() )
+ nIndex = pNode->Len();
+
+ aNewSelection.Max().SetNode( pNode );
+ aNewSelection.Max().SetIndex( nIndex );
+
+ return aNewSelection;
+}
+
+void ImpEditEngine::SetActiveView( EditView* pView )
+{
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ // Actually, now bHasVisSel and HideSelection would be necessary !!!
+
+ if ( pView == pActiveView )
+ return;
+
+ if ( pActiveView && pActiveView->HasSelection() )
+ pActiveView->pImpEditView->DrawSelectionXOR();
+
+ pActiveView = pView;
+
+ if ( pActiveView && pActiveView->HasSelection() )
+ pActiveView->pImpEditView->DrawSelectionXOR();
+
+ // NN: Quick fix for #78668#:
+ // When editing of a cell in Calc is ended, the edit engine is not deleted,
+ // only the edit views are removed. If mpIMEInfos is still set in that case,
+ // mpIMEInfos->aPos points to an invalid selection.
+ // -> reset mpIMEInfos now
+ // (probably something like this is necessary whenever the content is modified
+ // from the outside)
+
+ if ( !pView && mpIMEInfos )
+ {
+ mpIMEInfos.reset();
+ }
+}
+
+uno::Reference< datatransfer::XTransferable > ImpEditEngine::CreateTransferable( const EditSelection& rSelection )
+{
+ EditSelection aSelection( rSelection );
+ aSelection.Adjust( GetEditDoc() );
+
+ rtl::Reference<EditDataObject> pDataObj = new EditDataObject;
+
+ pDataObj->GetString() = convertLineEnd(GetSelected(aSelection), GetSystemLineEnd()); // System specific
+
+ WriteRTF( pDataObj->GetRTFStream(), aSelection );
+ pDataObj->GetRTFStream().Seek( 0 );
+
+ WriteXML( pDataObj->GetODFStream(), aSelection );
+ pDataObj->GetODFStream().Seek( 0 );
+
+ //Dumping the ODFStream to a XML file for testing purpose
+ /*
+ std::filebuf afilebuf;
+ afilebuf.open ("gsoc17_clipboard_test.xml",std::ios::out);
+ std::ostream os(&afilebuf);
+ os.write((const char*)(pDataObj->GetODFStream().GetData()), pDataObj->GetODFStream().remainingSize());
+ afilebuf.close();
+ */
+ //dumping ends
+
+ if ( ( aSelection.Min().GetNode() == aSelection.Max().GetNode() )
+ && ( aSelection.Max().GetIndex() == (aSelection.Min().GetIndex()+1) ) )
+ {
+ const EditCharAttrib* pAttr = aSelection.Min().GetNode()->GetCharAttribs().
+ FindFeature( aSelection.Min().GetIndex() );
+ if ( pAttr &&
+ ( pAttr->GetStart() == aSelection.Min().GetIndex() ) &&
+ ( pAttr->Which() == EE_FEATURE_FIELD ) )
+ {
+ const SvxFieldItem* pField = static_cast<const SvxFieldItem*>(pAttr->GetItem());
+ const SvxFieldData* pFld = pField->GetField();
+ if ( auto pUrlField = dynamic_cast<const SvxURLField* >(pFld) )
+ {
+ // Office-Bookmark
+ pDataObj->GetURL() = pUrlField->GetURL();
+ }
+ }
+ }
+
+ return pDataObj;
+}
+
+EditSelection ImpEditEngine::PasteText( uno::Reference< datatransfer::XTransferable > const & rxDataObj, const OUString& rBaseURL, const EditPaM& rPaM, bool bUseSpecial, SotClipboardFormatId format)
+{
+ EditSelection aNewSelection( rPaM );
+
+ if ( !rxDataObj.is() )
+ return aNewSelection;
+
+ datatransfer::DataFlavor aFlavor;
+ bool bDone = false;
+
+ if ( bUseSpecial )
+ {
+ // XML
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT, aFlavor );
+ if ( rxDataObj->isDataFlavorSupported( aFlavor ) && (SotClipboardFormatId::NONE == format || SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT == format))
+ {
+ try
+ {
+ uno::Any aData = rxDataObj->getTransferData( aFlavor );
+ uno::Sequence< sal_Int8 > aSeq;
+ aData >>= aSeq;
+ {
+ SvMemoryStream aODFStream( aSeq.getArray(), aSeq.getLength(), StreamMode::READ );
+ aNewSelection = Read( aODFStream, rBaseURL, EETextFormat::Xml, rPaM );
+ }
+ bDone = true;
+ }
+ catch( const css::uno::Exception&)
+ {
+ TOOLS_WARN_EXCEPTION( "editeng", "Unable to paste EDITENGINE_ODF_TEXT_FLAT" );
+ }
+ }
+
+ if ( !bDone )
+ {
+ // RTF
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RTF, aFlavor );
+ // RICHTEXT
+ datatransfer::DataFlavor aFlavorRichtext;
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::RICHTEXT, aFlavorRichtext );
+ bool bRtfSupported = rxDataObj->isDataFlavorSupported( aFlavor );
+ bool bRichtextSupported = rxDataObj->isDataFlavorSupported( aFlavorRichtext );
+ if ( (bRtfSupported || bRichtextSupported) && (SotClipboardFormatId::NONE == format || SotClipboardFormatId::RICHTEXT == format || SotClipboardFormatId::RTF == format))
+ {
+ if(bRichtextSupported)
+ {
+ aFlavor = aFlavorRichtext;
+ }
+ try
+ {
+ uno::Any aData = rxDataObj->getTransferData( aFlavor );
+ uno::Sequence< sal_Int8 > aSeq;
+ aData >>= aSeq;
+ {
+ SvMemoryStream aRTFStream( aSeq.getArray(), aSeq.getLength(), StreamMode::READ );
+ aNewSelection = Read( aRTFStream, rBaseURL, EETextFormat::Rtf, rPaM );
+ }
+ bDone = true;
+ }
+ catch( const css::uno::Exception& )
+ {
+ }
+ }
+ }
+ if (!bDone) {
+ // HTML
+ SotExchange::GetFormatDataFlavor(SotClipboardFormatId::HTML_SIMPLE, aFlavor);
+ bool bHtmlSupported = rxDataObj->isDataFlavorSupported(aFlavor);
+ if (bHtmlSupported && (SotClipboardFormatId::NONE == format || SotClipboardFormatId::HTML_SIMPLE == format)) {
+ MSE40HTMLClipFormatObj aMSE40HTMLClipFormatObj;
+ try
+ {
+ uno::Any aData = rxDataObj->getTransferData(aFlavor);
+ uno::Sequence< sal_Int8 > aSeq;
+ aData >>= aSeq;
+ {
+ SvMemoryStream aHtmlStream(aSeq.getArray(), aSeq.getLength(), StreamMode::READ);
+ SvStream* pHtmlStream = aMSE40HTMLClipFormatObj.IsValid(aHtmlStream);
+ if (pHtmlStream != nullptr) {
+ aNewSelection = Read(*pHtmlStream, rBaseURL, EETextFormat::Html, rPaM);
+ }
+ }
+ bDone = true;
+ }
+ catch (const css::uno::Exception&)
+ {
+ }
+ }
+ }
+ }
+ if ( !bDone )
+ {
+ SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
+ if ( rxDataObj->isDataFlavorSupported( aFlavor ) )
+ {
+ try
+ {
+ uno::Any aData = rxDataObj->getTransferData( aFlavor );
+ OUString aText;
+ aData >>= aText;
+ aNewSelection = ImpInsertText( rPaM, aText );
+ }
+ catch( ... )
+ {
+ ; // #i9286# can happen, even if isDataFlavorSupported returns true...
+ }
+ }
+ }
+
+ return aNewSelection;
+}
+
+sal_Int32 ImpEditEngine::GetChar(
+ const ParaPortion* pParaPortion, const EditLine* pLine, tools::Long nXPos, bool bSmart)
+{
+ assert(pLine);
+
+ sal_Int32 nChar = -1;
+ sal_Int32 nCurIndex = pLine->GetStart();
+
+
+ // Search best matching portion with GetPortionXOffset()
+ for ( sal_Int32 i = pLine->GetStartPortion(); i <= pLine->GetEndPortion(); i++ )
+ {
+ const TextPortion& rPortion = pParaPortion->GetTextPortions()[i];
+ tools::Long nXLeft = GetPortionXOffset( pParaPortion, pLine, i );
+ tools::Long nXRight = nXLeft + rPortion.GetSize().Width();
+ if ( ( nXLeft <= nXPos ) && ( nXRight >= nXPos ) )
+ {
+ nChar = nCurIndex;
+
+ // Search within Portion...
+
+ // Don't search within special portions...
+ if ( rPortion.GetKind() != PortionKind::TEXT )
+ {
+ // ...but check on which side
+ if ( bSmart )
+ {
+ tools::Long nLeftDiff = nXPos-nXLeft;
+ tools::Long nRightDiff = nXRight-nXPos;
+ if ( nRightDiff < nLeftDiff )
+ nChar++;
+ }
+ }
+ else
+ {
+ sal_Int32 nMax = rPortion.GetLen();
+ sal_Int32 nOffset = -1;
+ sal_Int32 nTmpCurIndex = nChar - pLine->GetStart();
+
+ tools::Long nXInPortion = nXPos - nXLeft;
+ if ( rPortion.IsRightToLeft() )
+ nXInPortion = nXRight - nXPos;
+
+ // Search in Array...
+ for ( sal_Int32 x = 0; x < nMax; x++ )
+ {
+ tools::Long nTmpPosMax = pLine->GetCharPosArray()[nTmpCurIndex+x];
+ if ( nTmpPosMax > nXInPortion )
+ {
+ // Check whether this or the previous...
+ tools::Long nTmpPosMin = x ? pLine->GetCharPosArray()[nTmpCurIndex+x-1] : 0;
+ tools::Long nDiffLeft = nXInPortion - nTmpPosMin;
+ tools::Long nDiffRight = nTmpPosMax - nXInPortion;
+ OSL_ENSURE( nDiffLeft >= 0, "DiffLeft negative" );
+ OSL_ENSURE( nDiffRight >= 0, "DiffRight negative" );
+
+ if (bSmart && nDiffRight < nDiffLeft)
+ {
+ // I18N: If there are character position with the length of 0,
+ // they belong to the same character, we can not use this position as an index.
+ // Skip all 0-positions, cheaper than using XBreakIterator:
+ tools::Long nX = pLine->GetCharPosArray()[nTmpCurIndex + x];
+ while(x < nMax && pLine->GetCharPosArray()[nTmpCurIndex + x] == nX)
+ ++x;
+ }
+ nOffset = x;
+ break;
+ }
+ }
+
+ // There should not be any inaccuracies when using the
+ // CharPosArray! Maybe for kerning?
+ // 0xFFF happens for example for Outline-Font when at the very end.
+ if ( nOffset < 0 )
+ nOffset = nMax;
+
+ OSL_ENSURE( nOffset <= nMax, "nOffset > nMax" );
+
+ nChar = nChar + nOffset;
+
+ // Check if index is within a cell:
+ if ( nChar && ( nChar < pParaPortion->GetNode()->Len() ) )
+ {
+ EditPaM aPaM( pParaPortion->GetNode(), nChar+1 );
+ sal_uInt16 nScriptType = GetI18NScriptType( aPaM );
+ if ( nScriptType == i18n::ScriptType::COMPLEX )
+ {
+ uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
+ sal_Int32 nCount = 1;
+ lang::Locale aLocale = GetLocale( aPaM );
+ sal_Int32 nRight = _xBI->nextCharacters(
+ pParaPortion->GetNode()->GetString(), nChar, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount );
+ sal_Int32 nLeft = _xBI->previousCharacters(
+ pParaPortion->GetNode()->GetString(), nRight, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, nCount, nCount );
+ if ( ( nLeft != nChar ) && ( nRight != nChar ) )
+ {
+ nChar = ( std::abs( nRight - nChar ) < std::abs( nLeft - nChar ) ) ? nRight : nLeft;
+ }
+ }
+ else
+ {
+ OUString aStr(pParaPortion->GetNode()->GetString());
+ // tdf#102625: don't select middle of a pair of surrogates with mouse cursor
+ if (rtl::isSurrogate(aStr[nChar]))
+ --nChar;
+ }
+ }
+ }
+ }
+
+ nCurIndex = nCurIndex + rPortion.GetLen();
+ }
+
+ if ( nChar == -1 )
+ {
+ nChar = ( nXPos <= pLine->GetStartPosX() ) ? pLine->GetStart() : pLine->GetEnd();
+ }
+
+ return nChar;
+}
+
+Range ImpEditEngine::GetLineXPosStartEnd( const ParaPortion* pParaPortion, const EditLine* pLine ) const
+{
+ Range aLineXPosStartEnd;
+
+ sal_Int32 nPara = GetEditDoc().GetPos( pParaPortion->GetNode() );
+ if ( !IsRightToLeft( nPara ) )
+ {
+ aLineXPosStartEnd.Min() = pLine->GetStartPosX();
+ aLineXPosStartEnd.Max() = pLine->GetStartPosX() + pLine->GetTextWidth();
+ }
+ else
+ {
+ aLineXPosStartEnd.Min() = GetPaperSize().Width() - ( pLine->GetStartPosX() + pLine->GetTextWidth() );
+ aLineXPosStartEnd.Max() = GetPaperSize().Width() - pLine->GetStartPosX();
+ }
+
+
+ return aLineXPosStartEnd;
+}
+
+tools::Long ImpEditEngine::GetPortionXOffset(
+ const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nTextPortion) const
+{
+ tools::Long nX = pLine->GetStartPosX();
+
+ for ( sal_Int32 i = pLine->GetStartPortion(); i < nTextPortion; i++ )
+ {
+ const TextPortion& rPortion = pParaPortion->GetTextPortions()[i];
+ switch ( rPortion.GetKind() )
+ {
+ case PortionKind::FIELD:
+ case PortionKind::TEXT:
+ case PortionKind::HYPHENATOR:
+ case PortionKind::TAB:
+ {
+ nX += rPortion.GetSize().Width();
+ }
+ break;
+ case PortionKind::LINEBREAK: break;
+ }
+ }
+
+ sal_Int32 nPara = GetEditDoc().GetPos( pParaPortion->GetNode() );
+ bool bR2LPara = IsRightToLeft( nPara );
+
+ const TextPortion& rDestPortion = pParaPortion->GetTextPortions()[nTextPortion];
+ if ( rDestPortion.GetKind() != PortionKind::TAB )
+ {
+ if ( !bR2LPara && rDestPortion.GetRightToLeftLevel() )
+ {
+ // Portions behind must be added, visual before this portion
+ sal_Int32 nTmpPortion = nTextPortion+1;
+ while ( nTmpPortion <= pLine->GetEndPortion() )
+ {
+ const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nTmpPortion];
+ if ( rNextTextPortion.GetRightToLeftLevel() && ( rNextTextPortion.GetKind() != PortionKind::TAB ) )
+ nX += rNextTextPortion.GetSize().Width();
+ else
+ break;
+ nTmpPortion++;
+ }
+ // Portions before must be removed, visual behind this portion
+ nTmpPortion = nTextPortion;
+ while ( nTmpPortion > pLine->GetStartPortion() )
+ {
+ --nTmpPortion;
+ const TextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[nTmpPortion];
+ if ( rPrevTextPortion.GetRightToLeftLevel() && ( rPrevTextPortion.GetKind() != PortionKind::TAB ) )
+ nX -= rPrevTextPortion.GetSize().Width();
+ else
+ break;
+ }
+ }
+ else if ( bR2LPara && !rDestPortion.IsRightToLeft() )
+ {
+ // Portions behind must be removed, visual behind this portion
+ sal_Int32 nTmpPortion = nTextPortion+1;
+ while ( nTmpPortion <= pLine->GetEndPortion() )
+ {
+ const TextPortion& rNextTextPortion = pParaPortion->GetTextPortions()[nTmpPortion];
+ if ( !rNextTextPortion.IsRightToLeft() && ( rNextTextPortion.GetKind() != PortionKind::TAB ) )
+ nX += rNextTextPortion.GetSize().Width();
+ else
+ break;
+ nTmpPortion++;
+ }
+ // Portions before must be added, visual before this portion
+ nTmpPortion = nTextPortion;
+ while ( nTmpPortion > pLine->GetStartPortion() )
+ {
+ --nTmpPortion;
+ const TextPortion& rPrevTextPortion = pParaPortion->GetTextPortions()[nTmpPortion];
+ if ( !rPrevTextPortion.IsRightToLeft() && ( rPrevTextPortion.GetKind() != PortionKind::TAB ) )
+ nX -= rPrevTextPortion.GetSize().Width();
+ else
+ break;
+ }
+ }
+ }
+ if ( bR2LPara )
+ {
+ // Switch X positions...
+ OSL_ENSURE( GetTextRanger() || GetPaperSize().Width(), "GetPortionXOffset - paper size?!" );
+ OSL_ENSURE( GetTextRanger() || (nX <= GetPaperSize().Width()), "GetPortionXOffset - position out of paper size!" );
+ nX = GetPaperSize().Width() - nX;
+ nX -= rDestPortion.GetSize().Width();
+ }
+
+ return nX;
+}
+
+tools::Long ImpEditEngine::GetXPos(
+ const ParaPortion* pParaPortion, const EditLine* pLine, sal_Int32 nIndex, bool bPreferPortionStart) const
+{
+ assert(pLine);
+ OSL_ENSURE( ( nIndex >= pLine->GetStart() ) && ( nIndex <= pLine->GetEnd() ) , "GetXPos has to be called properly!" );
+
+ bool bDoPreferPortionStart = bPreferPortionStart;
+ // Assure that the portion belongs to this line:
+ if ( nIndex == pLine->GetStart() )
+ bDoPreferPortionStart = true;
+ else if ( nIndex == pLine->GetEnd() )
+ bDoPreferPortionStart = false;
+
+ sal_Int32 nTextPortionStart = 0;
+ sal_Int32 nTextPortion = pParaPortion->GetTextPortions().FindPortion( nIndex, nTextPortionStart, bDoPreferPortionStart );
+
+ OSL_ENSURE( ( nTextPortion >= pLine->GetStartPortion() ) && ( nTextPortion <= pLine->GetEndPortion() ), "GetXPos: Portion not in current line! " );
+
+ const TextPortion& rPortion = pParaPortion->GetTextPortions()[nTextPortion];
+
+ tools::Long nX = GetPortionXOffset( pParaPortion, pLine, nTextPortion );
+
+ // calc text width, portion size may include CJK/CTL spacing...
+ // But the array might not be init yet, if using text ranger this method is called within CreateLines()...
+ tools::Long nPortionTextWidth = rPortion.GetSize().Width();
+ if ( ( rPortion.GetKind() == PortionKind::TEXT ) && rPortion.GetLen() && !GetTextRanger() )
+ nPortionTextWidth = pLine->GetCharPosArray()[nTextPortionStart + rPortion.GetLen() - 1 - pLine->GetStart()];
+
+ if ( nTextPortionStart != nIndex )
+ {
+ // Search within portion...
+ if ( nIndex == ( nTextPortionStart + rPortion.GetLen() ) )
+ {
+ // End of Portion
+ if ( rPortion.GetKind() == PortionKind::TAB )
+ {
+ if ( nTextPortion+1 < pParaPortion->GetTextPortions().Count() )
+ {
+ const TextPortion& rNextPortion = pParaPortion->GetTextPortions()[nTextPortion+1];
+ if ( rNextPortion.GetKind() != PortionKind::TAB )
+ {
+ if ( !bPreferPortionStart )
+ nX = GetXPos( pParaPortion, pLine, nIndex, true );
+ else if ( !IsRightToLeft( GetEditDoc().GetPos( pParaPortion->GetNode() ) ) )
+ nX += nPortionTextWidth;
+ }
+ }
+ else if ( !IsRightToLeft( GetEditDoc().GetPos( pParaPortion->GetNode() ) ) )
+ {
+ nX += nPortionTextWidth;
+ }
+ }
+ else if ( !rPortion.IsRightToLeft() )
+ {
+ nX += nPortionTextWidth;
+ }
+ }
+ else if ( rPortion.GetKind() == PortionKind::TEXT )
+ {
+ OSL_ENSURE( nIndex != pLine->GetStart(), "Strange behavior in new GetXPos()" );
+ OSL_ENSURE( !pLine->GetCharPosArray().empty(), "svx::ImpEditEngine::GetXPos(), portion in an empty line?" );
+
+ if( !pLine->GetCharPosArray().empty() )
+ {
+ sal_Int32 nPos = nIndex - 1 - pLine->GetStart();
+ if (nPos < 0 || o3tl::make_unsigned(nPos) >= pLine->GetCharPosArray().size())
+ {
+ nPos = pLine->GetCharPosArray().size()-1;
+ OSL_FAIL("svx::ImpEditEngine::GetXPos(), index out of range!");
+ }
+
+ // old code restored see #i112788 (which leaves #i74188 unfixed again)
+ tools::Long nPosInPortion = pLine->GetCharPosArray()[nPos];
+
+ if ( !rPortion.IsRightToLeft() )
+ {
+ nX += nPosInPortion;
+ }
+ else
+ {
+ nX += nPortionTextWidth - nPosInPortion;
+ }
+
+ if ( rPortion.GetExtraInfos() && rPortion.GetExtraInfos()->bCompressed )
+ {
+ nX += rPortion.GetExtraInfos()->nPortionOffsetX;
+ if ( rPortion.GetExtraInfos()->nAsianCompressionTypes & AsianCompressionFlags::PunctuationRight )
+ {
+ AsianCompressionFlags nType = GetCharTypeForCompression( pParaPortion->GetNode()->GetChar( nIndex ) );
+ if ( nType == AsianCompressionFlags::PunctuationRight && !pLine->GetCharPosArray().empty() )
+ {
+ sal_Int32 n = nIndex - nTextPortionStart;
+ const sal_Int32* pDXArray = pLine->GetCharPosArray().data()+( nTextPortionStart-pLine->GetStart() );
+ sal_Int32 nCharWidth = ( ( (n+1) < rPortion.GetLen() ) ? pDXArray[n] : rPortion.GetSize().Width() )
+ - ( n ? pDXArray[n-1] : 0 );
+ if ( (n+1) < rPortion.GetLen() )
+ {
+ // smaller, when char behind is AsianCompressionFlags::PunctuationRight also
+ nType = GetCharTypeForCompression( pParaPortion->GetNode()->GetChar( nIndex+1 ) );
+ if ( nType == AsianCompressionFlags::PunctuationRight )
+ {
+ sal_Int32 nNextCharWidth = ( ( (n+2) < rPortion.GetLen() ) ? pDXArray[n+1] : rPortion.GetSize().Width() )
+ - pDXArray[n];
+ sal_Int32 nCompressed = nNextCharWidth/2;
+ nCompressed *= rPortion.GetExtraInfos()->nMaxCompression100thPercent;
+ nCompressed /= 10000;
+ nCharWidth += nCompressed;
+ }
+ }
+ else
+ {
+ nCharWidth *= 2; // last char pos to portion end is only compressed size
+ }
+ nX += nCharWidth/2; // 50% compression
+ }
+ }
+ }
+ }
+ }
+ }
+ else // if ( nIndex == pLine->GetStart() )
+ {
+ if ( rPortion.IsRightToLeft() )
+ {
+ nX += nPortionTextWidth;
+ }
+ }
+
+ return nX;
+}
+
+void ImpEditEngine::CalcHeight( ParaPortion* pPortion )
+{
+ pPortion->nHeight = 0;
+ pPortion->nFirstLineOffset = 0;
+
+ if ( !pPortion->IsVisible() )
+ return;
+
+ OSL_ENSURE( pPortion->GetLines().Count(), "Paragraph with no lines in ParaPortion::CalcHeight" );
+ for (sal_Int32 nLine = 0; nLine < pPortion->GetLines().Count(); ++nLine)
+ pPortion->nHeight += pPortion->GetLines()[nLine].GetHeight();
+
+ if (maStatus.IsOutliner())
+ return;
+
+ const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE );
+ const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
+ sal_Int32 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix ) ? scaleYSpacingValue(rLSItem.GetInterLineSpace()) : 0;
+
+ if ( nSBL )
+ {
+ if ( pPortion->GetLines().Count() > 1 )
+ pPortion->nHeight += ( pPortion->GetLines().Count() - 1 ) * nSBL;
+ if (maStatus.ULSpaceSummation())
+ pPortion->nHeight += nSBL;
+ }
+
+ sal_Int32 nPortion = GetParaPortions().GetPos( pPortion );
+ if ( nPortion )
+ {
+ sal_uInt16 nUpper = scaleYSpacingValue(rULItem.GetUpper());
+ pPortion->nHeight += nUpper;
+ pPortion->nFirstLineOffset = nUpper;
+ }
+
+ if ( nPortion != (GetParaPortions().Count()-1) )
+ {
+ pPortion->nHeight += scaleYSpacingValue(rULItem.GetLower()); // not in the last
+ }
+
+
+ if ( !nPortion || maStatus.ULSpaceSummation() )
+ return;
+
+ ParaPortion* pPrev = GetParaPortions().SafeGetObject( nPortion-1 );
+ if (!pPrev)
+ return;
+
+ const SvxULSpaceItem& rPrevULItem = pPrev->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE );
+ const SvxLineSpacingItem& rPrevLSItem = pPrev->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
+
+ // In relation between WinWord6/Writer3:
+ // With a proportional line spacing the paragraph spacing is
+ // also manipulated.
+ // Only Writer3: Do not add up, but minimum distance.
+
+ // check if distance by LineSpacing > Upper:
+ sal_uInt16 nExtraSpace = scaleYSpacingValue(lcl_CalcExtraSpace(rLSItem));
+ if ( nExtraSpace > pPortion->nFirstLineOffset )
+ {
+ // Paragraph becomes 'bigger':
+ pPortion->nHeight += ( nExtraSpace - pPortion->nFirstLineOffset );
+ pPortion->nFirstLineOffset = nExtraSpace;
+ }
+
+ // Determine nFirstLineOffset now f(pNode) => now f(pNode, pPrev):
+ sal_uInt16 nPrevLower = scaleYSpacingValue(rPrevULItem.GetLower());
+
+ // This PrevLower is still in the height of PrevPortion ...
+ if ( nPrevLower > pPortion->nFirstLineOffset )
+ {
+ // Paragraph is 'small':
+ pPortion->nHeight -= pPortion->nFirstLineOffset;
+ pPortion->nFirstLineOffset = 0;
+ }
+ else if ( nPrevLower )
+ {
+ // Paragraph becomes 'somewhat smaller':
+ pPortion->nHeight -= nPrevLower;
+ pPortion->nFirstLineOffset =
+ pPortion->nFirstLineOffset - nPrevLower;
+ }
+ // I find it not so good, but Writer3 feature:
+ // Check if distance by LineSpacing > Lower: this value is not
+ // stuck in the height of PrevPortion.
+ if ( pPrev->IsInvalid() )
+ return;
+
+ nExtraSpace = scaleYSpacingValue(lcl_CalcExtraSpace(rPrevLSItem));
+ if ( nExtraSpace > nPrevLower )
+ {
+ sal_uInt16 nMoreLower = nExtraSpace - nPrevLower;
+ // Paragraph becomes 'bigger', 'grows' downwards:
+ if ( nMoreLower > pPortion->nFirstLineOffset )
+ {
+ pPortion->nHeight += ( nMoreLower - pPortion->nFirstLineOffset );
+ pPortion->nFirstLineOffset = nMoreLower;
+ }
+ }
+}
+
+void ImpEditEngine::SetValidPaperSize( const Size& rNewSz )
+{
+ maPaperSize = rNewSz;
+
+ tools::Long nMinWidth = maStatus.AutoPageWidth() ? maMinAutoPaperSize.Width() : 0;
+ tools::Long nMaxWidth = maStatus.AutoPageWidth() ? maMaxAutoPaperSize.Width() : 0x7FFFFFFF;
+ tools::Long nMinHeight = maStatus.AutoPageHeight() ? maMinAutoPaperSize.Height() : 0;
+ tools::Long nMaxHeight = maStatus.AutoPageHeight() ? maMaxAutoPaperSize.Height() : 0x7FFFFFFF;
+
+ // Minimum/Maximum width:
+ if ( maPaperSize.Width() < nMinWidth )
+ maPaperSize.setWidth( nMinWidth );
+ else if ( maPaperSize.Width() > nMaxWidth )
+ maPaperSize.setWidth( nMaxWidth );
+
+ // Minimum/Maximum height:
+ if ( maPaperSize.Height() < nMinHeight )
+ maPaperSize.setHeight( nMinHeight );
+ else if ( maPaperSize.Height() > nMaxHeight )
+ maPaperSize.setHeight( nMaxHeight );
+}
+
+std::shared_ptr<SvxForbiddenCharactersTable> const & ImpEditEngine::GetForbiddenCharsTable()
+{
+ return EditDLL::Get().GetGlobalData()->GetForbiddenCharsTable();
+}
+
+void ImpEditEngine::SetForbiddenCharsTable(const std::shared_ptr<SvxForbiddenCharactersTable>& xForbiddenChars)
+{
+ EditDLL::Get().GetGlobalData()->SetForbiddenCharsTable( xForbiddenChars );
+}
+
+bool ImpEditEngine::IsVisualCursorTravelingEnabled()
+{
+ bool bVisualCursorTravaling = false;
+
+ if ( SvtCTLOptions::IsCTLFontEnabled() && ( SvtCTLOptions::GetCTLCursorMovement() == SvtCTLOptions::MOVEMENT_VISUAL ) )
+ {
+ bVisualCursorTravaling = true;
+ }
+
+ return bVisualCursorTravaling;
+
+}
+
+bool ImpEditEngine::DoVisualCursorTraveling()
+{
+ // Don't check if it's necessary, because we also need it when leaving the paragraph
+ return IsVisualCursorTravelingEnabled();
+}
+
+IMPL_LINK_NOARG(ImpEditEngine, DocModified, LinkParamNone*, void)
+{
+ aModifyHdl.Call( nullptr /*GetEditEnginePtr()*/ ); // NULL, because also used for Outliner
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/impedit3.cxx b/editeng/source/editeng/impedit3.cxx
new file mode 100644
index 0000000000..b24cc00401
--- /dev/null
+++ b/editeng/source/editeng/impedit3.cxx
@@ -0,0 +1,4910 @@
+/* -*- 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 <vcl/svapp.hxx>
+#include <vcl/metaact.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/window.hxx>
+
+#include <editeng/outliner.hxx>
+#include <editeng/tstpitem.hxx>
+#include <editeng/lspcitem.hxx>
+#include <editeng/flditem.hxx>
+#include <editeng/forbiddenruleitem.hxx>
+#include "impedit.hxx"
+#include <editeng/editeng.hxx>
+#include <editeng/editview.hxx>
+#include <editeng/escapementitem.hxx>
+#include <editeng/txtrange.hxx>
+#include <editeng/udlnitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <editeng/ulspitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <editeng/postitem.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/scriptspaceitem.hxx>
+#include <editeng/charscaleitem.hxx>
+#include <editeng/numitem.hxx>
+#include <outleeng.hxx>
+
+#include <svtools/colorcfg.hxx>
+#include <svl/ctloptions.hxx>
+#include <svl/asiancfg.hxx>
+
+#include <svx/compatflags.hxx>
+#include <sfx2/viewsh.hxx>
+
+#include <editeng/hngpnctitem.hxx>
+#include <editeng/forbiddencharacterstable.hxx>
+
+#include <unotools/configmgr.hxx>
+
+#include <math.h>
+#include <vcl/metric.hxx>
+#include <com/sun/star/i18n/BreakIterator.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <com/sun/star/i18n/InputSequenceChecker.hpp>
+#include <vcl/pdfextoutdevdata.hxx>
+#include <i18nlangtag/mslangid.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/lok.hxx>
+#include <rtl/ustrbuf.hxx>
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <o3tl/sorted_vector.hxx>
+#include <osl/diagnose.h>
+#include <comphelper/string.hxx>
+#include <cstddef>
+#include <memory>
+#include <set>
+
+#include <vcl/outdev/ScopedStates.hxx>
+
+#include <unicode/uchar.h>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::linguistic2;
+
+constexpr OUString CH_HYPH = u"-"_ustr;
+
+constexpr tools::Long WRONG_SHOW_MIN = 5;
+
+namespace {
+
+struct TabInfo
+{
+ bool bValid;
+
+ SvxTabStop aTabStop;
+ sal_Int32 nTabPortion;
+ tools::Long nStartPosX;
+ tools::Long nTabPos;
+
+ TabInfo()
+ : bValid(false)
+ , nTabPortion(0)
+ , nStartPosX(0)
+ , nTabPos(0)
+ { }
+
+};
+
+}
+
+AsianCompressionFlags GetCharTypeForCompression( sal_Unicode cChar )
+{
+ switch ( cChar )
+ {
+ case 0x3008: case 0x300A: case 0x300C: case 0x300E:
+ case 0x3010: case 0x3014: case 0x3016: case 0x3018:
+ case 0x301A: case 0x301D: case 0xFF09: case 0xFF3D:
+ case 0xFF5D:
+ {
+ return AsianCompressionFlags::PunctuationRight;
+ }
+ case 0x3001: case 0x3002: case 0x3009: case 0x300B:
+ case 0x300D: case 0x300F: case 0x3011: case 0x3015:
+ case 0x3017: case 0x3019: case 0x301B: case 0x301E:
+ case 0x301F: case 0xFF08: case 0xFF0C: case 0xFF0E:
+ case 0xFF1A: case 0xFF1B: case 0xFF3B: case 0xFF5B:
+ {
+ return AsianCompressionFlags::PunctuationLeft;
+ }
+ default:
+ {
+ return ( ( 0x3040 <= cChar ) && ( 0x3100 > cChar ) ) ? AsianCompressionFlags::Kana : AsianCompressionFlags::Normal;
+ }
+ }
+}
+
+static void lcl_DrawRedLines( OutputDevice& rOutDev,
+ tools::Long nFontHeight,
+ const Point& rPoint,
+ size_t nIndex,
+ size_t nMaxEnd,
+ std::span<const sal_Int32> pDXArray,
+ WrongList const * pWrongs,
+ Degree10 nOrientation,
+ const Point& rOrigin,
+ bool bVertical,
+ bool bIsRightToLeft )
+{
+ // But only if font is not too small...
+ tools::Long nHeight = rOutDev.LogicToPixel(Size(0, nFontHeight)).Height();
+ if (WRONG_SHOW_MIN >= nHeight)
+ return;
+
+ size_t nEnd, nStart = nIndex;
+ bool bWrong = pWrongs->NextWrong(nStart, nEnd);
+
+ while (bWrong)
+ {
+ if (nStart >= nMaxEnd)
+ break;
+
+ if (nStart < nIndex) // Corrected
+ nStart = nIndex;
+
+ if (nEnd > nMaxEnd)
+ nEnd = nMaxEnd;
+
+ Point aPoint1(rPoint);
+ if (bVertical)
+ {
+ // VCL doesn't know that the text is vertical, and is manipulating
+ // the positions a little bit in y direction...
+ tools::Long nOnePixel = rOutDev.PixelToLogic(Size(0, 1)).Height();
+ tools::Long nCorrect = 2 * nOnePixel;
+ aPoint1.AdjustY(-nCorrect);
+ aPoint1.AdjustX(-nCorrect);
+ }
+ if (nStart > nIndex)
+ {
+ if (!bVertical)
+ {
+ // since for RTL portions rPoint is on the visual right end of the portion
+ // (i.e. at the start of the first RTL char) we need to subtract the offset
+ // for RTL portions...
+ aPoint1.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nStart - nIndex - 1]);
+ }
+ else
+ aPoint1.AdjustY(pDXArray[nStart - nIndex - 1]);
+ }
+ Point aPoint2(rPoint);
+ assert(nEnd > nIndex && "RedLine: aPnt2?");
+ if (!bVertical)
+ {
+ // since for RTL portions rPoint is on the visual right end of the portion
+ // (i.e. at the start of the first RTL char) we need to subtract the offset
+ // for RTL portions...
+ aPoint2.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nEnd - nIndex - 1]);
+ }
+ else
+ {
+ aPoint2.AdjustY(pDXArray[nEnd - nIndex - 1]);
+ }
+
+ if (nOrientation)
+ {
+ rOrigin.RotateAround(aPoint1, nOrientation);
+ rOrigin.RotateAround(aPoint2, nOrientation);
+ }
+
+ {
+ vcl::ScopedAntialiasing a(rOutDev, true);
+ rOutDev.DrawWaveLine(aPoint1, aPoint2);
+ }
+
+ nStart = nEnd + 1;
+ if (nEnd < nMaxEnd)
+ bWrong = pWrongs->NextWrong(nStart, nEnd);
+ else
+ bWrong = false;
+ }
+}
+
+// For Kashidas from sw/source/core/text/porlay.cxx
+
+#define IS_JOINING_GROUP(c, g) ( u_getIntPropertyValue( (c), UCHAR_JOINING_GROUP ) == U_JG_##g )
+#define isAinChar(c) IS_JOINING_GROUP((c), AIN)
+#define isAlefChar(c) IS_JOINING_GROUP((c), ALEF)
+#define isDalChar(c) IS_JOINING_GROUP((c), DAL)
+#define isFehChar(c) (IS_JOINING_GROUP((c), FEH) || IS_JOINING_GROUP((c), AFRICAN_FEH))
+#define isGafChar(c) IS_JOINING_GROUP((c), GAF)
+#define isHehChar(c) IS_JOINING_GROUP((c), HEH)
+#define isKafChar(c) IS_JOINING_GROUP((c), KAF)
+#define isLamChar(c) IS_JOINING_GROUP((c), LAM)
+#define isQafChar(c) (IS_JOINING_GROUP((c), QAF) || IS_JOINING_GROUP((c), AFRICAN_QAF))
+#define isRehChar(c) IS_JOINING_GROUP((c), REH)
+#define isTahChar(c) IS_JOINING_GROUP((c), TAH)
+#define isTehMarbutaChar(c) IS_JOINING_GROUP((c), TEH_MARBUTA)
+#define isWawChar(c) IS_JOINING_GROUP((c), WAW)
+#define isSeenOrSadChar(c) (IS_JOINING_GROUP((c), SAD) || IS_JOINING_GROUP((c), SEEN))
+
+// Beh and characters that behave like Beh in medial form.
+static bool isBehChar(sal_Unicode cCh)
+{
+ bool bRet = false;
+ switch (u_getIntPropertyValue(cCh, UCHAR_JOINING_GROUP))
+ {
+ case U_JG_BEH:
+ case U_JG_NOON:
+ case U_JG_AFRICAN_NOON:
+ case U_JG_NYA:
+ case U_JG_YEH:
+ case U_JG_FARSI_YEH:
+ case U_JG_BURUSHASKI_YEH_BARREE:
+ bRet = true;
+ break;
+ default:
+ bRet = false;
+ break;
+ }
+
+ return bRet;
+}
+
+// Yeh and characters that behave like Yeh in final form.
+static bool isYehChar(sal_Unicode cCh)
+{
+ bool bRet = false;
+ switch (u_getIntPropertyValue(cCh, UCHAR_JOINING_GROUP))
+ {
+ case U_JG_YEH:
+ case U_JG_FARSI_YEH:
+ case U_JG_YEH_BARREE:
+ case U_JG_BURUSHASKI_YEH_BARREE:
+ case U_JG_YEH_WITH_TAIL:
+ bRet = true;
+ break;
+ default:
+ bRet = false;
+ break;
+ }
+
+ return bRet;
+}
+
+static bool isTransparentChar ( sal_Unicode cCh )
+{
+ return u_getIntPropertyValue( cCh, UCHAR_JOINING_TYPE ) == U_JT_TRANSPARENT;
+}
+
+static bool lcl_IsLigature( sal_Unicode cCh, sal_Unicode cNextCh )
+{
+ // Lam + Alef
+ return ( isLamChar ( cCh ) && isAlefChar ( cNextCh ));
+}
+
+static bool lcl_ConnectToPrev( sal_Unicode cCh, sal_Unicode cPrevCh )
+{
+ const int32_t nJoiningType = u_getIntPropertyValue( cPrevCh, UCHAR_JOINING_TYPE );
+ bool bRet = nJoiningType != U_JT_RIGHT_JOINING && nJoiningType != U_JT_NON_JOINING;
+
+ // check for ligatures cPrevChar + cChar
+ if ( bRet )
+ bRet = ! lcl_IsLigature( cPrevCh, cCh );
+
+ return bRet;
+}
+
+
+
+void ImpEditEngine::UpdateViews( EditView* pCurView )
+{
+ if ( !IsUpdateLayout() || IsFormatting() || aInvalidRect.IsEmpty() )
+ return;
+
+ DBG_ASSERT( IsFormatted(), "UpdateViews: Doc not formatted!" );
+
+ for (EditView* pView : aEditViews)
+ {
+ pView->HideCursor();
+
+ tools::Rectangle aClipRect( aInvalidRect );
+ tools::Rectangle aVisArea( pView->GetVisArea() );
+ aClipRect.Intersection( aVisArea );
+
+ if ( !aClipRect.IsEmpty() )
+ {
+ // convert to window coordinates...
+ aClipRect = pView->pImpEditView->GetWindowPos( aClipRect );
+
+ // moved to one executing method to allow finer control
+ pView->InvalidateWindow(aClipRect);
+
+ pView->InvalidateOtherViewWindows( aClipRect );
+ }
+ }
+
+ if ( pCurView )
+ {
+ bool bGotoCursor = pCurView->pImpEditView->DoAutoScroll();
+ pCurView->ShowCursor( bGotoCursor );
+ }
+
+ aInvalidRect = tools::Rectangle();
+ CallStatusHdl();
+}
+
+IMPL_LINK_NOARG(ImpEditEngine, OnlineSpellHdl, Timer *, void)
+{
+ if ( !Application::AnyInput( VclInputFlags::KEYBOARD ) && IsUpdateLayout() && IsFormatted() )
+ DoOnlineSpelling();
+ else
+ aOnlineSpellTimer.Start();
+}
+
+IMPL_LINK_NOARG(ImpEditEngine, IdleFormatHdl, Timer *, void)
+{
+ aIdleFormatter.ResetRestarts();
+
+ // #i97146# check if that view is still available
+ // else probably the idle format timer fired while we're already
+ // downing
+ EditView* pView = aIdleFormatter.GetView();
+ for (EditView* aEditView : aEditViews)
+ {
+ if( aEditView == pView )
+ {
+ FormatAndLayout( pView );
+ break;
+ }
+ }
+}
+
+void ImpEditEngine::CheckIdleFormatter()
+{
+ aIdleFormatter.ForceTimeout();
+ // If not idle, but still not formatted:
+ if ( !IsFormatted() )
+ FormatDoc();
+}
+
+bool ImpEditEngine::IsPageOverflow( ) const
+{
+ return mbNeedsChainingHandling;
+}
+
+
+void ImpEditEngine::FormatFullDoc()
+{
+ for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ )
+ GetParaPortions()[nPortion]->MarkSelectionInvalid( 0 );
+ FormatDoc();
+}
+
+void ImpEditEngine::FormatDoc()
+{
+ if (!IsUpdateLayout() || IsFormatting())
+ return;
+
+ mbIsFormatting = true;
+
+ // Then I can also start the spell-timer...
+ if ( GetStatus().DoOnlineSpelling() )
+ StartOnlineSpellTimer();
+
+ tools::Long nY = 0;
+ bool bGrow = false;
+
+ // Here already, so that not always in CreateLines...
+ bool bMapChanged = ImpCheckRefMapMode();
+ sal_Int32 nParaCount = GetParaPortions().Count();
+ o3tl::sorted_vector<sal_Int32> aRepaintParas;
+ aRepaintParas.reserve(nParaCount);
+
+ for ( sal_Int32 nPara = 0; nPara < nParaCount; nPara++ )
+ {
+ ParaPortion* pParaPortion = GetParaPortions()[nPara];
+ if ( pParaPortion->MustRepaint() || ( pParaPortion->IsInvalid() && pParaPortion->IsVisible() ) )
+ {
+ // No formatting should be necessary for MustRepaint()!
+ if ( !pParaPortion->IsInvalid() || CreateLines( nPara, nY ) )
+ {
+ if ( !bGrow && GetTextRanger() )
+ {
+ // For a change in height all below must be reformatted...
+ for ( sal_Int32 n = nPara+1; n < GetParaPortions().Count(); n++ )
+ {
+ ParaPortion* pPP = GetParaPortions()[n];
+ pPP->MarkSelectionInvalid( 0 );
+ pPP->GetLines().Reset();
+ }
+ }
+ bGrow = true;
+ if ( IsCallParaInsertedOrDeleted() )
+ {
+ GetEditEnginePtr()->ParagraphHeightChanged( nPara );
+
+ for (EditView* pView : aEditViews)
+ {
+ ImpEditView* pImpView = pView->pImpEditView.get();
+ pImpView->ScrollStateChange();
+ }
+
+ }
+ pParaPortion->SetMustRepaint( false );
+ }
+
+ aRepaintParas.insert(nPara);
+ }
+ nY += pParaPortion->GetHeight();
+ }
+
+ aInvalidRect = tools::Rectangle(); // make empty
+
+ // One can also get into the formatting through UpdateMode ON=>OFF=>ON...
+ // enable optimization first after Vobis delivery...
+ {
+ tools::Long nNewHeightNTP;
+ tools::Long nNewHeight = CalcTextHeight(&nNewHeightNTP);
+ tools::Long nDiff = nNewHeight - nCurTextHeight;
+ if ( nDiff )
+ {
+ aInvalidRect.Union(tools::Rectangle::Normalize(
+ { 0, nNewHeight }, { getWidthDirectionAware(maPaperSize), nCurTextHeight }));
+ maStatus.GetStatusWord() |= !IsEffectivelyVertical() ? EditStatusFlags::TextHeightChanged : EditStatusFlags::TEXTWIDTHCHANGED;
+ }
+
+ nCurTextHeight = nNewHeight;
+ nCurTextHeightNTP = nNewHeightNTP;
+
+ if ( maStatus.AutoPageSize() )
+ CheckAutoPageSize();
+ else if ( nDiff )
+ {
+ for (EditView* pView : aEditViews)
+ {
+ ImpEditView* pImpView = pView->pImpEditView.get();
+ if ( pImpView->DoAutoHeight() )
+ {
+ Size aSz( pImpView->GetOutputArea().GetWidth(), nCurTextHeight );
+ if ( aSz.Height() > maMaxAutoPaperSize.Height() )
+ aSz.setHeight( maMaxAutoPaperSize.Height() );
+ else if ( aSz.Height() < maMinAutoPaperSize.Height() )
+ aSz.setHeight( maMinAutoPaperSize.Height() );
+ pImpView->ResetOutputArea( tools::Rectangle(
+ pImpView->GetOutputArea().TopLeft(), aSz ) );
+ }
+ }
+ }
+
+ if (!aRepaintParas.empty())
+ {
+ auto CombineRepaintParasAreas = [&](const LineAreaInfo& rInfo) {
+ if (aRepaintParas.count(rInfo.nPortion))
+ aInvalidRect.Union(rInfo.aArea);
+ return CallbackResult::Continue;
+ };
+ IterateLineAreas(CombineRepaintParasAreas, IterFlag::inclILS);
+ }
+ }
+
+ mbIsFormatting = false;
+ mbFormatted = true;
+
+ if ( bMapChanged )
+ GetRefDevice()->Pop();
+
+ CallStatusHdl(); // If Modified...
+}
+
+bool ImpEditEngine::ImpCheckRefMapMode()
+{
+ bool bChange = false;
+
+ if ( maStatus.DoFormat100() )
+ {
+ MapMode aMapMode( GetRefDevice()->GetMapMode() );
+ if ( aMapMode.GetScaleX().GetNumerator() != aMapMode.GetScaleX().GetDenominator() )
+ bChange = true;
+ else if ( aMapMode.GetScaleY().GetNumerator() != aMapMode.GetScaleY().GetDenominator() )
+ bChange = true;
+
+ if ( bChange )
+ {
+ Fraction Scale1( 1, 1 );
+ aMapMode.SetScaleX( Scale1 );
+ aMapMode.SetScaleY( Scale1 );
+ GetRefDevice()->Push();
+ GetRefDevice()->SetMapMode( aMapMode );
+ }
+ }
+
+ return bChange;
+}
+
+void ImpEditEngine::CheckAutoPageSize()
+{
+ Size aPrevPaperSize( GetPaperSize() );
+ if ( GetStatus().AutoPageWidth() )
+ maPaperSize.setWidth( !IsEffectivelyVertical() ? CalcTextWidth( true ) : GetTextHeight() );
+ if ( GetStatus().AutoPageHeight() )
+ maPaperSize.setHeight( !IsEffectivelyVertical() ? GetTextHeight() : CalcTextWidth( true ) );
+
+ SetValidPaperSize( maPaperSize ); // consider Min, Max
+
+ if ( maPaperSize == aPrevPaperSize )
+ return;
+
+ if ( ( !IsEffectivelyVertical() && ( maPaperSize.Width() != aPrevPaperSize.Width() ) )
+ || ( IsEffectivelyVertical() && ( maPaperSize.Height() != aPrevPaperSize.Height() ) ) )
+ {
+ // If ahead is centered / right or tabs...
+ maStatus.GetStatusWord() |= !IsEffectivelyVertical() ? EditStatusFlags::TEXTWIDTHCHANGED : EditStatusFlags::TextHeightChanged;
+ for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ )
+ {
+ // Only paragraphs which are not aligned to the left need to be
+ // reformatted, the height can not be changed here anymore.
+ ParaPortion* pParaPortion = GetParaPortions()[nPara];
+ SvxAdjust eJustification = GetJustification( nPara );
+ if ( eJustification != SvxAdjust::Left )
+ {
+ pParaPortion->MarkSelectionInvalid( 0 );
+ CreateLines( nPara, 0 ); // 0: For AutoPageSize no TextRange!
+ }
+ }
+ }
+
+ Size aInvSize = maPaperSize;
+ if ( maPaperSize.Width() < aPrevPaperSize.Width() )
+ aInvSize.setWidth( aPrevPaperSize.Width() );
+ if ( maPaperSize.Height() < aPrevPaperSize.Height() )
+ aInvSize.setHeight( aPrevPaperSize.Height() );
+
+ Size aSz( aInvSize );
+ if ( IsEffectivelyVertical() )
+ {
+ aSz.setWidth( aInvSize.Height() );
+ aSz.setHeight( aInvSize.Width() );
+ }
+ aInvalidRect = tools::Rectangle( Point(), aSz );
+
+
+ for (EditView* pView : aEditViews)
+ {
+ pView->pImpEditView->RecalcOutputArea();
+ }
+}
+
+void ImpEditEngine::CheckPageOverflow()
+{
+ SAL_INFO("editeng.chaining", "[CONTROL_STATUS] AutoPageSize is " << (( maStatus.GetControlWord() & EEControlBits::AUTOPAGESIZE ) ? "ON" : "OFF") );
+
+ tools::Long nBoxHeight = GetMaxAutoPaperSize().Height();
+ SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current MaxAutoPaperHeight is " << nBoxHeight);
+
+ tools::Long nTxtHeight = CalcTextHeight(nullptr);
+ SAL_INFO("editeng.chaining", "[OVERFLOW-CHECK] Current Text Height is " << nTxtHeight);
+
+ sal_uInt32 nParaCount = GetParaPortions().Count();
+ sal_uInt32 nFirstLineCount = GetLineCount(0);
+ bool bOnlyOneEmptyPara = (nParaCount == 1) &&
+ (nFirstLineCount == 1) &&
+ (GetLineLen(0,0) == 0);
+
+ if (nTxtHeight > nBoxHeight && !bOnlyOneEmptyPara)
+ {
+ // which paragraph is the first to cause higher size of the box?
+ ImplUpdateOverflowingParaNum( nBoxHeight); // XXX: currently only for horizontal text
+ //maStatus.SetPageOverflow(true);
+ mbNeedsChainingHandling = true;
+ } else
+ {
+ // No overflow if within box boundaries
+ //maStatus.SetPageOverflow(false);
+ mbNeedsChainingHandling = false;
+ }
+
+}
+
+static sal_Int32 ImplCalculateFontIndependentLineSpacing( const sal_Int32 nFontHeight )
+{
+ constexpr const double f120Percent = 12.0 / 10.0;
+ return basegfx::fround(nFontHeight * f120Percent); // + 20%
+}
+
+tools::Long ImpEditEngine::GetColumnWidth(const Size& rPaperSize) const
+{
+ assert(mnColumns >= 1);
+ tools::Long nWidth = IsEffectivelyVertical() ? rPaperSize.Height() : rPaperSize.Width();
+ return (nWidth - mnColumnSpacing * (mnColumns - 1)) / mnColumns;
+}
+
+bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY )
+{
+ ParaPortion* pParaPortion = GetParaPortions()[nPara];
+
+ // sal_Bool: Changes in the height of paragraph Yes / No - sal_True/sal_False
+ assert( pParaPortion->GetNode() && "Portion without Node in CreateLines" );
+ DBG_ASSERT( pParaPortion->IsVisible(), "Invisible paragraphs not formatted!" );
+ DBG_ASSERT( pParaPortion->IsInvalid(), "CreateLines: Portion not invalid!" );
+
+ bool bProcessingEmptyLine = ( pParaPortion->GetNode()->Len() == 0 );
+ bool bEmptyNodeWithPolygon = ( pParaPortion->GetNode()->Len() == 0 ) && GetTextRanger();
+
+
+ // Fast special treatment for empty paragraphs...
+
+ if ( ( pParaPortion->GetNode()->Len() == 0 ) && !GetTextRanger() )
+ {
+ // fast special treatment...
+ if ( pParaPortion->GetTextPortions().Count() )
+ pParaPortion->GetTextPortions().Reset();
+ if ( pParaPortion->GetLines().Count() )
+ pParaPortion->GetLines().Reset();
+ CreateAndInsertEmptyLine( pParaPortion );
+ return FinishCreateLines( pParaPortion );
+ }
+
+ sal_Int64 nCurrentPosY = nStartPosY;
+ // If we're allowed to skip parts outside and this cannot possibly fit in the given height,
+ // bail out to avoid possibly formatting a lot of text that will not be used. For the first
+ // paragraph still format at least a bit.
+ if( mbSkipOutsideFormat && nPara != 0
+ && !maStatus.AutoPageHeight() && maPaperSize.Height() < nCurrentPosY )
+ {
+ return false;
+ }
+
+ // Initialization...
+
+ // Always format for 100%:
+ bool bMapChanged = ImpCheckRefMapMode();
+
+ if ( pParaPortion->GetLines().Count() == 0 )
+ {
+ EditLine* pL = new EditLine;
+ pParaPortion->GetLines().Append(pL);
+ }
+
+
+ // Get Paragraph attributes...
+
+ ContentNode* const pNode = pParaPortion->GetNode();
+
+ bool bRightToLeftPara = IsRightToLeft( nPara );
+
+ SvxAdjust eJustification = GetJustification( nPara );
+ bool bHyphenatePara = pNode->GetContentAttribs().GetItem( EE_PARA_HYPHENATE ).GetValue();
+ sal_Int32 nSpaceBefore = 0;
+ sal_Int32 nMinLabelWidth = 0;
+ sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pNode, &nSpaceBefore, &nMinLabelWidth );
+ const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pNode );
+ const SvxLineSpacingItem& rLSItem = pNode->GetContentAttribs().GetItem( EE_PARA_SBL );
+ const bool bScriptSpace = pNode->GetContentAttribs().GetItem( EE_PARA_ASIANCJKSPACING ).GetValue();
+
+ const short nInvalidDiff = pParaPortion->GetInvalidDiff();
+ const sal_Int32 nInvalidStart = pParaPortion->GetInvalidPosStart();
+ const sal_Int32 nInvalidEnd = nInvalidStart + std::abs( nInvalidDiff );
+
+ bool bQuickFormat = false;
+ if ( !bEmptyNodeWithPolygon && !HasScriptType( nPara, i18n::ScriptType::COMPLEX ) )
+ {
+ if ( ( pParaPortion->IsSimpleInvalid() ) && ( nInvalidDiff > 0 ) &&
+ ( pNode->GetString().indexOf( CH_FEATURE, nInvalidStart ) > nInvalidEnd ) )
+ {
+ bQuickFormat = true;
+ }
+ else if ( ( pParaPortion->IsSimpleInvalid() ) && ( nInvalidDiff < 0 ) )
+ {
+ // check if delete over the portion boundaries was done...
+ sal_Int32 nStart = nInvalidStart; // DOUBLE !!!!!!!!!!!!!!!
+ sal_Int32 nEnd = nStart - nInvalidDiff; // negative
+ bQuickFormat = true;
+ sal_Int32 nPos = 0;
+ sal_Int32 nPortions = pParaPortion->GetTextPortions().Count();
+ for ( sal_Int32 nTP = 0; nTP < nPortions; nTP++ )
+ {
+ // There must be no start / end in the deleted area.
+ const TextPortion& rTP = pParaPortion->GetTextPortions()[ nTP ];
+ nPos = nPos + rTP.GetLen();
+ if ( ( nPos > nStart ) && ( nPos < nEnd ) )
+ {
+ bQuickFormat = false;
+ break;
+ }
+ }
+ }
+ }
+
+ // Saving both layout mode and language (since I'm potentially changing both)
+ GetRefDevice()->Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE );
+
+ ImplInitLayoutMode(*GetRefDevice(), nPara, -1);
+
+ sal_Int32 nRealInvalidStart = nInvalidStart;
+
+ if ( bEmptyNodeWithPolygon )
+ {
+ TextPortion* pDummyPortion = new TextPortion( 0 );
+ pParaPortion->GetTextPortions().Reset();
+ pParaPortion->GetTextPortions().Append(pDummyPortion);
+ }
+ else if ( bQuickFormat )
+ {
+ // faster Method:
+ RecalcTextPortion( pParaPortion, nInvalidStart, nInvalidDiff );
+ }
+ else // nRealInvalidStart can be before InvalidStart, since Portions were deleted...
+ {
+ CreateTextPortions( pParaPortion, nRealInvalidStart );
+ }
+
+
+ // Search for line with InvalidPos, start one line before
+ // Flag the line => do not remove it !
+
+
+ sal_Int32 nLine = pParaPortion->GetLines().Count()-1;
+ for ( sal_Int32 nL = 0; nL <= nLine; nL++ )
+ {
+ EditLine& rLine = pParaPortion->GetLines()[nL];
+ if ( rLine.GetEnd() > nRealInvalidStart ) // not nInvalidStart!
+ {
+ nLine = nL;
+ break;
+ }
+ rLine.SetValid();
+ }
+ // Begin one line before...
+ // If it is typed at the end, the line in front cannot change.
+ if ( nLine && ( !pParaPortion->IsSimpleInvalid() || ( nInvalidEnd < pNode->Len() ) || ( nInvalidDiff <= 0 ) ) )
+ nLine--;
+
+ EditLine* pLine = &pParaPortion->GetLines()[nLine];
+
+ static const tools::Rectangle aZeroArea { Point(), Point() };
+ tools::Rectangle aBulletArea( aZeroArea );
+ if ( !nLine )
+ {
+ aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos( pParaPortion ) );
+ if ( !aBulletArea.IsWidthEmpty() && aBulletArea.Right() > 0 )
+ pParaPortion->SetBulletX(sal_Int32(scaleXSpacingValue(aBulletArea.Right())));
+ else
+ pParaPortion->SetBulletX( 0 ); // if Bullet is set incorrectly
+ }
+
+
+ // Reformat all lines from here...
+
+ sal_Int32 nDelFromLine = -1;
+ bool bLineBreak = false;
+
+ sal_Int32 nIndex = pLine->GetStart();
+ EditLine aSaveLine( *pLine );
+ SvxFont aTmpFont( pNode->GetCharAttribs().GetDefFont() );
+
+ KernArray aCharPositionArray;
+
+ bool bSameLineAgain = false; // For TextRanger, if the height changes.
+ TabInfo aCurrentTab;
+
+ bool bForceOneRun = bEmptyNodeWithPolygon;
+ bool bCompressedChars = false;
+
+ while ( ( nIndex < pNode->Len() ) || bForceOneRun )
+ {
+ assert(pLine);
+
+ bForceOneRun = false;
+
+ bool bEOL = false;
+ bool bEOC = false;
+ sal_Int32 nPortionStart = 0;
+ sal_Int32 nPortionEnd = 0;
+
+ tools::Long nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + nSpaceBeforeAndMinLabelWidth);
+ if ( nIndex == 0 )
+ {
+ tools::Long nFI = scaleXSpacingValue(rLRItem.GetTextFirstLineOffset());
+ nStartX += nFI;
+
+ if ( !nLine && ( pParaPortion->GetBulletX() > nStartX ) )
+ {
+ nStartX = pParaPortion->GetBulletX();
+ }
+ }
+
+ const bool bAutoSize = IsEffectivelyVertical() ? maStatus.AutoPageHeight() : maStatus.AutoPageWidth();
+ tools::Long nMaxLineWidth = GetColumnWidth(bAutoSize ? maMaxAutoPaperSize : maPaperSize);
+
+ nMaxLineWidth -= scaleXSpacingValue(rLRItem.GetRight());
+ nMaxLineWidth -= nStartX;
+
+ // If PaperSize == long_max, one cannot take away any negative
+ // first line indent. (Overflow)
+ if ( ( nMaxLineWidth < 0 ) && ( nStartX < 0 ) )
+ nMaxLineWidth = GetColumnWidth(maPaperSize) - scaleXSpacingValue(rLRItem.GetRight());
+
+ // If still less than 0, it may be just the right edge.
+ if ( nMaxLineWidth <= 0 )
+ nMaxLineWidth = 1;
+
+ // Problem:
+ // Since formatting starts a line _before_ the invalid position,
+ // the positions unfortunately have to be redefined...
+ // Solution:
+ // The line before can only become longer, not smaller
+ // =>...
+ pLine->GetCharPosArray().clear();
+
+ sal_Int32 nTmpPos = nIndex;
+ sal_Int32 nTmpPortion = pLine->GetStartPortion();
+ tools::Long nTmpWidth = 0;
+ tools::Long nXWidth = nMaxLineWidth;
+
+ std::deque<tools::Long>* pTextRanges = nullptr;
+ tools::Long nTextExtraYOffset = 0;
+ tools::Long nTextXOffset = 0;
+ tools::Long nTextLineHeight = 0;
+ if ( GetTextRanger() )
+ {
+ GetTextRanger()->SetVertical( IsEffectivelyVertical() );
+
+ tools::Long nTextY = nStartPosY + GetEditCursor( pParaPortion, pLine, pLine->GetStart(), GetCursorFlags::NONE ).Top();
+ if ( !bSameLineAgain )
+ {
+ SeekCursor( pNode, nTmpPos+1, aTmpFont );
+ aTmpFont.SetPhysFont(*GetRefDevice());
+ ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());
+
+ if ( IsFixedCellHeight() )
+ nTextLineHeight = ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() );
+ else
+ nTextLineHeight = aTmpFont.GetPhysTxtSize( GetRefDevice() ).Height();
+ // Metrics can be greater
+ FormatterFontMetric aTempFormatterMetrics;
+ RecalcFormatterFontMetrics( aTempFormatterMetrics, aTmpFont );
+ sal_uInt16 nLineHeight = aTempFormatterMetrics.GetHeight();
+ if ( nLineHeight > nTextLineHeight )
+ nTextLineHeight = nLineHeight;
+ }
+ else
+ nTextLineHeight = pLine->GetHeight();
+
+ nXWidth = 0;
+ while ( !nXWidth )
+ {
+ tools::Long nYOff = nTextY + nTextExtraYOffset;
+ tools::Long nYDiff = nTextLineHeight;
+ if ( IsEffectivelyVertical() )
+ {
+ tools::Long nMaxPolygonX = GetTextRanger()->GetBoundRect().Right();
+ nYOff = nMaxPolygonX-nYOff;
+ nYDiff = -nTextLineHeight;
+ }
+ pTextRanges = GetTextRanger()->GetTextRanges( Range( nYOff, nYOff + nYDiff ) );
+ assert( pTextRanges && "GetTextRanges?!" );
+ tools::Long nMaxRangeWidth = 0;
+ // Use the widest range...
+ // The widest range could be a bit confusing, so normally it
+ // is the first one. Best with gaps.
+ assert(pTextRanges->size() % 2 == 0 && "textranges are always in pairs");
+ if (!pTextRanges->empty())
+ {
+ tools::Long nA = pTextRanges->at(0);
+ tools::Long nB = pTextRanges->at(1);
+ DBG_ASSERT( nA <= nB, "TextRange distorted?" );
+ tools::Long nW = nB - nA;
+ if ( nW > nMaxRangeWidth )
+ {
+ nMaxRangeWidth = nW;
+ nTextXOffset = nA;
+ }
+ }
+ nXWidth = nMaxRangeWidth;
+ if ( nXWidth )
+ nMaxLineWidth = nXWidth - nStartX - scaleXSpacingValue(rLRItem.GetRight());
+ else
+ {
+ // Try further down in the polygon.
+ // Below the polygon use the Paper Width.
+ nTextExtraYOffset += std::max( static_cast<tools::Long>(nTextLineHeight / 10), tools::Long(1) );
+ if ( ( nTextY + nTextExtraYOffset ) > GetTextRanger()->GetBoundRect().Bottom() )
+ {
+ nXWidth = getWidthDirectionAware(GetPaperSize());
+ if ( !nXWidth ) // AutoPaperSize
+ nXWidth = 0x7FFFFFFF;
+ }
+ }
+ }
+ }
+
+ // search for Portion that no longer fits in line...
+ TextPortion* pPortion = nullptr;
+ sal_Int32 nPortionLen = 0;
+ bool bContinueLastPortion = false;
+ bool bBrokenLine = false;
+ bLineBreak = false;
+ const EditCharAttrib* pNextFeature = pNode->GetCharAttribs().FindFeature( pLine->GetStart() );
+ while ( ( nTmpWidth < nXWidth ) && !bEOL )
+ {
+ const sal_Int32 nTextPortions = pParaPortion->GetTextPortions().Count();
+ assert(nTextPortions > 0);
+ bContinueLastPortion = (nTmpPortion >= nTextPortions);
+ if (bContinueLastPortion)
+ {
+ if (nTmpPos >= pNode->Len())
+ break; // while
+
+ // Continue with remainder. This only to have *some* valid
+ // X-values and not endlessly create new lines until DOOM...
+ // Happened in the scenario of tdf#104152 where inserting a
+ // paragraph lead to a11y attempting to format the doc to
+ // obtain content when notified.
+ nTmpPortion = nTextPortions - 1;
+ SAL_WARN("editeng","ImpEditEngine::CreateLines - continuation of a broken portion");
+ }
+
+ nPortionStart = nTmpPos;
+ pPortion = &pParaPortion->GetTextPortions()[nTmpPortion];
+ if ( !bContinueLastPortion && pPortion->GetKind() == PortionKind::HYPHENATOR )
+ {
+ // Throw away a Portion, if necessary correct the one before,
+ // if the Hyph portion has swallowed a character...
+ sal_Int32 nTmpLen = pPortion->GetLen();
+ pParaPortion->GetTextPortions().Remove( nTmpPortion );
+ if (nTmpPortion && nTmpLen)
+ {
+ nTmpPortion--;
+ TextPortion& rPrev = pParaPortion->GetTextPortions()[nTmpPortion];
+ DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" );
+ nTmpWidth -= rPrev.GetSize().Width();
+ nTmpPos = nTmpPos - rPrev.GetLen();
+ rPrev.SetLen(rPrev.GetLen() + nTmpLen);
+ rPrev.setWidth(-1);
+ }
+
+ assert( nTmpPortion < pParaPortion->GetTextPortions().Count() && "No more Portions left!" );
+ pPortion = &pParaPortion->GetTextPortions()[nTmpPortion];
+ }
+
+ if (bContinueLastPortion)
+ {
+ // Note that this may point behind the portion and is only to
+ // be used with the node's string offsets to generate X-values.
+ nPortionLen = pNode->Len() - nPortionStart;
+ }
+ else
+ {
+ nPortionLen = pPortion->GetLen();
+ }
+
+ DBG_ASSERT( pPortion->GetKind() != PortionKind::HYPHENATOR, "CreateLines: Hyphenator-Portion!" );
+ DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion in CreateLines ?!" );
+ if ( pNextFeature && ( pNextFeature->GetStart() == nTmpPos ) )
+ {
+ SAL_WARN_IF( bContinueLastPortion,
+ "editeng","ImpEditEngine::CreateLines - feature in continued portion will be wrong");
+ sal_uInt16 nWhich = pNextFeature->GetItem()->Which();
+ switch ( nWhich )
+ {
+ case EE_FEATURE_TAB:
+ {
+ tools::Long nOldTmpWidth = nTmpWidth;
+
+ // Search for Tab-Pos...
+ tools::Long nCurPos = nTmpWidth + nStartX;
+ // consider scaling
+ if (maStatus.DoStretch() && (mfFontScaleX != 100.0))
+ nCurPos = basegfx::fround(double(nCurPos) * 100.0 / std::max(mfFontScaleX, 1.0));
+
+ short nAllSpaceBeforeText = static_cast< short >(rLRItem.GetTextLeft()/* + rLRItem.GetTextLeft()*/ + nSpaceBeforeAndMinLabelWidth);
+ aCurrentTab.aTabStop = pNode->GetContentAttribs().FindTabStop( nCurPos - nAllSpaceBeforeText /*rLRItem.GetTextLeft()*/, maEditDoc.GetDefTab() );
+ aCurrentTab.nTabPos = scaleXFontValue(tools::Long(aCurrentTab.aTabStop.GetTabPos() + nAllSpaceBeforeText/*rLRItem.GetTextLeft()*/));
+ aCurrentTab.bValid = false;
+
+ // Switch direction in R2L para...
+ if ( bRightToLeftPara )
+ {
+ if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right )
+ aCurrentTab.aTabStop.GetAdjustment() = SvxTabAdjust::Left;
+ else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Left )
+ aCurrentTab.aTabStop.GetAdjustment() = SvxTabAdjust::Right;
+ }
+
+ if ( ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right ) ||
+ ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center ) ||
+ ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal ) )
+ {
+ // For LEFT / DEFAULT this tab is not considered.
+ aCurrentTab.bValid = true;
+ aCurrentTab.nStartPosX = nTmpWidth;
+ aCurrentTab.nTabPortion = nTmpPortion;
+ }
+
+ pPortion->SetKind(PortionKind::TAB);
+ pPortion->SetExtraValue( aCurrentTab.aTabStop.GetFill() );
+ pPortion->setWidth( aCurrentTab.nTabPos - (nTmpWidth+nStartX) );
+
+ // Height needed...
+ SeekCursor( pNode, nTmpPos+1, aTmpFont );
+ pPortion->setHeight( GetRefDevice()->GetTextHeight() );
+
+ DBG_ASSERT( pPortion->GetSize().Width() >= 0, "Tab incorrectly calculated!" );
+
+ nTmpWidth = aCurrentTab.nTabPos-nStartX;
+
+ // If this is the first token on the line,
+ // and nTmpWidth > maPaperSize.Width, => infinite loop!
+ if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
+ {
+ // What now?
+ // make the tab fitting
+ pPortion->setWidth( nXWidth-nOldTmpWidth );
+ nTmpWidth = nXWidth-1;
+ bEOL = true;
+ bBrokenLine = true;
+ }
+ EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray();
+ size_t nPos = nTmpPos - pLine->GetStart();
+ rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
+ bCompressedChars = false;
+ }
+ break;
+ case EE_FEATURE_LINEBR:
+ {
+ assert( pPortion );
+ pPortion->setWidth(0);
+ bEOL = true;
+ bLineBreak = true;
+ pPortion->SetKind( PortionKind::LINEBREAK );
+ bCompressedChars = false;
+ EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray();
+ size_t nPos = nTmpPos - pLine->GetStart();
+ rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
+ }
+ break;
+ case EE_FEATURE_FIELD:
+ {
+ SeekCursor( pNode, nTmpPos+1, aTmpFont );
+ aTmpFont.SetPhysFont(*GetRefDevice());
+ ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());
+
+ OUString aFieldValue = static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue();
+ // get size, but also DXArray to allow length information in line breaking below
+ KernArray aTmpDXArray;
+ pPortion->SetSize(aTmpFont.QuickGetTextSize(GetRefDevice(),
+ aFieldValue, 0, aFieldValue.getLength(), &aTmpDXArray));
+
+ // So no scrolling for oversized fields
+ if ( pPortion->GetSize().Width() > nXWidth )
+ {
+ // create ExtraPortionInfo on-demand, flush lineBreaksList
+ ExtraPortionInfo *pExtraInfo = pPortion->GetExtraInfos();
+
+ if(nullptr == pExtraInfo)
+ {
+ pExtraInfo = new ExtraPortionInfo();
+ pExtraInfo->nOrgWidth = nXWidth;
+ pPortion->SetExtraInfos(pExtraInfo);
+ }
+ else
+ {
+ pExtraInfo->lineBreaksList.clear();
+ }
+
+ // iterate over CellBreaks using XBreakIterator to be on the
+ // safe side with international texts/charSets
+ Reference < i18n::XBreakIterator > xBreakIterator(ImplGetBreakIterator());
+ const sal_Int32 nTextLength(aFieldValue.getLength());
+ const lang::Locale aLocale(GetLocale(EditPaM(pNode, nPortionStart)));
+ sal_Int32 nDone(0);
+ sal_Int32 nNextCellBreak(
+ xBreakIterator->nextCharacters(
+ aFieldValue,
+ 0,
+ aLocale,
+ css::i18n::CharacterIteratorMode::SKIPCELL,
+ 0,
+ nDone));
+ sal_Int32 nLastCellBreak(0);
+ sal_Int32 nLineStartX(0);
+
+ // always add 1st line break (safe, we already know we are larger than nXWidth)
+ pExtraInfo->lineBreaksList.push_back(0);
+
+ for(sal_Int32 a(0); a < nTextLength; a++)
+ {
+ if(a == nNextCellBreak)
+ {
+ // check width
+ if(aTmpDXArray[a] - nLineStartX > nXWidth)
+ {
+ // new CellBreak does not fit in current line, need to
+ // create a break at LastCellBreak - but do not add 1st
+ // line break twice for very tall frames
+ if(0 != a)
+ {
+ pExtraInfo->lineBreaksList.push_back(a);
+ }
+
+ // moveLineStart forward in X
+ nLineStartX = aTmpDXArray[nLastCellBreak];
+ }
+
+ // update CellBreak iteration values
+ nLastCellBreak = a;
+ nNextCellBreak = xBreakIterator->nextCharacters(
+ aFieldValue,
+ a,
+ aLocale,
+ css::i18n::CharacterIteratorMode::SKIPCELL,
+ 1,
+ nDone);
+ }
+ }
+ }
+ nTmpWidth += pPortion->GetSize().Width();
+ EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray();
+ size_t nPos = nTmpPos - pLine->GetStart();
+ rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
+ pPortion->SetKind(PortionKind::FIELD);
+ // If this is the first token on the line,
+ // and nTmpWidth > maPaperSize.Width, => infinite loop!
+ if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
+ {
+ nTmpWidth = nXWidth-1;
+ bEOL = true;
+ bBrokenLine = true;
+ }
+ // Compression in Fields????
+ // I think this could be a little bit difficult and is not very useful
+ bCompressedChars = false;
+ }
+ break;
+ default: OSL_FAIL( "What feature?" );
+ }
+ pNextFeature = pNode->GetCharAttribs().FindFeature( pNextFeature->GetStart() + 1 );
+ }
+ else
+ {
+ DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion - Extra Space?!" );
+ SeekCursor( pNode, nTmpPos+1, aTmpFont );
+ aTmpFont.SetPhysFont(*GetRefDevice());
+ ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());
+
+ if (!bContinueLastPortion)
+ pPortion->SetRightToLeftLevel( GetRightToLeft( nPara, nTmpPos+1 ) );
+
+ if (bContinueLastPortion)
+ {
+ Size aSize( aTmpFont.QuickGetTextSize( GetRefDevice(),
+ pParaPortion->GetNode()->GetString(), nTmpPos, nPortionLen, &aCharPositionArray ));
+ pPortion->adjustSize(aSize.Width(), 0);
+ if (pPortion->GetSize().Height() < aSize.Height())
+ pPortion->setHeight(aSize.Height());
+ }
+ else
+ {
+ auto aSize = aTmpFont.QuickGetTextSize(GetRefDevice(), pParaPortion->GetNode()->GetString(), nTmpPos, nPortionLen, &aCharPositionArray);
+ pPortion->SetSize(aSize);
+ }
+
+ // #i9050# Do Kerning also behind portions...
+ if ( ( aTmpFont.GetFixKerning() > 0 ) && ( ( nTmpPos + nPortionLen ) < pNode->Len() ) )
+ pPortion->adjustSize(aTmpFont.GetFixKerning(), 0);
+ if ( IsFixedCellHeight() )
+ {
+ pPortion->setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) );
+ }
+ // The array is generally flattened at the beginning
+ // => Always simply quick inserts.
+ size_t nPos = nTmpPos - pLine->GetStart();
+ EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray();
+ assert(aCharPositionArray.get_factor() == 1);
+ std::vector<sal_Int32>& rKernArray = aCharPositionArray.get_subunit_array();
+ rArray.insert( rArray.begin() + nPos, rKernArray.data(), rKernArray.data() + nPortionLen);
+
+ // And now check for Compression:
+ if ( !bContinueLastPortion && nPortionLen && GetAsianCompressionMode() != CharCompressType::NONE )
+ {
+ sal_Int32* pDXArray = rArray.data() + nTmpPos - pLine->GetStart();
+ bCompressedChars |= ImplCalcAsianCompression(
+ pNode, pPortion, nTmpPos, pDXArray, 10000, false);
+ }
+
+ nTmpWidth += pPortion->GetSize().Width();
+
+ sal_Int32 _nPortionEnd = nTmpPos + nPortionLen;
+ if( bScriptSpace && ( _nPortionEnd < pNode->Len() ) && ( nTmpWidth < nXWidth ) && IsScriptChange( EditPaM( pNode, _nPortionEnd ) ) )
+ {
+ bool bAllow = false;
+ sal_uInt16 nScriptTypeLeft = GetI18NScriptType( EditPaM( pNode, _nPortionEnd ) );
+ sal_uInt16 nScriptTypeRight = GetI18NScriptType( EditPaM( pNode, _nPortionEnd+1 ) );
+ if ( ( nScriptTypeLeft == i18n::ScriptType::ASIAN ) || ( nScriptTypeRight == i18n::ScriptType::ASIAN ) )
+ bAllow = true;
+
+ // No spacing within L2R/R2L nesting
+ if ( bAllow )
+ {
+ tools::Long nExtraSpace = pPortion->GetSize().Height() / 5;
+ nExtraSpace = scaleXSpacingValue(nExtraSpace);
+ pPortion->adjustSize(nExtraSpace, 0);
+ nTmpWidth += nExtraSpace;
+ }
+ }
+ }
+
+ if ( aCurrentTab.bValid && ( nTmpPortion != aCurrentTab.nTabPortion ) )
+ {
+ tools::Long nWidthAfterTab = 0;
+ for ( sal_Int32 n = aCurrentTab.nTabPortion+1; n <= nTmpPortion; n++ )
+ {
+ const TextPortion& rTP = pParaPortion->GetTextPortions()[n];
+ nWidthAfterTab += rTP.GetSize().Width();
+ }
+ tools::Long nW = nWidthAfterTab; // Length before tab position
+ if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right )
+ {
+ }
+ else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center )
+ {
+ nW = nWidthAfterTab/2;
+ }
+ else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal )
+ {
+ OUString aText = GetSelected( EditSelection( EditPaM( pParaPortion->GetNode(), nTmpPos ),
+ EditPaM( pParaPortion->GetNode(), nTmpPos + nPortionLen ) ) );
+ sal_Int32 nDecPos = aText.indexOf( aCurrentTab.aTabStop.GetDecimal() );
+ if ( nDecPos != -1 )
+ {
+ nW -= pParaPortion->GetTextPortions()[nTmpPortion].GetSize().Width();
+ nW += aTmpFont.QuickGetTextSize( GetRefDevice(), pParaPortion->GetNode()->GetString(),
+ nTmpPos, nDecPos, nullptr ).Width();
+ aCurrentTab.bValid = false;
+ }
+ }
+ else
+ {
+ OSL_FAIL( "CreateLines: Tab not handled!" );
+ }
+ tools::Long nMaxW = aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nStartX;
+ if ( nW >= nMaxW )
+ {
+ nW = nMaxW;
+ aCurrentTab.bValid = false;
+ }
+ TextPortion& rTabPortion = pParaPortion->GetTextPortions()[aCurrentTab.nTabPortion];
+ rTabPortion.setWidth( aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nW - nStartX );
+ nTmpWidth = aCurrentTab.nStartPosX + rTabPortion.GetSize().Width() + nWidthAfterTab;
+ }
+
+ nTmpPos = nTmpPos + nPortionLen;
+ nPortionEnd = nTmpPos;
+ nTmpPortion++;
+ if (maStatus.OneCharPerLine())
+ bEOL = true;
+ }
+
+ DBG_ASSERT( pPortion, "no portion!?" );
+
+ aCurrentTab.bValid = false;
+
+ assert(pLine);
+
+ // this was possibly a portion too far:
+ bool bFixedEnd = false;
+ if (maStatus.OneCharPerLine())
+ {
+ // State before Portion (apart from nTmpWidth):
+ nTmpPos -= pPortion ? nPortionLen : 0;
+ nPortionStart = nTmpPos;
+ nTmpPortion--;
+
+ bEOL = true;
+ bEOC = false;
+
+ // And now just one character:
+ nTmpPos++;
+ nTmpPortion++;
+ nPortionEnd = nTmpPortion;
+ // one Non-Feature-Portion has to be wrapped
+ if ( pPortion && nPortionLen > 1 )
+ {
+ DBG_ASSERT( pPortion->GetKind() == PortionKind::TEXT, "Len>1, but no TextPortion?" );
+ nTmpWidth -= pPortion->GetSize().Width();
+ sal_Int32 nP = SplitTextPortion( pParaPortion, nTmpPos, pLine );
+ nTmpWidth += pParaPortion->GetTextPortions()[nP].GetSize().Width();
+ }
+ }
+ else if ( nTmpWidth >= nXWidth )
+ {
+ nPortionEnd = nTmpPos;
+ nTmpPos -= pPortion ? nPortionLen : 0;
+ nPortionStart = nTmpPos;
+ nTmpPortion--;
+ bEOL = false;
+ bEOC = false;
+ if( pPortion ) switch ( pPortion->GetKind() )
+ {
+ case PortionKind::TEXT:
+ {
+ nTmpWidth -= pPortion->GetSize().Width();
+ }
+ break;
+ case PortionKind::FIELD:
+ case PortionKind::TAB:
+ {
+ nTmpWidth -= pPortion->GetSize().Width();
+ bEOL = true;
+ bFixedEnd = true;
+ }
+ break;
+ default:
+ {
+ // A feature is not wrapped:
+ DBG_ASSERT( ( pPortion->GetKind() == PortionKind::LINEBREAK ), "What Feature ?" );
+ bEOL = true;
+ bFixedEnd = true;
+ }
+ }
+ }
+ else
+ {
+ bEOL = true;
+ bEOC = true;
+ pLine->SetEnd( nPortionEnd );
+ assert( pParaPortion->GetTextPortions().Count() && "No TextPortions?" );
+ pLine->SetEndPortion( pParaPortion->GetTextPortions().Count() - 1 );
+ }
+
+ if (maStatus.OneCharPerLine())
+ {
+ pLine->SetEnd( nPortionEnd );
+ pLine->SetEndPortion( nTmpPortion-1 );
+ }
+ else if ( bFixedEnd )
+ {
+ pLine->SetEnd( nPortionStart );
+ pLine->SetEndPortion( nTmpPortion-1 );
+ }
+ else if ( bLineBreak || bBrokenLine )
+ {
+ pLine->SetEnd( nPortionStart+1 );
+ pLine->SetEndPortion( nTmpPortion-1 );
+ bEOC = false; // was set above, maybe change the sequence of the if's?
+ }
+ else if ( !bEOL && !bContinueLastPortion )
+ {
+ DBG_ASSERT( pPortion && ((nPortionEnd-nPortionStart) == pPortion->GetLen()), "However, another portion?!" );
+ tools::Long nRemainingWidth = !maStatus.IsSingleLine() ?
+ nMaxLineWidth - nTmpWidth : pLine->GetCharPosArray()[pLine->GetCharPosArray().size() - 1] + 1;
+ bool bCanHyphenate = ( aTmpFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL );
+ if ( bCompressedChars && pPortion && ( pPortion->GetLen() > 1 ) && pPortion->GetExtraInfos() && pPortion->GetExtraInfos()->bCompressed )
+ {
+ // I need the manipulated DXArray for determining the break position...
+ sal_Int32* pDXArray = pLine->GetCharPosArray().data() + (nPortionStart - pLine->GetStart());
+ ImplCalcAsianCompression(
+ pNode, pPortion, nPortionStart, pDXArray, 10000, true);
+ }
+ if( pPortion )
+ ImpBreakLine( pParaPortion, pLine, pPortion, nPortionStart,
+ nRemainingWidth, bCanHyphenate && bHyphenatePara );
+ }
+
+
+ // Line finished => adjust
+
+
+ // CalcTextSize should be replaced by a continuous registering!
+ Size aTextSize = pLine->CalcTextSize( *pParaPortion );
+
+ if ( aTextSize.Height() == 0 )
+ {
+ SeekCursor( pNode, pLine->GetStart()+1, aTmpFont );
+ aTmpFont.SetPhysFont(*pRefDev);
+ ImplInitDigitMode(*pRefDev, aTmpFont.GetLanguage());
+
+ if ( IsFixedCellHeight() )
+ aTextSize.setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) );
+ else
+ aTextSize.setHeight( aTmpFont.GetPhysTxtSize( pRefDev ).Height() );
+ pLine->SetHeight( static_cast<sal_uInt16>(aTextSize.Height()) );
+ }
+
+ // The font metrics can not be calculated continuously, if the font is
+ // set anyway, because a large font only after wrapping suddenly ends
+ // up in the next line => Font metrics too big.
+ FormatterFontMetric aFormatterMetrics;
+ sal_Int32 nTPos = pLine->GetStart();
+ for ( sal_Int32 nP = pLine->GetStartPortion(); nP <= pLine->GetEndPortion(); nP++ )
+ {
+ const TextPortion& rTP = pParaPortion->GetTextPortions()[nP];
+ // problem with hard font height attribute, when everything but the line break has this attribute
+ if ( rTP.GetKind() != PortionKind::LINEBREAK )
+ {
+ SeekCursor( pNode, nTPos+1, aTmpFont );
+ aTmpFont.SetPhysFont(*GetRefDevice());
+ ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());
+ RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont );
+ }
+ nTPos = nTPos + rTP.GetLen();
+ }
+ sal_uInt16 nLineHeight = aFormatterMetrics.GetHeight();
+ if ( nLineHeight > pLine->GetHeight() )
+ pLine->SetHeight( nLineHeight );
+ pLine->SetMaxAscent( aFormatterMetrics.nMaxAscent );
+
+ bSameLineAgain = false;
+ if ( GetTextRanger() && ( pLine->GetHeight() > nTextLineHeight ) )
+ {
+ // put down with the other size!
+ bSameLineAgain = true;
+ }
+
+ if (!bSameLineAgain && !maStatus.IsOutliner())
+ {
+ if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min )
+ {
+ double fMinHeight = scaleYSpacingValue(rLSItem.GetLineHeight());
+ sal_uInt16 nMinHeight = basegfx::fround(fMinHeight);
+
+ sal_uInt16 nTxtHeight = pLine->GetHeight();
+ if ( nTxtHeight < nMinHeight )
+ {
+ // The Ascent has to be adjusted for the difference:
+ tools::Long nDiff = nMinHeight - nTxtHeight;
+ pLine->SetMaxAscent( static_cast<sal_uInt16>(pLine->GetMaxAscent() + nDiff) );
+ pLine->SetHeight( nMinHeight, nTxtHeight );
+ }
+ }
+ else if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Fix )
+ {
+ double fFixHeight = scaleYSpacingValue(rLSItem.GetLineHeight());
+ sal_uInt16 nFixHeight = basegfx::fround(fFixHeight);
+
+ sal_uInt16 nTxtHeight = pLine->GetHeight();
+ pLine->SetMaxAscent( static_cast<sal_uInt16>(pLine->GetMaxAscent() + ( nFixHeight - nTxtHeight ) ) );
+ pLine->SetHeight( nFixHeight, nTxtHeight );
+ }
+ else if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop )
+ {
+ // There are documents with PropLineSpace 0, why?
+ // (cmc: re above question :-) such documents can be seen by importing a .ppt
+ sal_uInt16 nPropLineSpace = rLSItem.GetPropLineSpace();
+ double fProportionalScale = double(nPropLineSpace) / 100.0;
+ constexpr const double f80Percent = 8.0 / 10.0;
+ double fSpacingFactor = mfSpacingScaleY / 100.0;
+ if (nPropLineSpace && nPropLineSpace < 100)
+ {
+ // Adapted code from sw/source/core/text/itrform2.cxx
+ sal_uInt16 nAscent = pLine->GetMaxAscent();
+ sal_uInt16 nNewAscent = basegfx::fround(pLine->GetTxtHeight() * fSpacingFactor * fProportionalScale * f80Percent);
+ if (!nAscent || nAscent > nNewAscent)
+ pLine->SetMaxAscent(nNewAscent);
+ sal_uInt16 nHeight = basegfx::fround(pLine->GetHeight() * fProportionalScale * fSpacingFactor);
+
+ pLine->SetHeight(nHeight, pLine->GetTxtHeight());
+ }
+ else if (nPropLineSpace && nPropLineSpace != 100)
+ {
+ sal_uInt16 nTxtHeight = pLine->GetHeight();
+ sal_Int32 nPropTextHeight = nTxtHeight * fProportionalScale * fSpacingFactor;
+ // The Ascent has to be adjusted for the difference:
+ tools::Long nDiff = pLine->GetHeight() - nPropTextHeight;
+ pLine->SetMaxAscent( static_cast<sal_uInt16>( pLine->GetMaxAscent() - nDiff ) );
+ pLine->SetHeight( static_cast<sal_uInt16>( nPropTextHeight ), nTxtHeight );
+ }
+ }
+ else if (rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Off)
+ {
+ if (mfSpacingScaleY < 100.0)
+ {
+ double fSpacingFactor = mfSpacingScaleY / 100.0;
+ sal_uInt16 nPropLineSpace = basegfx::fround(100.0 * fSpacingFactor);
+ if (nPropLineSpace && nPropLineSpace < 100)
+ {
+ // Adapted code from sw/source/core/text/itrform2.cxx
+ sal_uInt16 nAscent = pLine->GetMaxAscent();
+ sal_uInt16 nNewAscent = basegfx::fround(pLine->GetTxtHeight() * fSpacingFactor);
+ if (!nAscent || nAscent > nNewAscent)
+ pLine->SetMaxAscent(nNewAscent);
+ sal_uInt16 nHeight = basegfx::fround(pLine->GetHeight() * fSpacingFactor);
+
+ pLine->SetHeight(nHeight, pLine->GetTxtHeight());
+ }
+
+ }
+ }
+ }
+
+ if ( ( !IsEffectivelyVertical() && maStatus.AutoPageWidth() ) ||
+ ( IsEffectivelyVertical() && maStatus.AutoPageHeight() ) )
+ {
+ // If the row fits within the current paper width, then this width
+ // has to be used for the Alignment. If it does not fit or if it
+ // will change the paper width, it will be formatted again for
+ // Justification! = LEFT anyway.
+ tools::Long nMaxLineWidthFix = GetColumnWidth(maPaperSize) - scaleXSpacingValue(rLRItem.GetRight()) - nStartX;
+ if ( aTextSize.Width() < nMaxLineWidthFix )
+ nMaxLineWidth = nMaxLineWidthFix;
+ }
+
+ if ( bCompressedChars )
+ {
+ tools::Long nRemainingWidth = nMaxLineWidth - aTextSize.Width();
+ if ( nRemainingWidth > 0 )
+ {
+ ImplExpandCompressedPortions( pLine, pParaPortion, nRemainingWidth );
+ aTextSize = pLine->CalcTextSize( *pParaPortion );
+ }
+ }
+
+ if ( pLine->IsHangingPunctuation() )
+ {
+ // Width from HangingPunctuation was set to 0 in ImpBreakLine,
+ // check for rel width now, maybe create compression...
+ tools::Long n = nMaxLineWidth - aTextSize.Width();
+ TextPortion& rTP = pParaPortion->GetTextPortions()[pLine->GetEndPortion()];
+ sal_Int32 nPosInArray = pLine->GetEnd()-1-pLine->GetStart();
+ tools::Long nNewValue = ( nPosInArray ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 ) + n;
+ if (o3tl::make_unsigned(nPosInArray) < pLine->GetCharPosArray().size())
+ {
+ pLine->GetCharPosArray()[ nPosInArray ] = nNewValue;
+ }
+ rTP.adjustSize(n, 0);
+ }
+
+ pLine->SetTextWidth( aTextSize.Width() );
+ switch ( eJustification )
+ {
+ case SvxAdjust::Center:
+ {
+ tools::Long n = ( nMaxLineWidth - aTextSize.Width() ) / 2;
+ n += nStartX; // Indentation is kept.
+ pLine->SetStartPosX( n );
+ }
+ break;
+ case SvxAdjust::Right:
+ {
+ // For automatically wrapped lines, which has a blank at the end
+ // the blank must not be displayed!
+ tools::Long n = nMaxLineWidth - aTextSize.Width();
+ n += nStartX; // Indentation is kept.
+ pLine->SetStartPosX( n );
+ }
+ break;
+ case SvxAdjust::Block:
+ {
+ bool bDistLastLine = (GetJustifyMethod(nPara) == SvxCellJustifyMethod::Distribute);
+ tools::Long nRemainingSpace = nMaxLineWidth - aTextSize.Width();
+ pLine->SetStartPosX( nStartX );
+ if ( nRemainingSpace > 0 && (!bEOC || bDistLastLine) )
+ ImpAdjustBlocks( pParaPortion, pLine, nRemainingSpace );
+ }
+ break;
+ default:
+ {
+ pLine->SetStartPosX( nStartX ); // FI, LI
+ }
+ break;
+ }
+
+
+ // Check whether the line must be re-issued...
+
+ pLine->SetInvalid();
+
+ // If a portion was wrapped there may be far too many positions in
+ // CharPosArray:
+ EditLine::CharPosArrayType& rArray = pLine->GetCharPosArray();
+ size_t nLen = pLine->GetLen();
+ if (rArray.size() > nLen)
+ rArray.erase(rArray.begin()+nLen, rArray.end());
+
+ if ( GetTextRanger() )
+ {
+ if ( nTextXOffset )
+ pLine->SetStartPosX( pLine->GetStartPosX() + nTextXOffset );
+ if ( nTextExtraYOffset )
+ {
+ pLine->SetHeight( static_cast<sal_uInt16>( pLine->GetHeight() + nTextExtraYOffset ), 0 );
+ pLine->SetMaxAscent( static_cast<sal_uInt16>( pLine->GetMaxAscent() + nTextExtraYOffset ) );
+ }
+ }
+
+ // for <0 think over !
+ if ( pParaPortion->IsSimpleInvalid() )
+ {
+ // Change through simple Text changes...
+ // Do not cancel formatting since Portions possibly have to be split
+ // again! If at some point cancelable, then validate the following
+ // line! But if applicable, mark as valid, so there is less output...
+ if ( pLine->GetEnd() < nInvalidStart )
+ {
+ if ( *pLine == aSaveLine )
+ {
+ pLine->SetValid();
+ }
+ }
+ else
+ {
+ sal_Int32 nStart = pLine->GetStart();
+ sal_Int32 nEnd = pLine->GetEnd();
+
+ if ( nStart > nInvalidEnd )
+ {
+ if ( ( ( nStart-nInvalidDiff ) == aSaveLine.GetStart() ) &&
+ ( ( nEnd-nInvalidDiff ) == aSaveLine.GetEnd() ) )
+ {
+ pLine->SetValid();
+ if (bQuickFormat)
+ {
+ bLineBreak = false;
+ pParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
+ break;
+ }
+ }
+ }
+ else if (bQuickFormat && (nEnd > nInvalidEnd))
+ {
+ // If the invalid line ends so that the next begins on the
+ // 'same' passage as before, i.e. not wrapped differently,
+ // then the text width does not have to be determined anew:
+ if ( nEnd == ( aSaveLine.GetEnd() + nInvalidDiff ) )
+ {
+ bLineBreak = false;
+ pParaPortion->CorrectValuesBehindLastFormattedLine( nLine );
+ break;
+ }
+ }
+ }
+ }
+
+ if ( !bSameLineAgain )
+ {
+ nIndex = pLine->GetEnd(); // next line start = last line end
+ // as nEnd points to the last character!
+
+ sal_Int32 nEndPortion = pLine->GetEndPortion();
+ nCurrentPosY += pLine->GetHeight();
+
+ // Next line or maybe a new line...
+ pLine = nullptr;
+ if ( nLine < pParaPortion->GetLines().Count()-1 )
+ pLine = &pParaPortion->GetLines()[++nLine];
+ if ( pLine && ( nIndex >= pNode->Len() ) )
+ {
+ nDelFromLine = nLine;
+ break;
+ }
+ // Stop processing if allowed and this is outside of the paper size height.
+ // Format at least two lines though, in case something detects whether
+ // the text has been wrapped or something similar.
+ if( mbSkipOutsideFormat && nLine > 2
+ && !maStatus.AutoPageHeight() && maPaperSize.Height() < nCurrentPosY )
+ {
+ if ( pLine && ( nIndex >= pNode->Len()) )
+ nDelFromLine = nLine;
+ break;
+ }
+ if ( !pLine )
+ {
+ if ( nIndex < pNode->Len() )
+ {
+ pLine = new EditLine;
+ pParaPortion->GetLines().Insert(++nLine, pLine);
+ }
+ else if ( nIndex && bLineBreak && GetTextRanger() )
+ {
+ // normally CreateAndInsertEmptyLine would be called, but I want to use
+ // CreateLines, so I need Polygon code only here...
+ TextPortion* pDummyPortion = new TextPortion( 0 );
+ pParaPortion->GetTextPortions().Append(pDummyPortion);
+ pLine = new EditLine;
+ pParaPortion->GetLines().Insert(++nLine, pLine);
+ bForceOneRun = true;
+ bProcessingEmptyLine = true;
+ }
+ }
+ if ( pLine )
+ {
+ aSaveLine = *pLine;
+ pLine->SetStart( nIndex );
+ pLine->SetEnd( nIndex );
+ pLine->SetStartPortion( nEndPortion+1 );
+ pLine->SetEndPortion( nEndPortion+1 );
+ }
+ }
+ } // while ( Index < Len )
+
+ if ( nDelFromLine >= 0 )
+ pParaPortion->GetLines().DeleteFromLine( nDelFromLine );
+
+ DBG_ASSERT( pParaPortion->GetLines().Count(), "No line after CreateLines!" );
+
+ if ( bLineBreak )
+ CreateAndInsertEmptyLine( pParaPortion );
+
+ bool bHeightChanged = FinishCreateLines( pParaPortion );
+
+ if ( bMapChanged )
+ GetRefDevice()->Pop();
+
+ GetRefDevice()->Pop();
+
+ return bHeightChanged;
+}
+
+void ImpEditEngine::CreateAndInsertEmptyLine( ParaPortion* pParaPortion )
+{
+ DBG_ASSERT( !GetTextRanger(), "Don't use CreateAndInsertEmptyLine with a polygon!" );
+
+ EditLine* pTmpLine = new EditLine;
+ pTmpLine->SetStart( pParaPortion->GetNode()->Len() );
+ pTmpLine->SetEnd( pParaPortion->GetNode()->Len() );
+ pParaPortion->GetLines().Append(pTmpLine);
+
+ bool bLineBreak = pParaPortion->GetNode()->Len() > 0;
+ sal_Int32 nSpaceBefore = 0;
+ sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pParaPortion->GetNode(), &nSpaceBefore );
+ const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pParaPortion->GetNode() );
+ const SvxLineSpacingItem& rLSItem = pParaPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
+ tools::Long nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBefore);
+
+ tools::Rectangle aBulletArea { Point(), Point() };
+ if ( bLineBreak )
+ {
+ nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBeforeAndMinLabelWidth);
+ }
+ else
+ {
+ aBulletArea = GetEditEnginePtr()->GetBulletArea( GetParaPortions().GetPos( pParaPortion ) );
+ if ( !aBulletArea.IsEmpty() && aBulletArea.Right() > 0 )
+ pParaPortion->SetBulletX(sal_Int32(scaleXSpacingValue(aBulletArea.Right())));
+ else
+ pParaPortion->SetBulletX( 0 ); // If Bullet set incorrectly.
+ if ( pParaPortion->GetBulletX() > nStartX )
+ {
+ nStartX = scaleXSpacingValue(rLRItem.GetTextLeft() + rLRItem.GetTextFirstLineOffset() + nSpaceBeforeAndMinLabelWidth);
+ if ( pParaPortion->GetBulletX() > nStartX )
+ nStartX = pParaPortion->GetBulletX();
+ }
+ }
+
+ SvxFont aTmpFont;
+ SeekCursor( pParaPortion->GetNode(), bLineBreak ? pParaPortion->GetNode()->Len() : 0, aTmpFont );
+ aTmpFont.SetPhysFont(*pRefDev);
+
+ TextPortion* pDummyPortion = new TextPortion( 0 );
+ pDummyPortion->SetSize(aTmpFont.GetPhysTxtSize(pRefDev));
+ if ( IsFixedCellHeight() )
+ pDummyPortion->setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) );
+ pParaPortion->GetTextPortions().Append(pDummyPortion);
+ FormatterFontMetric aFormatterMetrics;
+ RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont );
+ pTmpLine->SetMaxAscent( aFormatterMetrics.nMaxAscent );
+ pTmpLine->SetHeight( static_cast<sal_uInt16>(pDummyPortion->GetSize().Height()) );
+ sal_uInt16 nLineHeight = aFormatterMetrics.GetHeight();
+ if ( nLineHeight > pTmpLine->GetHeight() )
+ pTmpLine->SetHeight( nLineHeight );
+
+ if (!maStatus.IsOutliner())
+ {
+ sal_Int32 nPara = GetParaPortions().GetPos( pParaPortion );
+ SvxAdjust eJustification = GetJustification( nPara );
+ tools::Long nMaxLineWidth = GetColumnWidth(maPaperSize);
+ nMaxLineWidth -= scaleXSpacingValue(rLRItem.GetRight());
+ if ( nMaxLineWidth < 0 )
+ nMaxLineWidth = 1;
+ if ( eJustification == SvxAdjust::Center )
+ nStartX = nMaxLineWidth / 2;
+ else if ( eJustification == SvxAdjust::Right )
+ nStartX = nMaxLineWidth;
+ }
+
+ pTmpLine->SetStartPosX( nStartX );
+
+ if (!maStatus.IsOutliner())
+ {
+ if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min )
+ {
+ sal_uInt16 nMinHeight = rLSItem.GetLineHeight();
+ sal_uInt16 nTxtHeight = pTmpLine->GetHeight();
+ if ( nTxtHeight < nMinHeight )
+ {
+ // The Ascent has to be adjusted for the difference:
+ tools::Long nDiff = nMinHeight - nTxtHeight;
+ pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + nDiff) );
+ pTmpLine->SetHeight( nMinHeight, nTxtHeight );
+ }
+ }
+ else if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Fix )
+ {
+ sal_uInt16 nFixHeight = rLSItem.GetLineHeight();
+ sal_uInt16 nTxtHeight = pTmpLine->GetHeight();
+
+ pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + ( nFixHeight - nTxtHeight ) ) );
+ pTmpLine->SetHeight( nFixHeight, nTxtHeight );
+ }
+ else if ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop )
+ {
+ sal_Int32 nPara = GetParaPortions().GetPos( pParaPortion );
+ if ( nPara || pTmpLine->GetStartPortion() ) // Not the very first line
+ {
+ // There are documents with PropLineSpace 0, why?
+ // (cmc: re above question :-) such documents can be seen by importing a .ppt
+ if ( rLSItem.GetPropLineSpace() && ( rLSItem.GetPropLineSpace() != 100 ) )
+ {
+ sal_uInt16 nTxtHeight = pTmpLine->GetHeight();
+ sal_Int32 nH = nTxtHeight;
+ nH *= rLSItem.GetPropLineSpace();
+ nH /= 100;
+ // The Ascent has to be adjusted for the difference:
+ tools::Long nDiff = pTmpLine->GetHeight() - nH;
+ if ( nDiff > pTmpLine->GetMaxAscent() )
+ nDiff = pTmpLine->GetMaxAscent();
+ pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() - nDiff) );
+ pTmpLine->SetHeight( static_cast<sal_uInt16>(nH), nTxtHeight );
+ }
+ }
+ }
+ }
+
+ if ( !bLineBreak )
+ {
+ tools::Long nMinHeight = aBulletArea.GetHeight();
+ if ( nMinHeight > static_cast<tools::Long>(pTmpLine->GetHeight()) )
+ {
+ tools::Long nDiff = nMinHeight - static_cast<tools::Long>(pTmpLine->GetHeight());
+ // distribute nDiff upwards and downwards
+ pTmpLine->SetMaxAscent( static_cast<sal_uInt16>(pTmpLine->GetMaxAscent() + nDiff/2) );
+ pTmpLine->SetHeight( static_cast<sal_uInt16>(nMinHeight) );
+ }
+ }
+ else
+ {
+ // -2: The new one is already inserted.
+#ifdef DBG_UTIL
+ EditLine& rLastLine = pParaPortion->GetLines()[pParaPortion->GetLines().Count()-2];
+ DBG_ASSERT( rLastLine.GetEnd() == pParaPortion->GetNode()->Len(), "different anyway?" );
+#endif
+ sal_Int32 nPos = pParaPortion->GetTextPortions().Count() - 1 ;
+ pTmpLine->SetStartPortion( nPos );
+ pTmpLine->SetEndPortion( nPos );
+ }
+}
+
+bool ImpEditEngine::FinishCreateLines( ParaPortion* pParaPortion )
+{
+// CalcCharPositions( pParaPortion );
+ pParaPortion->SetValid();
+ tools::Long nOldHeight = pParaPortion->GetHeight();
+ CalcHeight( pParaPortion );
+
+ DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "FinishCreateLines: No Text-Portion?" );
+ bool bRet = ( pParaPortion->GetHeight() != nOldHeight );
+ return bRet;
+}
+
+void ImpEditEngine::ImpBreakLine( ParaPortion* pParaPortion, EditLine* pLine, TextPortion const * pPortion, sal_Int32 nPortionStart, tools::Long nRemainingWidth, bool bCanHyphenate )
+{
+ ContentNode* const pNode = pParaPortion->GetNode();
+
+ sal_Int32 nBreakInLine = nPortionStart - pLine->GetStart();
+ sal_Int32 nMax = nBreakInLine + pPortion->GetLen();
+ while ( ( nBreakInLine < nMax ) && ( pLine->GetCharPosArray()[nBreakInLine] < nRemainingWidth ) )
+ nBreakInLine++;
+
+ sal_Int32 nMaxBreakPos = nBreakInLine + pLine->GetStart();
+ sal_Int32 nBreakPos = SAL_MAX_INT32;
+
+ bool bCompressBlank = false;
+ bool bHyphenated = false;
+ bool bHangingPunctuation = false;
+ sal_Unicode cAlternateReplChar = 0;
+ sal_Unicode cAlternateExtraChar = 0;
+ bool bAltFullLeft = false;
+ bool bAltFullRight = false;
+ sal_uInt32 nAltDelChar = 0;
+
+ if ( ( nMaxBreakPos < ( nMax + pLine->GetStart() ) ) && ( pNode->GetChar( nMaxBreakPos ) == ' ' ) )
+ {
+ // Break behind the blank, blank will be compressed...
+ nBreakPos = nMaxBreakPos + 1;
+ bCompressBlank = true;
+ }
+ else
+ {
+ sal_Int32 nMinBreakPos = pLine->GetStart();
+ const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs();
+ for (size_t nAttr = rAttrs.size(); nAttr; )
+ {
+ const EditCharAttrib& rAttr = *rAttrs[--nAttr];
+ if (rAttr.IsFeature() && rAttr.GetEnd() > nMinBreakPos && rAttr.GetEnd() <= nMaxBreakPos)
+ {
+ nMinBreakPos = rAttr.GetEnd();
+ break;
+ }
+ }
+ assert(nMinBreakPos <= nMaxBreakPos);
+
+ lang::Locale aLocale = GetLocale( EditPaM( pNode, nMaxBreakPos ) );
+
+ Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
+ const bool bAllowPunctuationOutsideMargin = static_cast<const SfxBoolItem&>(
+ pNode->GetContentAttribs().GetItem( EE_PARA_HANGINGPUNCTUATION )).GetValue();
+
+ if (nMinBreakPos == nMaxBreakPos)
+ {
+ nBreakPos = nMinBreakPos;
+ }
+ else
+ {
+ Reference< XHyphenator > xHyph;
+ if ( bCanHyphenate )
+ xHyph = GetHyphenator();
+ i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, Sequence< PropertyValue >(), 1 );
+ i18n::LineBreakUserOptions aUserOptions;
+
+ const i18n::ForbiddenCharacters* pForbidden = GetForbiddenCharsTable()->GetForbiddenCharacters( LanguageTag::convertToLanguageType( aLocale ), true );
+ aUserOptions.forbiddenBeginCharacters = pForbidden->beginLine;
+ aUserOptions.forbiddenEndCharacters = pForbidden->endLine;
+ aUserOptions.applyForbiddenRules = static_cast<const SfxBoolItem&>(pNode->GetContentAttribs().GetItem( EE_PARA_FORBIDDENRULES )).GetValue();
+ aUserOptions.allowPunctuationOutsideMargin = bAllowPunctuationOutsideMargin;
+ aUserOptions.allowHyphenateEnglish = false;
+
+ if (!maStatus.IsSingleLine())
+ {
+ i18n::LineBreakResults aLBR = _xBI->getLineBreak(
+ pNode->GetString(), nMaxBreakPos, aLocale, nMinBreakPos, aHyphOptions, aUserOptions );
+ nBreakPos = aLBR.breakIndex;
+
+ // show soft hyphen
+ if ( nBreakPos && CH_SOFTHYPHEN == pNode->GetString()[ sal_Int32(nBreakPos) - 1 ] )
+ bHyphenated = true;
+ }
+ else
+ {
+ nBreakPos = nMaxBreakPos;
+ }
+
+ // BUG in I18N - under special condition (break behind field, #87327#) breakIndex is < nMinBreakPos
+ if ( nBreakPos < nMinBreakPos )
+ {
+ nBreakPos = nMinBreakPos;
+ }
+ else if ( ( nBreakPos > nMaxBreakPos ) && !aUserOptions.allowPunctuationOutsideMargin )
+ {
+ OSL_FAIL( "I18N: XBreakIterator::getLineBreak returns position > Max" );
+ nBreakPos = nMaxBreakPos;
+ }
+ // Hanging punctuation is the only case that increases nBreakPos and makes
+ // nBreakPos > nMaxBreakPos. It's expected that the hanging punctuation goes over
+ // the border of the object.
+ }
+
+ // BUG in I18N - the japanese dot is in the next line!
+ // !!! Test!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ if ( (nBreakPos + ( bAllowPunctuationOutsideMargin ? 0 : 1 ) ) <= nMaxBreakPos )
+ {
+ sal_Unicode cFirstInNextLine = ( (nBreakPos+1) < pNode->Len() ) ? pNode->GetChar( nBreakPos ) : 0;
+ if ( cFirstInNextLine == 12290 )
+ nBreakPos++;
+ }
+
+ bHangingPunctuation = nBreakPos > nMaxBreakPos;
+ pLine->SetHangingPunctuation( bHangingPunctuation );
+
+ // Whether a separator or not, push the word after the separator through
+ // hyphenation... NMaxBreakPos is the last character that fits into
+ // the line, nBreakPos is the beginning of the word.
+ // There is a problem if the Doc is so narrow that a word is broken
+ // into more than two lines...
+ if ( !bHangingPunctuation && bCanHyphenate && GetHyphenator().is() )
+ {
+ i18n::Boundary aBoundary = _xBI->getWordBoundary(
+ pNode->GetString(), nBreakPos, GetLocale( EditPaM( pNode, nBreakPos ) ), css::i18n::WordType::DICTIONARY_WORD, true);
+ sal_Int32 nWordStart = nBreakPos;
+ sal_Int32 nWordEnd = aBoundary.endPos;
+ DBG_ASSERT( nWordEnd >= nWordStart, "Start >= End?" );
+
+ sal_Int32 nWordLen = nWordEnd - nWordStart;
+ if ( ( nWordEnd >= nMaxBreakPos ) && ( nWordLen > 3 ) )
+ {
+ // May happen, because getLineBreak may differ from getWordBoundary with DICTIONARY_WORD
+ const OUString aWord = pNode->GetString().copy(nWordStart, nWordLen);
+ sal_Int32 nMinTrail = nWordEnd-nMaxBreakPos+1; //+1: Before the dickey letter
+ Reference< XHyphenatedWord > xHyphWord;
+ if (xHyphenator.is())
+ xHyphWord = xHyphenator->hyphenate( aWord, aLocale, aWord.getLength() - nMinTrail, Sequence< PropertyValue >() );
+ if (xHyphWord.is())
+ {
+ bool bAlternate = xHyphWord->isAlternativeSpelling();
+ sal_Int32 _nWordLen = 1 + xHyphWord->getHyphenPos();
+
+ if ( ( _nWordLen >= 2 ) && ( (nWordStart+_nWordLen) >= (pLine->GetStart() + 2 ) ) )
+ {
+ if ( !bAlternate )
+ {
+ bHyphenated = true;
+ nBreakPos = nWordStart + _nWordLen;
+ }
+ else
+ {
+ // TODO: handle all alternative hyphenations (see hyphen-1.2.8/tests/unicode.*)
+ OUString aAlt( xHyphWord->getHyphenatedWord() );
+ std::u16string_view aAltLeft(aAlt.subView(0, _nWordLen));
+ std::u16string_view aAltRight(aAlt.subView(_nWordLen));
+ bAltFullLeft = aWord.startsWith(aAltLeft);
+ bAltFullRight = aWord.endsWith(aAltRight);
+ nAltDelChar = aWord.getLength() - aAlt.getLength() + static_cast<int>(!bAltFullLeft) + static_cast<int>(!bAltFullRight);
+
+ // NOTE: improved for other cases, see fdo#63711
+
+ // We expect[ed] the two cases:
+ // 1) packen becomes pak-ken
+ // 2) Schiffahrt becomes Schiff-fahrt
+ // In case 1, a character has to be replaced
+ // in case 2 a character is added.
+ // The identification is complicated by long
+ // compound words because the Hyphenator separates
+ // all position of the word. [This is not true for libhyphen.]
+ // "Schiffahrtsbrennesseln" -> "Schifffahrtsbrennnesseln"
+ // We can thus actually not directly connect the index of the
+ // AlternativeWord to aWord. The whole issue will be simplified
+ // by a function in the Hyphenator as soon as AMA builds this in...
+ sal_Int32 nAltStart = _nWordLen - 1;
+ sal_Int32 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength());
+ sal_Int32 nTxtEnd = nTxtStart;
+ sal_Int32 nAltEnd = nAltStart;
+
+ // The regions between the nStart and nEnd is the
+ // difference between alternative and original string.
+ while( nTxtEnd < aWord.getLength() && nAltEnd < aAlt.getLength() &&
+ aWord[nTxtEnd] != aAlt[nAltEnd] )
+ {
+ ++nTxtEnd;
+ ++nAltEnd;
+ }
+
+ // If a character is added, then we notice it now:
+ if( nAltEnd > nTxtEnd && nAltStart == nAltEnd &&
+ aWord[ nTxtEnd ] == aAlt[nAltEnd] )
+ {
+ ++nAltEnd;
+ ++nTxtStart;
+ ++nTxtEnd;
+ }
+
+ DBG_ASSERT( ( nAltEnd - nAltStart ) == 1, "Alternate: Wrong assumption!" );
+
+ if ( nTxtEnd > nTxtStart )
+ cAlternateReplChar = aAlt[nAltStart];
+ else
+ cAlternateExtraChar = aAlt[nAltStart];
+
+ bHyphenated = true;
+ nBreakPos = nWordStart + nTxtStart;
+ if ( cAlternateReplChar || aAlt.getLength() < aWord.getLength() || !bAltFullRight) // also for "oma-tje", "re-eel"
+ nBreakPos++;
+ }
+ }
+ }
+ }
+ }
+
+ if ( nBreakPos <= pLine->GetStart() )
+ {
+ // No separator in line => Chop!
+ nBreakPos = nMaxBreakPos;
+ // I18N nextCharacters !
+ if ( nBreakPos <= pLine->GetStart() )
+ nBreakPos = pLine->GetStart() + 1; // Otherwise infinite loop!
+ }
+ }
+
+ // the dickey portion is the end portion
+ pLine->SetEnd( nBreakPos );
+
+ sal_Int32 nEndPortion = SplitTextPortion( pParaPortion, nBreakPos, pLine );
+
+ if ( !bCompressBlank && !bHangingPunctuation )
+ {
+ // When justification is not SvxAdjust::Left, it's important to compress
+ // the trailing space even if there is enough room for the space...
+ // Don't check for SvxAdjust::Left, doesn't matter to compress in this case too...
+ assert( nBreakPos > pLine->GetStart() && "ImpBreakLines - BreakPos not expected!" );
+ if ( pNode->GetChar( nBreakPos-1 ) == ' ' )
+ bCompressBlank = true;
+ }
+
+ if ( bCompressBlank || bHangingPunctuation )
+ {
+ TextPortion& rTP = pParaPortion->GetTextPortions()[nEndPortion];
+ DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "BlankRubber: No TextPortion!" );
+ DBG_ASSERT( nBreakPos > pLine->GetStart(), "SplitTextPortion at the beginning of the line?" );
+ sal_Int32 nPosInArray = nBreakPos - 1 - pLine->GetStart();
+ rTP.setWidth( ( nPosInArray && ( rTP.GetLen() > 1 ) ) ? pLine->GetCharPosArray()[ nPosInArray-1 ] : 0 );
+ if (o3tl::make_unsigned(nPosInArray) < pLine->GetCharPosArray().size())
+ {
+ pLine->GetCharPosArray()[ nPosInArray ] = rTP.GetSize().Width();
+ }
+ }
+ else if ( bHyphenated )
+ {
+ // A portion for inserting the separator...
+ TextPortion* pHyphPortion = new TextPortion( 0 );
+ pHyphPortion->SetKind( PortionKind::HYPHENATOR );
+ if ( (cAlternateReplChar || cAlternateExtraChar) && bAltFullRight ) // alternation after the break doesn't supported
+ {
+ TextPortion& rPrev = pParaPortion->GetTextPortions()[nEndPortion];
+ DBG_ASSERT( rPrev.GetLen(), "Hyphenate: Prev portion?!" );
+ rPrev.SetLen( rPrev.GetLen() - nAltDelChar );
+ pHyphPortion->SetLen( nAltDelChar );
+ if (cAlternateReplChar && !bAltFullLeft) pHyphPortion->SetExtraValue( cAlternateReplChar );
+ // Correct width of the portion above:
+ rPrev.setWidth(
+ pLine->GetCharPosArray()[ nBreakPos-1 - pLine->GetStart() - nAltDelChar ] );
+ }
+
+ // Determine the width of the Hyph-Portion:
+ SvxFont aFont;
+ SeekCursor( pParaPortion->GetNode(), nBreakPos, aFont );
+ aFont.SetPhysFont(*GetRefDevice());
+ pHyphPortion->SetSize(Size(GetRefDevice()->GetTextWidth(CH_HYPH), GetRefDevice()->GetTextHeight()));
+
+ pParaPortion->GetTextPortions().Insert(++nEndPortion, pHyphPortion);
+ }
+ pLine->SetEndPortion( nEndPortion );
+}
+
+void ImpEditEngine::ImpAdjustBlocks( ParaPortion* pParaPortion, EditLine* pLine, tools::Long nRemainingSpace )
+{
+ DBG_ASSERT( nRemainingSpace > 0, "AdjustBlocks: Somewhat too little..." );
+ assert( pLine && "AdjustBlocks: Line ?!" );
+ if ( ( nRemainingSpace < 0 ) || pLine->IsEmpty() )
+ return ;
+
+ const sal_Int32 nFirstChar = pLine->GetStart();
+ const sal_Int32 nLastChar = pLine->GetEnd() -1; // Last points behind
+ ContentNode* pNode = pParaPortion->GetNode();
+
+ DBG_ASSERT( nLastChar < pNode->Len(), "AdjustBlocks: Out of range!" );
+
+ // Search blanks or Kashidas...
+ std::vector<sal_Int32> aPositions;
+
+ // Kashidas ?
+ ImpFindKashidas( pNode, nFirstChar, nLastChar, aPositions );
+ auto nKashidas = aPositions.size();
+
+ sal_uInt16 nLastScript = i18n::ScriptType::LATIN;
+ for ( sal_Int32 nChar = nFirstChar; nChar <= nLastChar; nChar++ )
+ {
+ EditPaM aPaM( pNode, nChar+1 );
+ LanguageType eLang = GetLanguage(aPaM).nLang;
+ sal_uInt16 nScript = GetI18NScriptType(aPaM);
+ // Arabic script is handled above, but if no Kashida positions are found, use blanks.
+ if (MsLangId::getPrimaryLanguage(eLang) == LANGUAGE_ARABIC_PRIMARY_ONLY && nKashidas)
+ continue;
+
+ if ( pNode->GetChar(nChar) == ' ' )
+ {
+ // Normal latin script.
+ aPositions.push_back( nChar );
+ }
+ else if (nChar > nFirstChar)
+ {
+ if (nLastScript == i18n::ScriptType::ASIAN)
+ {
+ // Set break position between this and the last character if
+ // the last character is asian script.
+ aPositions.push_back( nChar-1 );
+ }
+ else if (nScript == i18n::ScriptType::ASIAN)
+ {
+ // Set break position between a latin script and asian script.
+ aPositions.push_back( nChar-1 );
+ }
+ }
+
+ nLastScript = nScript;
+ }
+
+ if ( aPositions.empty() )
+ return;
+
+ // If the last character is a blank, it is rejected!
+ // The width must be distributed to the blockers in front...
+ // But not if it is the only one.
+ if ( ( pNode->GetChar( nLastChar ) == ' ' ) && ( aPositions.size() > 1 ) &&
+ ( MsLangId::getPrimaryLanguage( GetLanguage( EditPaM( pNode, nLastChar ) ).nLang ) != LANGUAGE_ARABIC_PRIMARY_ONLY ) )
+ {
+ aPositions.pop_back();
+ sal_Int32 nPortionStart, nPortion;
+ nPortion = pParaPortion->GetTextPortions().FindPortion( nLastChar+1, nPortionStart );
+ TextPortion& rLastPortion = pParaPortion->GetTextPortions()[ nPortion ];
+ tools::Long nRealWidth = pLine->GetCharPosArray()[nLastChar-nFirstChar];
+ tools::Long nBlankWidth = nRealWidth;
+ if ( nLastChar > nPortionStart )
+ nBlankWidth -= pLine->GetCharPosArray()[nLastChar-nFirstChar-1];
+ // Possibly the blank has already been deducted in ImpBreakLine:
+ if ( nRealWidth == rLastPortion.GetSize().Width() )
+ {
+ // For the last character the portion must stop behind the blank
+ // => Simplify correction:
+ DBG_ASSERT( ( nPortionStart + rLastPortion.GetLen() ) == ( nLastChar+1 ), "Blank actually not at the end of the portion!?");
+ rLastPortion.adjustSize(-nBlankWidth, 0);
+ nRemainingSpace += nBlankWidth;
+ }
+ pLine->GetCharPosArray()[nLastChar-nFirstChar] -= nBlankWidth;
+ }
+
+ size_t nGaps = aPositions.size();
+ const tools::Long nMore4Everyone = nRemainingSpace / nGaps;
+ tools::Long nSomeExtraSpace = nRemainingSpace - nMore4Everyone*nGaps;
+
+ DBG_ASSERT( nSomeExtraSpace < static_cast<tools::Long>(nGaps), "AdjustBlocks: ExtraSpace too large" );
+ DBG_ASSERT( nSomeExtraSpace >= 0, "AdjustBlocks: ExtraSpace < 0 " );
+
+ // Mark Kashida positions, so that VCL knows where to insert Kashida and
+ // where to only expand the width.
+ if (nKashidas)
+ {
+ pLine->GetKashidaArray().resize(pLine->GetCharPosArray().size(), false);
+ for (size_t i = 0; i < nKashidas; i++)
+ {
+ auto nChar = aPositions[i];
+ if ( nChar < nLastChar )
+ pLine->GetKashidaArray()[nChar-nFirstChar] = 1 /*sal_True*/;
+ }
+ }
+
+ // Correct the positions in the Array and the portion widths:
+ // Last character won't be considered...
+ for (auto const& nChar : aPositions)
+ {
+ if ( nChar < nLastChar )
+ {
+ sal_Int32 nPortionStart, nPortion;
+ nPortion = pParaPortion->GetTextPortions().FindPortion( nChar, nPortionStart, true );
+ TextPortion& rLastPortion = pParaPortion->GetTextPortions()[ nPortion ];
+
+ // The width of the portion:
+ rLastPortion.adjustSize(nMore4Everyone, 0);
+ if (nSomeExtraSpace)
+ {
+ rLastPortion.adjustSize(1, 0);
+ }
+
+ // Correct positions in array
+ sal_Int32 nPortionEnd = nPortionStart + rLastPortion.GetLen();
+ for ( sal_Int32 _n = nChar; _n < nPortionEnd; _n++ )
+ {
+ pLine->GetCharPosArray()[_n-nFirstChar] += nMore4Everyone;
+ if ( nSomeExtraSpace )
+ pLine->GetCharPosArray()[_n-nFirstChar]++;
+ }
+
+ if ( nSomeExtraSpace )
+ nSomeExtraSpace--;
+ }
+ }
+
+ // Now the text width contains the extra width...
+ pLine->SetTextWidth( pLine->GetTextWidth() + nRemainingSpace );
+}
+
+// For Kashidas from sw/source/core/text/porlay.cxx
+void ImpEditEngine::ImpFindKashidas( ContentNode* pNode, sal_Int32 nStart, sal_Int32 nEnd, std::vector<sal_Int32>& rArray )
+{
+ // Kashida glyph looks suspicious, skip Kashida justification
+ if (GetRefDevice()->GetMinKashida() <= 0)
+ return;
+
+ std::vector<sal_Int32> aKashidaArray;
+
+ // the search has to be performed on a per word base
+
+ EditSelection aWordSel( EditPaM( pNode, nStart ) );
+ aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD );
+ if ( aWordSel.Min().GetIndex() < nStart )
+ aWordSel.Min().SetIndex( nStart );
+
+ while ( ( aWordSel.Min().GetNode() == pNode ) && ( aWordSel.Min().GetIndex() < nEnd ) )
+ {
+ const sal_Int32 nSavPos = aWordSel.Max().GetIndex();
+ if ( aWordSel.Max().GetIndex() > nEnd )
+ aWordSel.Max().SetIndex( nEnd );
+
+ OUString aWord = GetSelected( aWordSel );
+
+ // restore selection for proper iteration at the end of the function
+ aWordSel.Max().SetIndex( nSavPos );
+
+ sal_Int32 nIdx = 0, nPrevIdx = 0;
+ sal_Int32 nKashidaPos = -1;
+ sal_Unicode cCh, cPrevCh = 0;
+
+ int nPriorityLevel = 7; // 0..6 = level found
+ // 7 not found
+
+ sal_Int32 nWordLen = aWord.getLength();
+
+ // ignore trailing vowel chars
+ while( nWordLen && isTransparentChar( aWord[ nWordLen - 1 ] ))
+ --nWordLen;
+
+ while ( nIdx < nWordLen )
+ {
+ cCh = aWord[ nIdx ];
+
+ // 1. Priority:
+ // after user inserted kashida
+ if ( 0x640 == cCh )
+ {
+ nKashidaPos = aWordSel.Min().GetIndex() + nIdx;
+ nPriorityLevel = 0;
+ }
+
+ // 2. Priority:
+ // after a Seen or Sad
+ if (nPriorityLevel >= 1 && nIdx < nWordLen - 1)
+ {
+ if( isSeenOrSadChar( cCh )
+ && (aWord[ nIdx+1 ] != 0x200C) ) // #i98410#: prevent ZWNJ expansion
+ {
+ nKashidaPos = aWordSel.Min().GetIndex() + nIdx;
+ nPriorityLevel = 1;
+ }
+ }
+
+ // 3. Priority:
+ // before final form of Teh Marbuta, Heh, Dal
+ if ( nPriorityLevel >= 2 && nIdx > 0 )
+ {
+ if ( isTehMarbutaChar ( cCh ) || // Teh Marbuta (right joining)
+ isDalChar ( cCh ) || // Dal (right joining) final form may appear in the middle of word
+ ( isHehChar ( cCh ) && nIdx == nWordLen - 1)) // Heh (dual joining) only at end of word
+ {
+
+ SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" );
+ // check if character is connectable to previous character,
+ if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
+ {
+ nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx;
+ nPriorityLevel = 2;
+ }
+ }
+ }
+
+ // 4. Priority:
+ // before final form of Alef, Tah, Lam, Kaf or Gaf
+ if ( nPriorityLevel >= 3 && nIdx > 0 )
+ {
+ if ( isAlefChar ( cCh ) || // Alef (right joining) final form may appear in the middle of word
+ (( isLamChar ( cCh ) || // Lam,
+ isTahChar ( cCh ) || // Tah,
+ isKafChar ( cCh ) || // Kaf (all dual joining)
+ isGafChar ( cCh ) )
+ && nIdx == nWordLen - 1)) // only at end of word
+ {
+ SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" );
+ // check if character is connectable to previous character,
+ if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
+ {
+ nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx;
+ nPriorityLevel = 3;
+ }
+ }
+ }
+
+ // 5. Priority:
+ // before medial Beh-like
+ if ( nPriorityLevel >= 4 && nIdx > 0 && nIdx < nWordLen - 1 )
+ {
+ if ( isBehChar ( cCh ) )
+ {
+ // check if next character is Reh or Yeh-like
+ sal_Unicode cNextCh = aWord[ nIdx + 1 ];
+ if ( isRehChar ( cNextCh ) || isYehChar ( cNextCh ))
+ {
+ SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" );
+ // check if character is connectable to previous character,
+ if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
+ {
+ nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx;
+ nPriorityLevel = 4;
+ }
+ }
+ }
+ }
+
+ // 6. Priority:
+ // before the final form of Waw, Ain, Qaf and Feh
+ if ( nPriorityLevel >= 5 && nIdx > 0 )
+ {
+ if ( isWawChar ( cCh ) || // Wav (right joining)
+ // final form may appear in the middle of word
+ (( isAinChar ( cCh ) || // Ain (dual joining)
+ isQafChar ( cCh ) || // Qaf (dual joining)
+ isFehChar ( cCh ) ) // Feh (dual joining)
+ && nIdx == nWordLen - 1)) // only at end of word
+ {
+ SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" );
+ // check if character is connectable to previous character,
+ if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
+ {
+ nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx;
+ nPriorityLevel = 5;
+ }
+ }
+ }
+
+ // other connecting possibilities
+ if ( nPriorityLevel >= 6 && nIdx > 0 )
+ {
+ // Reh, Zain
+ if ( isRehChar ( cCh ) )
+ {
+ SAL_WARN_IF( 0 == cPrevCh, "editeng", "No previous character" );
+ // check if character is connectable to previous character,
+ if ( lcl_ConnectToPrev( cCh, cPrevCh ) )
+ {
+ nKashidaPos = aWordSel.Min().GetIndex() + nPrevIdx;
+ nPriorityLevel = 6;
+ }
+ }
+ }
+
+ // Do not consider vowel marks when checking if a character
+ // can be connected to previous character.
+ if ( !isTransparentChar ( cCh) )
+ {
+ cPrevCh = cCh;
+ nPrevIdx = nIdx;
+ }
+
+ ++nIdx;
+ } // end of current word
+
+ if ( nKashidaPos>=0 )
+ aKashidaArray.push_back( nKashidaPos );
+
+ aWordSel = WordRight( aWordSel.Max(), css::i18n::WordType::DICTIONARY_WORD );
+ aWordSel = SelectWord( aWordSel, css::i18n::WordType::DICTIONARY_WORD );
+ }
+
+ // Validate
+ std::vector<sal_Int32> aDropped(aKashidaArray.size());
+ auto nOldLayout = GetRefDevice()->GetLayoutMode();
+ GetRefDevice()->SetLayoutMode(nOldLayout | vcl::text::ComplexTextLayoutFlags::BiDiRtl);
+ GetRefDevice()->ValidateKashidas(pNode->GetString(), nStart, nEnd - nStart,
+ aKashidaArray.size(), aKashidaArray.data(), aDropped.data());
+ GetRefDevice()->SetLayoutMode(nOldLayout);
+
+ for (auto const& pos : aKashidaArray)
+ if (std::find(aDropped.begin(), aDropped.end(), pos) == aDropped.end())
+ rArray.push_back(pos);
+}
+
+sal_Int32 ImpEditEngine::SplitTextPortion( ParaPortion* pPortion, sal_Int32 nPos, EditLine* pCurLine )
+{
+ // The portion at nPos is split, if there is not a transition at nPos anyway
+ if ( nPos == 0 )
+ return 0;
+
+ assert( pPortion && "SplitTextPortion: Which ?" );
+
+ sal_Int32 nSplitPortion;
+ sal_Int32 nTmpPos = 0;
+ TextPortion* pTextPortion = nullptr;
+ sal_Int32 nPortions = pPortion->GetTextPortions().Count();
+ for ( nSplitPortion = 0; nSplitPortion < nPortions; nSplitPortion++ )
+ {
+ TextPortion& rTP = pPortion->GetTextPortions()[nSplitPortion];
+ nTmpPos = nTmpPos + rTP.GetLen();
+ if ( nTmpPos >= nPos )
+ {
+ if ( nTmpPos == nPos ) // then nothing needs to be split
+ {
+ return nSplitPortion;
+ }
+ pTextPortion = &rTP;
+ break;
+ }
+ }
+
+ DBG_ASSERT( pTextPortion, "Position outside the area!" );
+
+ if (!pTextPortion)
+ return 0;
+
+ DBG_ASSERT( pTextPortion->GetKind() == PortionKind::TEXT, "SplitTextPortion: No TextPortion!" );
+
+ sal_Int32 nOverlapp = nTmpPos - nPos;
+ pTextPortion->SetLen( pTextPortion->GetLen() - nOverlapp );
+ TextPortion* pNewPortion = new TextPortion( nOverlapp );
+ pPortion->GetTextPortions().Insert(nSplitPortion+1, pNewPortion);
+ // Set sizes
+ if ( pCurLine )
+ {
+ // No new GetTextSize, instead use values from the Array:
+ assert( nPos > pCurLine->GetStart() && "SplitTextPortion at the beginning of the line?" );
+ pTextPortion->setWidth(pCurLine->GetCharPosArray()[nPos - pCurLine->GetStart() - 1]);
+
+ if ( pTextPortion->GetExtraInfos() && pTextPortion->GetExtraInfos()->bCompressed )
+ {
+ // We need the original size from the portion
+ sal_Int32 nTxtPortionStart = pPortion->GetTextPortions().GetStartPos( nSplitPortion );
+ SvxFont aTmpFont( pPortion->GetNode()->GetCharAttribs().GetDefFont() );
+ SeekCursor( pPortion->GetNode(), nTxtPortionStart+1, aTmpFont );
+ aTmpFont.SetPhysFont(*GetRefDevice());
+ GetRefDevice()->Push( vcl::PushFlags::TEXTLANGUAGE );
+ ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());
+ Size aSz = aTmpFont.QuickGetTextSize( GetRefDevice(), pPortion->GetNode()->GetString(),
+ nTxtPortionStart, pTextPortion->GetLen(), nullptr );
+ GetRefDevice()->Pop();
+ pTextPortion->GetExtraInfos()->nOrgWidth = aSz.Width();
+ }
+ }
+ else
+ pTextPortion->setWidth(-1);
+
+ return nSplitPortion;
+}
+
+void ImpEditEngine::CreateTextPortions( ParaPortion* pParaPortion, sal_Int32& rStart )
+{
+ sal_Int32 nStartPos = rStart;
+ ContentNode* pNode = pParaPortion->GetNode();
+ DBG_ASSERT( pNode->Len(), "CreateTextPortions should not be used for empty paragraphs!" );
+
+ o3tl::sorted_vector< sal_Int32 > aPositions;
+ aPositions.insert( 0 );
+
+ for (std::size_t nAttr = 0;; ++nAttr)
+ {
+ // Insert Start and End into the Array...
+ // The Insert method does not allow for duplicate values...
+ EditCharAttrib* pAttrib = GetAttrib(pNode->GetCharAttribs().GetAttribs(), nAttr);
+ if (!pAttrib)
+ break;
+ aPositions.insert( pAttrib->GetStart() );
+ aPositions.insert( pAttrib->GetEnd() );
+ }
+ aPositions.insert( pNode->Len() );
+
+ if ( pParaPortion->aScriptInfos.empty() )
+ InitScriptTypes( GetParaPortions().GetPos( pParaPortion ) );
+
+ const ScriptTypePosInfos& rTypes = pParaPortion->aScriptInfos;
+ for (const ScriptTypePosInfo& rType : rTypes)
+ aPositions.insert( rType.nStartPos );
+
+ const WritingDirectionInfos& rWritingDirections = pParaPortion->aWritingDirectionInfos;
+ for (const WritingDirectionInfo & rWritingDirection : rWritingDirections)
+ aPositions.insert( rWritingDirection.nStartPos );
+
+ if ( mpIMEInfos && mpIMEInfos->nLen && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetNode() == pNode ) )
+ {
+ ExtTextInputAttr nLastAttr = ExtTextInputAttr(0xFFFF);
+ for( sal_Int32 n = 0; n < mpIMEInfos->nLen; n++ )
+ {
+ if ( mpIMEInfos->pAttribs[n] != nLastAttr )
+ {
+ aPositions.insert( mpIMEInfos->aPos.GetIndex() + n );
+ nLastAttr = mpIMEInfos->pAttribs[n];
+ }
+ }
+ aPositions.insert( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen );
+ }
+
+ // From ... Delete:
+ // Unfortunately, the number of text portions does not have to match
+ // aPositions.Count(), since there might be line breaks...
+ sal_Int32 nPortionStart = 0;
+ sal_Int32 nInvPortion = 0;
+ sal_Int32 nP;
+ for ( nP = 0; nP < pParaPortion->GetTextPortions().Count(); nP++ )
+ {
+ const TextPortion& rTmpPortion = pParaPortion->GetTextPortions()[nP];
+ nPortionStart = nPortionStart + rTmpPortion.GetLen();
+ if ( nPortionStart >= nStartPos )
+ {
+ nPortionStart = nPortionStart - rTmpPortion.GetLen();
+ rStart = nPortionStart;
+ nInvPortion = nP;
+ break;
+ }
+ }
+ DBG_ASSERT( nP < pParaPortion->GetTextPortions().Count() || !pParaPortion->GetTextPortions().Count(), "Nothing to delete: CreateTextPortions" );
+ if ( nInvPortion && ( nPortionStart+pParaPortion->GetTextPortions()[nInvPortion].GetLen() > nStartPos ) )
+ {
+ // prefer one in front...
+ // But only if it was in the middle of the portion of, otherwise it
+ // might be the only one in the row in front!
+ nInvPortion--;
+ nPortionStart = nPortionStart - pParaPortion->GetTextPortions()[nInvPortion].GetLen();
+ }
+ pParaPortion->GetTextPortions().DeleteFromPortion( nInvPortion );
+
+ // A portion may also have been formed by a line break:
+ aPositions.insert( nPortionStart );
+
+ auto nInvPos = aPositions.find( nPortionStart );
+ DBG_ASSERT( (nInvPos != aPositions.end()), "InvPos ?!" );
+
+ auto i = nInvPos;
+ ++i;
+ while ( i != aPositions.end() )
+ {
+ TextPortion* pNew = new TextPortion( (*i++) - *nInvPos++ );
+ pParaPortion->GetTextPortions().Append(pNew);
+ }
+
+ DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No Portions?!" );
+#if OSL_DEBUG_LEVEL > 0
+ OSL_ENSURE( ParaPortion::DbgCheckTextPortions(*pParaPortion), "Portion is broken?" );
+#endif
+}
+
+void ImpEditEngine::RecalcTextPortion( ParaPortion* pParaPortion, sal_Int32 nStartPos, sal_Int32 nNewChars )
+{
+ DBG_ASSERT( pParaPortion->GetTextPortions().Count(), "No Portions!" );
+ DBG_ASSERT( nNewChars, "RecalcTextPortion with Diff == 0" );
+
+ ContentNode* const pNode = pParaPortion->GetNode();
+ if ( nNewChars > 0 )
+ {
+ // If an Attribute begins/ends at nStartPos, then a new portion starts
+ // otherwise the portion is extended at nStartPos.
+ if ( pNode->GetCharAttribs().HasBoundingAttrib( nStartPos ) || IsScriptChange( EditPaM( pNode, nStartPos ) ) )
+ {
+ sal_Int32 nNewPortionPos = 0;
+ if ( nStartPos )
+ nNewPortionPos = SplitTextPortion( pParaPortion, nStartPos ) + 1;
+
+ // A blank portion may be here, if the paragraph was empty,
+ // or if a line was created by a hard line break.
+ if ( ( nNewPortionPos < pParaPortion->GetTextPortions().Count() ) &&
+ !pParaPortion->GetTextPortions()[nNewPortionPos].GetLen() )
+ {
+ TextPortion& rTP = pParaPortion->GetTextPortions()[nNewPortionPos];
+ DBG_ASSERT( rTP.GetKind() == PortionKind::TEXT, "the empty portion was no TextPortion!" );
+ rTP.SetLen( rTP.GetLen() + nNewChars );
+ }
+ else
+ {
+ TextPortion* pNewPortion = new TextPortion( nNewChars );
+ pParaPortion->GetTextPortions().Insert(nNewPortionPos, pNewPortion);
+ }
+ }
+ else
+ {
+ sal_Int32 nPortionStart;
+ const sal_Int32 nTP = pParaPortion->GetTextPortions().
+ FindPortion( nStartPos, nPortionStart );
+ TextPortion& rTP = pParaPortion->GetTextPortions()[ nTP ];
+ rTP.SetLen( rTP.GetLen() + nNewChars );
+ rTP.setWidth(-1);
+ }
+ }
+ else
+ {
+ // Shrink or remove portion if necessary.
+ // Before calling this method it must be ensured that no portions were
+ // in the deleted area!
+
+ // There must be no portions extending into the area or portions starting in
+ // the area, so it must be:
+ // nStartPos <= nPos <= nStartPos - nNewChars(neg.)
+ sal_Int32 nPortion = 0;
+ sal_Int32 nPos = 0;
+ sal_Int32 nEnd = nStartPos-nNewChars;
+ sal_Int32 nPortions = pParaPortion->GetTextPortions().Count();
+ TextPortion* pTP = nullptr;
+ for ( nPortion = 0; nPortion < nPortions; nPortion++ )
+ {
+ pTP = &pParaPortion->GetTextPortions()[ nPortion ];
+ if ( ( nPos+pTP->GetLen() ) > nStartPos )
+ {
+ DBG_ASSERT( nPos <= nStartPos, "Wrong Start!" );
+ DBG_ASSERT( nPos+pTP->GetLen() >= nEnd, "Wrong End!" );
+ break;
+ }
+ nPos = nPos + pTP->GetLen();
+ }
+ assert( pTP && "RecalcTextPortion: Portion not found" );
+ if ( ( nPos == nStartPos ) && ( (nPos+pTP->GetLen()) == nEnd ) )
+ {
+ // Remove portion;
+ PortionKind nType = pTP->GetKind();
+ pParaPortion->GetTextPortions().Remove( nPortion );
+ if ( nType == PortionKind::LINEBREAK )
+ {
+ TextPortion& rNext = pParaPortion->GetTextPortions()[ nPortion ];
+ if ( !rNext.GetLen() )
+ {
+ // Remove dummy portion
+ pParaPortion->GetTextPortions().Remove( nPortion );
+ }
+ }
+ }
+ else
+ {
+ DBG_ASSERT( pTP->GetLen() > (-nNewChars), "Portion too small to shrink! ");
+ pTP->SetLen( pTP->GetLen() + nNewChars );
+ }
+
+ sal_Int32 nPortionCount = pParaPortion->GetTextPortions().Count();
+ assert( nPortionCount );
+ if (nPortionCount)
+ {
+ // No HYPHENATOR portion is allowed to get stuck right at the end...
+ sal_Int32 nLastPortion = nPortionCount - 1;
+ pTP = &pParaPortion->GetTextPortions()[nLastPortion];
+ if ( pTP->GetKind() == PortionKind::HYPHENATOR )
+ {
+ // Discard portion; if possible, correct the ones before,
+ // if the Hyphenator portion has swallowed one character...
+ if ( nLastPortion && pTP->GetLen() )
+ {
+ TextPortion& rPrev = pParaPortion->GetTextPortions()[nLastPortion - 1];
+ DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" );
+ rPrev.SetLen( rPrev.GetLen() + pTP->GetLen() );
+ rPrev.setWidth(-1);
+ }
+ pParaPortion->GetTextPortions().Remove( nLastPortion );
+ }
+ }
+ }
+#if OSL_DEBUG_LEVEL > 0
+ OSL_ENSURE( ParaPortion::DbgCheckTextPortions(*pParaPortion), "Portions are broken?" );
+#endif
+}
+
+void ImpEditEngine::SetTextRanger( std::unique_ptr<TextRanger> pRanger )
+{
+ pTextRanger = std::move(pRanger);
+
+ for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ )
+ {
+ ParaPortion* pParaPortion = GetParaPortions()[nPara];
+ pParaPortion->MarkSelectionInvalid( 0 );
+ pParaPortion->GetLines().Reset();
+ }
+
+ FormatFullDoc();
+ UpdateViews( GetActiveView() );
+ if ( IsUpdateLayout() && GetActiveView() )
+ pActiveView->ShowCursor(false, false);
+}
+
+void ImpEditEngine::SetVertical( bool bVertical)
+{
+ if ( IsEffectivelyVertical() != bVertical)
+ {
+ GetEditDoc().SetVertical(bVertical);
+ bool bUseCharAttribs = bool(maStatus.GetControlWord() & EEControlBits::USECHARATTRIBS);
+ GetEditDoc().CreateDefFont( bUseCharAttribs );
+ if ( IsFormatted() )
+ {
+ FormatFullDoc();
+ UpdateViews( GetActiveView() );
+ }
+ }
+}
+
+void ImpEditEngine::SetRotation(TextRotation nRotation)
+{
+ if (GetEditDoc().GetRotation() == nRotation)
+ return; // not modified
+ GetEditDoc().SetRotation(nRotation);
+ bool bUseCharAttribs = bool(maStatus.GetControlWord() & EEControlBits::USECHARATTRIBS);
+ GetEditDoc().CreateDefFont( bUseCharAttribs );
+ if ( IsFormatted() )
+ {
+ FormatFullDoc();
+ UpdateViews( GetActiveView() );
+ }
+}
+
+void ImpEditEngine::SetTextColumns(sal_Int16 nColumns, sal_Int32 nSpacing)
+{
+ assert(nColumns >= 1);
+ if (mnColumns != nColumns || mnColumnSpacing != nSpacing)
+ {
+ if (nColumns == 0)
+ {
+ SAL_WARN("editeng", "bad nColumns value, ignoring");
+ nColumns = 1;
+ }
+ mnColumns = nColumns;
+ mnColumnSpacing = nSpacing;
+ if (IsFormatted())
+ {
+ FormatFullDoc();
+ UpdateViews(GetActiveView());
+ }
+ }
+}
+
+void ImpEditEngine::SetFixedCellHeight( bool bUseFixedCellHeight )
+{
+ if ( IsFixedCellHeight() != bUseFixedCellHeight )
+ {
+ GetEditDoc().SetFixedCellHeight( bUseFixedCellHeight );
+ if ( IsFormatted() )
+ {
+ FormatFullDoc();
+ UpdateViews( GetActiveView() );
+ }
+ }
+}
+
+void ImpEditEngine::SeekCursor( ContentNode* pNode, sal_Int32 nPos, SvxFont& rFont, OutputDevice* pOut )
+{
+ // It was planned, SeekCursor( nStartPos, nEndPos,... ), so that it would
+ // only be searched anew at the StartPosition.
+ // Problem: There would be two lists to consider/handle:
+ // OrderedByStart,OrderedByEnd.
+
+ if ( nPos > pNode->Len() )
+ nPos = pNode->Len();
+
+ rFont = pNode->GetCharAttribs().GetDefFont();
+
+ /*
+ * Set attributes for script types Asian and Complex
+ */
+ short nScriptTypeI18N = GetI18NScriptType( EditPaM( pNode, nPos ) );
+ SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N);
+ if ( ( nScriptTypeI18N == i18n::ScriptType::ASIAN ) || ( nScriptTypeI18N == i18n::ScriptType::COMPLEX ) )
+ {
+ const SvxFontItem& rFontItem = static_cast<const SvxFontItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_FONTINFO, nScriptType ) ));
+ rFont.SetFamilyName( rFontItem.GetFamilyName() );
+ rFont.SetFamily( rFontItem.GetFamily() );
+ rFont.SetPitch( rFontItem.GetPitch() );
+ rFont.SetCharSet( rFontItem.GetCharSet() );
+ Size aSz( rFont.GetFontSize() );
+ aSz.setHeight( static_cast<const SvxFontHeightItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType ) ) ).GetHeight() );
+ rFont.SetFontSize( aSz );
+ rFont.SetWeight( static_cast<const SvxWeightItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_WEIGHT, nScriptType ))).GetWeight() );
+ rFont.SetItalic( static_cast<const SvxPostureItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_ITALIC, nScriptType ))).GetPosture() );
+ rFont.SetLanguage( static_cast<const SvxLanguageItem&>(pNode->GetContentAttribs().GetItem( GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ))).GetLanguage() );
+ }
+
+ sal_uInt16 nRelWidth = pNode->GetContentAttribs().GetItem( EE_CHAR_FONTWIDTH).GetValue();
+
+ /*
+ * Set output device's line and overline colors
+ */
+ if ( pOut )
+ {
+ const SvxUnderlineItem& rTextLineColor = pNode->GetContentAttribs().GetItem( EE_CHAR_UNDERLINE );
+ if ( rTextLineColor.GetColor() != COL_TRANSPARENT )
+ pOut->SetTextLineColor( rTextLineColor.GetColor() );
+ else
+ pOut->SetTextLineColor();
+
+ const SvxOverlineItem& rOverlineColor = pNode->GetContentAttribs().GetItem( EE_CHAR_OVERLINE );
+ if ( rOverlineColor.GetColor() != COL_TRANSPARENT )
+ pOut->SetOverlineColor( rOverlineColor.GetColor() );
+ else
+ pOut->SetOverlineColor();
+ }
+
+ const SvxLanguageItem* pCJKLanguageItem = nullptr;
+
+ /*
+ * Scan through char attributes of pNode
+ */
+ if (maStatus.UseCharAttribs())
+ {
+ CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs();
+ size_t nAttr = 0;
+ EditCharAttrib* pAttrib = GetAttrib(rAttribs, nAttr);
+ while ( pAttrib && ( pAttrib->GetStart() <= nPos ) )
+ {
+ // when seeking, ignore attributes which start there! Empty attributes
+ // are considered (used) as these are just set. But do not use empty
+ // attributes: When just set and empty => no effect on font
+ // In a blank paragraph, set characters take effect immediately.
+ if ( ( pAttrib->Which() != 0 ) &&
+ ( ( ( pAttrib->GetStart() < nPos ) && ( pAttrib->GetEnd() >= nPos ) )
+ || ( !pNode->Len() ) ) )
+ {
+ DBG_ASSERT( ( pAttrib->Which() >= EE_CHAR_START ) && ( pAttrib->Which() <= EE_FEATURE_END ), "Invalid Attribute in Seek() " );
+ if ( IsScriptItemValid( pAttrib->Which(), nScriptTypeI18N ) )
+ {
+ pAttrib->SetFont( rFont, pOut );
+ // #i1550# hard color attrib should win over text color from field
+ if ( pAttrib->Which() == EE_FEATURE_FIELD )
+ {
+ EditCharAttrib* pColorAttr = pNode->GetCharAttribs().FindAttrib( EE_CHAR_COLOR, nPos );
+ if ( pColorAttr )
+ pColorAttr->SetFont( rFont, pOut );
+ }
+ }
+ if ( pAttrib->Which() == EE_CHAR_FONTWIDTH )
+ nRelWidth = static_cast<const SvxCharScaleWidthItem*>(pAttrib->GetItem())->GetValue();
+ if ( pAttrib->Which() == EE_CHAR_LANGUAGE_CJK )
+ pCJKLanguageItem = static_cast<const SvxLanguageItem*>( pAttrib->GetItem() );
+ }
+ pAttrib = GetAttrib( rAttribs, ++nAttr );
+ }
+ }
+
+ if ( !pCJKLanguageItem )
+ pCJKLanguageItem = &pNode->GetContentAttribs().GetItem( EE_CHAR_LANGUAGE_CJK );
+
+ rFont.SetCJKContextLanguage( pCJKLanguageItem->GetLanguage() );
+
+ if ( (rFont.GetKerning() != FontKerning::NONE) && IsKernAsianPunctuation() && ( nScriptTypeI18N == i18n::ScriptType::ASIAN ) )
+ rFont.SetKerning( rFont.GetKerning() | FontKerning::Asian );
+
+ if (maStatus.DoNotUseColors())
+ {
+ rFont.SetColor( /* rColorItem.GetValue() */ COL_BLACK );
+ }
+
+ if (maStatus.DoStretch() || ( nRelWidth != 100 ))
+ {
+ // For the current Output device, because otherwise if RefDev=Printer its looks
+ // ugly on the screen!
+ OutputDevice* pDev = pOut ? pOut : GetRefDevice();
+ rFont.SetPhysFont(*pDev);
+ FontMetric aMetric( pDev->GetFontMetric() );
+
+ // before forcing nPropr to 100%, calculate a new escapement relative to this fake size.
+ sal_uInt8 nPropr = rFont.GetPropr();
+ sal_Int16 nEsc = rFont.GetEscapement();
+ if ( nPropr && nEsc && nPropr != 100 && abs(nEsc) != DFLT_ESC_AUTO_SUPER )
+ rFont.SetEscapement( 100.0/nPropr * nEsc );
+
+ // Set the font as we want it to look like & reset the Propr attribute
+ // so that it is not counted twice.
+ Size aRealSz( aMetric.GetFontSize() );
+ rFont.SetPropr( 100 );
+
+ if (maStatus.DoStretch())
+ {
+ if (mfFontScaleY != 100.0)
+ {
+ double fHeightRounded = roundToNearestPt(aRealSz.Height());
+ double fNewHeight = fHeightRounded * (mfFontScaleY / 100.0);
+ fNewHeight = roundToNearestPt(fNewHeight);
+ aRealSz.setHeight(basegfx::fround(fNewHeight));
+ }
+ if (mfFontScaleX != 100.0)
+ {
+ if (mfFontScaleX == mfFontScaleY && nRelWidth == 100 )
+ {
+ aRealSz.setWidth( 0 );
+ }
+ else
+ {
+ double fWidthRounded = roundToNearestPt(aRealSz.Width());
+ double fNewWidth = fWidthRounded * (mfFontScaleX / 100.0);
+ fNewWidth = roundToNearestPt(fNewWidth);
+ aRealSz.setWidth(basegfx::fround(fNewWidth));
+
+ // Also the Kerning: (long due to handle Interim results)
+ tools::Long nKerning = rFont.GetFixKerning();
+/*
+ The consideration was: If negative kerning, but StretchX = 200
+ => Do not double the kerning, thus pull the letters closer together
+ ---------------------------
+ Kern StretchX =>Kern
+ ---------------------------
+ >0 <100 < (Proportional)
+ <0 <100 < (Proportional)
+ >0 >100 > (Proportional)
+ <0 >100 < (The amount, thus disproportional)
+*/
+ if (nKerning < 0 && mfFontScaleX > 100.0)
+ {
+ // disproportional
+ nKerning = basegfx::fround((double(nKerning) * 100.0) / mfFontScaleX);
+ }
+ else if ( nKerning )
+ {
+ // Proportional
+ nKerning = basegfx::fround((double(nKerning) * mfFontScaleX) / 100.0);
+ }
+ rFont.SetFixKerning( static_cast<short>(nKerning) );
+ }
+ }
+ }
+ if ( nRelWidth != 100 )
+ {
+ aRealSz.setWidth( aRealSz.Width() * nRelWidth );
+ aRealSz.setWidth( aRealSz.Width() / 100 );
+ }
+ rFont.SetFontSize( aRealSz );
+ // Font is not restored...
+ }
+
+ if ( ( ( rFont.GetColor() == COL_AUTO ) || ( IsForceAutoColor() ) ) && pOut )
+ {
+ // #i75566# Do not use AutoColor when printing OR Pdf export
+ const bool bPrinting(OUTDEV_PRINTER == pOut->GetOutDevType());
+ const bool bPDFExporting(OUTDEV_PDF == pOut->GetOutDevType());
+
+ if ( IsAutoColorEnabled() && !bPrinting && !bPDFExporting)
+ {
+ // Never use WindowTextColor on the printer
+ rFont.SetColor( GetAutoColor() );
+ }
+ else
+ {
+ if ( ( GetBackgroundColor() != COL_AUTO ) && GetBackgroundColor().IsDark() )
+ rFont.SetColor( COL_WHITE );
+ else
+ rFont.SetColor( COL_BLACK );
+ }
+ }
+
+ if ( !(mpIMEInfos && mpIMEInfos->pAttribs && ( mpIMEInfos->aPos.GetNode() == pNode ) &&
+ ( nPos > mpIMEInfos->aPos.GetIndex() ) && ( nPos <= ( mpIMEInfos->aPos.GetIndex() + mpIMEInfos->nLen ) )) )
+ return;
+
+ ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[ nPos - mpIMEInfos->aPos.GetIndex() - 1 ];
+ if ( nAttr & ExtTextInputAttr::Underline )
+ rFont.SetUnderline( LINESTYLE_SINGLE );
+ else if ( nAttr & ExtTextInputAttr::DoubleUnderline )
+ rFont.SetUnderline( LINESTYLE_DOUBLE );
+ else if ( nAttr & ExtTextInputAttr::BoldUnderline )
+ rFont.SetUnderline( LINESTYLE_BOLD );
+ else if ( nAttr & ExtTextInputAttr::DottedUnderline )
+ rFont.SetUnderline( LINESTYLE_DOTTED );
+ else if ( nAttr & ExtTextInputAttr::DashDotUnderline )
+ rFont.SetUnderline( LINESTYLE_DOTTED );
+ else if ( nAttr & ExtTextInputAttr::RedText )
+ rFont.SetColor( COL_RED );
+ else if ( nAttr & ExtTextInputAttr::HalfToneText )
+ rFont.SetColor( COL_LIGHTGRAY );
+ if ( nAttr & ExtTextInputAttr::Highlight )
+ {
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ rFont.SetColor( rStyleSettings.GetHighlightTextColor() );
+ rFont.SetFillColor( rStyleSettings.GetHighlightColor() );
+ rFont.SetTransparent( false );
+ }
+ else if ( nAttr & ExtTextInputAttr::GrayWaveline )
+ {
+ rFont.SetUnderline( LINESTYLE_WAVE );
+ if( pOut )
+ pOut->SetTextLineColor( COL_LIGHTGRAY );
+ }
+}
+
+void ImpEditEngine::RecalcFormatterFontMetrics( FormatterFontMetric& rCurMetrics, SvxFont& rFont )
+{
+ // for line height at high / low first without Propr!
+ sal_uInt16 nPropr = rFont.GetPropr();
+ DBG_ASSERT( ( nPropr == 100 ) || rFont.GetEscapement(), "Propr without Escape?!" );
+ if ( nPropr != 100 )
+ {
+ rFont.SetPropr( 100 );
+ rFont.SetPhysFont(*pRefDev);
+ }
+ sal_uInt16 nAscent, nDescent;
+
+ FontMetric aMetric( pRefDev->GetFontMetric() );
+ nAscent = static_cast<sal_uInt16>(aMetric.GetAscent());
+ if ( IsAddExtLeading() )
+ nAscent = sal::static_int_cast< sal_uInt16 >(
+ nAscent + aMetric.GetExternalLeading() );
+ nDescent = static_cast<sal_uInt16>(aMetric.GetDescent());
+
+ if ( IsFixedCellHeight() )
+ {
+ nAscent = sal::static_int_cast< sal_uInt16 >( rFont.GetFontHeight() );
+ nDescent= sal::static_int_cast< sal_uInt16 >( ImplCalculateFontIndependentLineSpacing( rFont.GetFontHeight() ) - nAscent );
+ }
+ else
+ {
+ sal_uInt16 nIntLeading = ( aMetric.GetInternalLeading() > 0 ) ? static_cast<sal_uInt16>(aMetric.GetInternalLeading()) : 0;
+ // Fonts without leading cause problems
+ if ( ( nIntLeading == 0 ) && ( pRefDev->GetOutDevType() == OUTDEV_PRINTER ) )
+ {
+ // Lets see what Leading one gets on the screen
+ VclPtr<VirtualDevice> pVDev = GetVirtualDevice( pRefDev->GetMapMode(), pRefDev->GetDrawMode() );
+ rFont.SetPhysFont(*pVDev);
+ aMetric = pVDev->GetFontMetric();
+
+ // This is so that the Leading does not count itself out again,
+ // if the whole line has the font, nTmpLeading.
+ nAscent = static_cast<sal_uInt16>(aMetric.GetAscent());
+ nDescent = static_cast<sal_uInt16>(aMetric.GetDescent());
+ }
+ }
+ if ( nAscent > rCurMetrics.nMaxAscent )
+ rCurMetrics.nMaxAscent = nAscent;
+ if ( nDescent > rCurMetrics.nMaxDescent )
+ rCurMetrics.nMaxDescent= nDescent;
+ // Special treatment of high/low:
+ if ( !rFont.GetEscapement() )
+ return;
+
+ // Now in consideration of Escape/Propr
+ // possibly enlarge Ascent or Descent
+ short nDiff = static_cast<short>(rFont.GetFontSize().Height()*rFont.GetEscapement()/100);
+ if ( rFont.GetEscapement() > 0 )
+ {
+ nAscent = static_cast<sal_uInt16>(static_cast<tools::Long>(nAscent)*nPropr/100 + nDiff);
+ if ( nAscent > rCurMetrics.nMaxAscent )
+ rCurMetrics.nMaxAscent = nAscent;
+ }
+ else // has to be < 0
+ {
+ nDescent = static_cast<sal_uInt16>(static_cast<tools::Long>(nDescent)*nPropr/100 - nDiff);
+ if ( nDescent > rCurMetrics.nMaxDescent )
+ rCurMetrics.nMaxDescent= nDescent;
+ }
+}
+
+tools::Long ImpEditEngine::getWidthDirectionAware(const Size& sz) const
+{
+ return !IsEffectivelyVertical() ? sz.Width() : sz.Height();
+}
+
+tools::Long ImpEditEngine::getHeightDirectionAware(const Size& sz) const
+{
+ return !IsEffectivelyVertical() ? sz.Height() : sz.Width();
+}
+
+void ImpEditEngine::adjustXDirectionAware(Point& pt, tools::Long x) const
+{
+ if (!IsEffectivelyVertical())
+ pt.AdjustX(x);
+ else
+ pt.AdjustY(IsTopToBottom() ? x : -x);
+}
+
+void ImpEditEngine::adjustYDirectionAware(Point& pt, tools::Long y) const
+{
+ if (!IsEffectivelyVertical())
+ pt.AdjustY(y);
+ else
+ pt.AdjustX(IsTopToBottom() ? -y : y);
+}
+
+void ImpEditEngine::setXDirectionAwareFrom(Point& ptDest, const Point& ptSrc) const
+{
+ if (!IsEffectivelyVertical())
+ ptDest.setX(ptSrc.X());
+ else
+ ptDest.setY(ptSrc.Y());
+}
+
+void ImpEditEngine::setYDirectionAwareFrom(Point& ptDest, const Point& ptSrc) const
+{
+ if (!IsEffectivelyVertical())
+ ptDest.setY(ptSrc.Y());
+ else
+ ptDest.setX(ptSrc.Y());
+}
+
+tools::Long ImpEditEngine::getYOverflowDirectionAware(const Point& pt,
+ const tools::Rectangle& rectMax) const
+{
+ tools::Long nRes;
+ if (!IsEffectivelyVertical())
+ nRes = pt.Y() - rectMax.Bottom();
+ else if (IsTopToBottom())
+ nRes = rectMax.Left() - pt.X();
+ else
+ nRes = pt.X() - rectMax.Right();
+ return std::max(nRes, tools::Long(0));
+}
+
+bool ImpEditEngine::isXOverflowDirectionAware(const Point& pt, const tools::Rectangle& rectMax) const
+{
+ if (!IsEffectivelyVertical())
+ return pt.X() > rectMax.Right();
+
+ if (IsTopToBottom())
+ return pt.Y() > rectMax.Bottom();
+ else
+ return pt.Y() < rectMax.Top();
+}
+
+tools::Long ImpEditEngine::getBottomDocOffset(const tools::Rectangle& rect) const
+{
+ if (!IsEffectivelyVertical())
+ return rect.Bottom();
+
+ if (IsTopToBottom())
+ return -rect.Left();
+ else
+ return rect.Right();
+}
+
+Size ImpEditEngine::getTopLeftDocOffset(const tools::Rectangle& rect) const
+{
+ if (!IsEffectivelyVertical())
+ return { rect.Left(), rect.Top() };
+
+ if (IsTopToBottom())
+ return { rect.Top(), -rect.Right() };
+ else
+ return { -rect.Bottom(), rect.Left() };
+}
+
+// Returns the resulting shift for the point; allows to apply the same shift to other points
+Point ImpEditEngine::MoveToNextLine(
+ Point& rMovePos, // [in, out] Point that will move to the next line
+ tools::Long nLineHeight, // [in] Y-direction move distance (direction-aware)
+ sal_Int16& rColumn, // [in, out] current column number
+ Point aOrigin, // [in] Origin point to calculate limits and initial Y position in a new column
+ tools::Long* pnHeightNeededToNotWrap // On column wrap, returns how much more height is needed
+) const
+{
+ const Point aOld = rMovePos;
+
+ // Move the point by the requested distance in Y direction
+ adjustYDirectionAware(rMovePos, nLineHeight);
+ // Check if the resulting position has moved beyond the limits, and more columns left.
+ // The limits are defined by a rectangle starting from aOrigin with width of maPaperSize
+ // and height of nCurTextHeight
+ Point aOtherCorner = aOrigin;
+ adjustXDirectionAware(aOtherCorner, getWidthDirectionAware(maPaperSize));
+ adjustYDirectionAware(aOtherCorner, nCurTextHeight);
+ tools::Long nNeeded
+ = getYOverflowDirectionAware(rMovePos, tools::Rectangle::Normalize(aOrigin, aOtherCorner));
+ if (pnHeightNeededToNotWrap)
+ *pnHeightNeededToNotWrap = nNeeded;
+ if (nNeeded && rColumn < mnColumns)
+ {
+ ++rColumn;
+ // If we didn't fit into the last column, indicate that only by setting the column number
+ // to the total number of columns; do not adjust
+ if (rColumn < mnColumns)
+ {
+ // Set Y position of the point to that of aOrigin
+ setYDirectionAwareFrom(rMovePos, aOrigin);
+ // Move the point by the requested distance in Y direction
+ adjustYDirectionAware(rMovePos, nLineHeight);
+ // Move the point by the column+spacing distance in X direction
+ adjustXDirectionAware(rMovePos, GetColumnWidth(maPaperSize) + mnColumnSpacing);
+ }
+ }
+
+ return rMovePos - aOld;
+}
+
+// TODO: use IterateLineAreas in ImpEditEngine::Paint, to avoid algorithm duplication
+
+void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Point aStartPos, bool bStripOnly, Degree10 nOrientation )
+{
+ if ( !IsUpdateLayout() && !bStripOnly )
+ return;
+
+ if ( !IsFormatted() )
+ FormatDoc();
+
+ tools::Long nFirstVisXPos = - rOutDev.GetMapMode().GetOrigin().X();
+ tools::Long nFirstVisYPos = - rOutDev.GetMapMode().GetOrigin().Y();
+
+ DBG_ASSERT( GetParaPortions().Count(), "No ParaPortion?!" );
+ SvxFont aTmpFont( GetParaPortions()[0]->GetNode()->GetCharAttribs().GetDefFont() );
+ vcl::PDFExtOutDevData* const pPDFExtOutDevData = dynamic_cast< vcl::PDFExtOutDevData* >( rOutDev.GetExtOutDevData() );
+
+ // In the case of rotated text is aStartPos considered TopLeft because
+ // other information is missing, and since the whole object is shown anyway
+ // un-scrolled.
+ // The rectangle is infinite.
+ const Point aOrigin( aStartPos );
+
+ // #110496# Added some more optional metafile comments. This
+ // change: factored out some duplicated code.
+ GDIMetaFile* pMtf = rOutDev.GetConnectMetaFile();
+ const bool bMetafileValid( pMtf != nullptr );
+
+ const tools::Long nVertLineSpacing = CalcVertLineSpacing(aStartPos);
+
+ sal_Int16 nColumn = 0;
+
+ // Over all the paragraphs...
+
+ for ( sal_Int32 n = 0; n < GetParaPortions().Count(); n++ )
+ {
+ const ParaPortion* const pPortion = GetParaPortions()[n];
+ assert( pPortion && "NULL-Pointer in TokenList in Paint" );
+ // if when typing idle formatting, asynchronous Paint.
+ // Invisible Portions may be invalid.
+ if ( pPortion->IsVisible() && pPortion->IsInvalid() )
+ return;
+
+ if ( pPDFExtOutDevData )
+ pPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Paragraph);
+
+ const tools::Long nParaHeight = pPortion->GetHeight();
+ if ( pPortion->IsVisible() && (
+ ( !IsEffectivelyVertical() && ( ( aStartPos.Y() + nParaHeight ) > aClipRect.Top() ) ) ||
+ ( IsEffectivelyVertical() && IsTopToBottom() && ( ( aStartPos.X() - nParaHeight ) < aClipRect.Right() ) ) ||
+ ( IsEffectivelyVertical() && !IsTopToBottom() && ( ( aStartPos.X() + nParaHeight ) > aClipRect.Left() ) ) ) )
+
+ {
+ Point aTmpPos;
+
+ // Over the lines of the paragraph...
+
+ const sal_Int32 nLines = pPortion->GetLines().Count();
+ const sal_Int32 nLastLine = nLines-1;
+
+ bool bEndOfParagraphWritten(false);
+
+ adjustYDirectionAware(aStartPos, pPortion->GetFirstLineOffset());
+
+ const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_SBL );
+ sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix )
+ ? scaleYSpacingValue(rLSItem.GetInterLineSpace()) : 0;
+ bool bPaintBullet (false);
+
+ for ( sal_Int32 nLine = 0; nLine < nLines; nLine++ )
+ {
+ const EditLine* const pLine = &pPortion->GetLines()[nLine];
+ assert( pLine && "NULL-Pointer in the line iterator in UpdateViews" );
+ sal_Int32 nIndex = pLine->GetStart();
+ tools::Long nLineHeight = pLine->GetHeight();
+ if (nLine != nLastLine)
+ nLineHeight += nVertLineSpacing;
+ MoveToNextLine(aStartPos, nLineHeight, nColumn, aOrigin);
+ aTmpPos = aStartPos;
+ adjustXDirectionAware(aTmpPos, pLine->GetStartPosX());
+ adjustYDirectionAware(aTmpPos, pLine->GetMaxAscent() - nLineHeight);
+
+ if ( ( !IsEffectivelyVertical() && ( aStartPos.Y() > aClipRect.Top() ) )
+ || ( IsEffectivelyVertical() && IsTopToBottom() && aStartPos.X() < aClipRect.Right() )
+ || ( IsEffectivelyVertical() && !IsTopToBottom() && aStartPos.X() > aClipRect.Left() ) )
+ {
+ bPaintBullet = false;
+
+ // Why not just also call when stripping portions? This will give the correct values
+ // and needs no position corrections in OutlinerEditEng::DrawingText which tries to call
+ // PaintBullet correctly; exactly what GetEditEnginePtr()->PaintingFirstLine
+ // does, too. No change for not-layouting (painting).
+ if(0 == nLine) // && !bStripOnly)
+ {
+ Point aLineStart(aStartPos);
+ adjustYDirectionAware(aLineStart, -nLineHeight);
+ GetEditEnginePtr()->PaintingFirstLine(n, aLineStart, aOrigin, nOrientation, rOutDev);
+
+ // Remember whether a bullet was painted.
+ const SfxBoolItem& rBulletState = pEditEngine->GetParaAttrib(n, EE_PARA_BULLETSTATE);
+ bPaintBullet = rBulletState.GetValue();
+ }
+
+
+ // Over the Portions of the line...
+
+ bool bParsingFields = false;
+ std::vector< sal_Int32 >::iterator itSubLines;
+
+ for ( sal_Int32 nPortion = pLine->GetStartPortion(); nPortion <= pLine->GetEndPortion(); nPortion++ )
+ {
+ DBG_ASSERT( pPortion->GetTextPortions().Count(), "Line without Textportion in Paint!" );
+ const TextPortion& rTextPortion = pPortion->GetTextPortions()[nPortion];
+
+ const tools::Long nPortionXOffset = GetPortionXOffset( pPortion, pLine, nPortion );
+ setXDirectionAwareFrom(aTmpPos, aStartPos);
+ adjustXDirectionAware(aTmpPos, nPortionXOffset);
+ if (isXOverflowDirectionAware(aTmpPos, aClipRect))
+ break; // No further output in line necessary
+
+ switch ( rTextPortion.GetKind() )
+ {
+ case PortionKind::TEXT:
+ case PortionKind::FIELD:
+ case PortionKind::HYPHENATOR:
+ {
+ SeekCursor( pPortion->GetNode(), nIndex+1, aTmpFont, &rOutDev );
+
+ bool bDrawFrame = false;
+
+ if ( ( rTextPortion.GetKind() == PortionKind::FIELD ) && !aTmpFont.IsTransparent() &&
+ ( GetBackgroundColor() != COL_AUTO ) && GetBackgroundColor().IsDark() &&
+ ( IsAutoColorEnabled() && ( rOutDev.GetOutDevType() != OUTDEV_PRINTER ) ) )
+ {
+ aTmpFont.SetTransparent( true );
+ rOutDev.SetFillColor();
+ rOutDev.SetLineColor( GetAutoColor() );
+ bDrawFrame = true;
+ }
+
+#if OSL_DEBUG_LEVEL > 2
+ // Do we really need this if statement?
+ if ( rTextPortion.GetKind() == PortionKind::HYPHENATOR )
+ {
+ aTmpFont.SetFillColor( COL_LIGHTGRAY );
+ aTmpFont.SetTransparent( sal_False );
+ }
+ if ( rTextPortion.IsRightToLeft() )
+ {
+ aTmpFont.SetFillColor( COL_LIGHTGRAY );
+ aTmpFont.SetTransparent( sal_False );
+ }
+ else if ( GetI18NScriptType( EditPaM( pPortion->GetNode(), nIndex+1 ) ) == i18n::ScriptType::COMPLEX )
+ {
+ aTmpFont.SetFillColor( COL_LIGHTCYAN );
+ aTmpFont.SetTransparent( sal_False );
+ }
+#endif
+ aTmpFont.SetPhysFont(rOutDev);
+
+ // #114278# Saving both layout mode and language (since I'm
+ // potentially changing both)
+ rOutDev.Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE );
+ ImplInitLayoutMode(rOutDev, n, nIndex);
+ ImplInitDigitMode(rOutDev, aTmpFont.GetLanguage());
+
+ OUString aText;
+ sal_Int32 nTextStart = 0;
+ sal_Int32 nTextLen = 0;
+ std::span<const sal_Int32> pDXArray;
+ std::span<const sal_Bool> pKashidaArray;
+ KernArray aTmpDXArray;
+
+ if ( rTextPortion.GetKind() == PortionKind::TEXT )
+ {
+ aText = pPortion->GetNode()->GetString();
+ nTextStart = nIndex;
+ nTextLen = rTextPortion.GetLen();
+ pDXArray = std::span(pLine->GetCharPosArray().data() + (nIndex - pLine->GetStart()),
+ pLine->GetCharPosArray().size() - (nIndex - pLine->GetStart()));
+
+ if (!pLine->GetKashidaArray().empty())
+ {
+ pKashidaArray = std::span(pLine->GetKashidaArray().data() + (nIndex - pLine->GetStart()),
+ pLine->GetKashidaArray().size() - (nIndex - pLine->GetStart()));
+ }
+
+ // Paint control characters (#i55716#)
+ /* XXX: Given that there's special handling
+ * only for some specific characters
+ * (U+200B ZERO WIDTH SPACE and U+2060 WORD
+ * JOINER) it is assumed to be not relevant
+ * for MarkUrlFields(). */
+ if (maStatus.MarkNonUrlFields())
+ {
+ sal_Int32 nTmpIdx;
+ const sal_Int32 nTmpEnd = nTextStart + rTextPortion.GetLen();
+
+ for ( nTmpIdx = nTextStart; nTmpIdx <= nTmpEnd ; ++nTmpIdx )
+ {
+ const sal_Unicode cChar = ( nTmpIdx != aText.getLength() && ( nTmpIdx != nTextStart || 0 == nTextStart ) ) ?
+ aText[nTmpIdx] :
+ 0;
+
+ if ( 0x200B == cChar || 0x2060 == cChar )
+ {
+ tools::Long nHalfBlankWidth = aTmpFont.QuickGetTextSize( &rOutDev,
+ " ", 0, 1, nullptr ).Width() / 2;
+
+ const tools::Long nAdvanceX = ( nTmpIdx == nTmpEnd ?
+ rTextPortion.GetSize().Width() :
+ pDXArray[ nTmpIdx - nTextStart ] ) - nHalfBlankWidth;
+ const tools::Long nAdvanceY = -pLine->GetMaxAscent();
+
+ Point aTopLeftRectPos( aTmpPos );
+ adjustXDirectionAware(aTopLeftRectPos, nAdvanceX);
+ adjustYDirectionAware(aTopLeftRectPos, nAdvanceY);
+
+ Point aBottomRightRectPos( aTopLeftRectPos );
+ adjustXDirectionAware(aBottomRightRectPos, 2 * nHalfBlankWidth);
+ adjustYDirectionAware(aBottomRightRectPos, pLine->GetHeight());
+
+ rOutDev.Push( vcl::PushFlags::FILLCOLOR );
+ rOutDev.Push( vcl::PushFlags::LINECOLOR );
+ rOutDev.SetFillColor( COL_LIGHTGRAY );
+ rOutDev.SetLineColor( COL_LIGHTGRAY );
+
+ const tools::Rectangle aBackRect( aTopLeftRectPos, aBottomRightRectPos );
+ rOutDev.DrawRect( aBackRect );
+
+ rOutDev.Pop();
+ rOutDev.Pop();
+
+ if ( 0x200B == cChar )
+ {
+ const OUString aSlash( '/' );
+ const short nOldEscapement = aTmpFont.GetEscapement();
+ const sal_uInt8 nOldPropr = aTmpFont.GetPropr();
+
+ aTmpFont.SetEscapement( -20 );
+ aTmpFont.SetPropr( 25 );
+ aTmpFont.SetPhysFont(rOutDev);
+
+ const Size aSlashSize = aTmpFont.QuickGetTextSize( &rOutDev,
+ aSlash, 0, 1, nullptr );
+ Point aSlashPos( aTmpPos );
+ const tools::Long nAddX = nHalfBlankWidth - aSlashSize.Width() / 2;
+ setXDirectionAwareFrom(aSlashPos, aTopLeftRectPos);
+ adjustXDirectionAware(aSlashPos, nAddX);
+
+ aTmpFont.QuickDrawText( &rOutDev, aSlashPos, aSlash, 0, 1, {} );
+
+ aTmpFont.SetEscapement( nOldEscapement );
+ aTmpFont.SetPropr( nOldPropr );
+ aTmpFont.SetPhysFont(rOutDev);
+ }
+ }
+ }
+ }
+ }
+ else if ( rTextPortion.GetKind() == PortionKind::FIELD )
+ {
+ const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex);
+ assert( pAttr && "Field not found");
+ DBG_ASSERT( dynamic_cast< const SvxFieldItem* >( pAttr->GetItem() ) != nullptr, "Field of the wrong type! ");
+ aText = static_cast<const EditCharAttribField*>(pAttr)->GetFieldValue();
+ nTextStart = 0;
+ nTextLen = aText.getLength();
+ ExtraPortionInfo *pExtraInfo = rTextPortion.GetExtraInfos();
+ // Do not split the Fields into different lines while editing
+ // With EditView on Overlay bStripOnly is now set for stripping to
+ // primitives. To stay compatible in EditMode use pActiveView to detect
+ // when we are in EditMode. For whatever reason URLs are drawn as single
+ // line in edit mode, originally clipped against edit area (which is no
+ // longer done in Overlay mode and allows to *read* the URL).
+ // It would be difficult to change this due to needed adaptations in
+ // EditEngine (look for lineBreaksList creation)
+ if( nullptr == pActiveView && bStripOnly && !bParsingFields && pExtraInfo && !pExtraInfo->lineBreaksList.empty() )
+ {
+ bParsingFields = true;
+ itSubLines = pExtraInfo->lineBreaksList.begin();
+ }
+
+ if( bParsingFields )
+ {
+ if( itSubLines != pExtraInfo->lineBreaksList.begin() )
+ {
+ // only use GetMaxAscent(), pLine->GetHeight() will not
+ // proceed as needed (see PortionKind::TEXT above and nAdvanceY)
+ // what will lead to a compressed look with multiple lines
+ const sal_uInt16 nMaxAscent(pLine->GetMaxAscent());
+
+ aTmpPos += MoveToNextLine(aStartPos, nMaxAscent,
+ nColumn, aOrigin);
+ }
+ std::vector< sal_Int32 >::iterator curIt = itSubLines;
+ ++itSubLines;
+ if( itSubLines != pExtraInfo->lineBreaksList.end() )
+ {
+ nTextStart = *curIt;
+ nTextLen = *itSubLines - nTextStart;
+ }
+ else
+ {
+ nTextStart = *curIt;
+ nTextLen = nTextLen - nTextStart;
+ bParsingFields = false;
+
+ if (nLine + 1 < nLines)
+ {
+ // tdf#148966 don't paint the line break following a
+ // multiline field based on a compat flag
+ OutlinerEditEng* pOutlEditEng{ dynamic_cast<OutlinerEditEng*>(pEditEngine) };
+ if (pOutlEditEng
+ && pOutlEditEng->GetCompatFlag(SdrCompatibilityFlag::IgnoreBreakAfterMultilineField)
+ .value_or(false))
+ {
+ int nStartNextLine = pPortion->GetLines()[nLine + 1].GetStartPortion();
+ const TextPortion& rNextTextPortion = pPortion->GetTextPortions()[nStartNextLine];
+ if (rNextTextPortion.GetKind() == PortionKind::LINEBREAK)
+ ++nLine; //ignore the following linebreak
+ }
+ }
+ }
+ }
+
+ aTmpFont.SetPhysFont(*GetRefDevice());
+ aTmpFont.QuickGetTextSize( GetRefDevice(), aText, nTextStart, nTextLen,
+ &aTmpDXArray );
+ assert(aTmpDXArray.get_factor() == 1);
+ std::vector<sal_Int32>& rKernArray = aTmpDXArray.get_subunit_array();
+ pDXArray = rKernArray;
+
+ // add a meta file comment if we record to a metafile
+ if( bMetafileValid )
+ {
+ const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
+ if( pFieldItem )
+ {
+ const SvxFieldData* pFieldData = pFieldItem->GetField();
+ if( pFieldData )
+ pMtf->AddAction( pFieldData->createBeginComment() );
+ }
+ }
+
+ }
+ else if ( rTextPortion.GetKind() == PortionKind::HYPHENATOR )
+ {
+ if ( rTextPortion.GetExtraValue() )
+ aText = OUString(rTextPortion.GetExtraValue());
+ aText += CH_HYPH;
+ nTextStart = 0;
+ nTextLen = aText.getLength();
+
+ // crash when accessing 0 pointer in pDXArray
+ aTmpFont.SetPhysFont(*GetRefDevice());
+ aTmpFont.QuickGetTextSize( GetRefDevice(), aText, 0, aText.getLength(),
+ &aTmpDXArray );
+ assert(aTmpDXArray.get_factor() == 1);
+ std::vector<sal_Int32>& rKernArray = aTmpDXArray.get_subunit_array();
+ pDXArray = rKernArray;
+ }
+
+ tools::Long nTxtWidth = rTextPortion.GetSize().Width();
+
+ Point aOutPos( aTmpPos );
+ Point aRedLineTmpPos = aTmpPos;
+ // In RTL portions spell markup pos should be at the start of the
+ // first chara as well. That is on the right end of the portion
+ if (rTextPortion.IsRightToLeft())
+ aRedLineTmpPos.AdjustX(rTextPortion.GetSize().Width() );
+
+ if ( bStripOnly )
+ {
+ EEngineData::WrongSpellVector aWrongSpellVector;
+
+ if(GetStatus().DoOnlineSpelling() && rTextPortion.GetLen())
+ {
+ WrongList* pWrongs = pPortion->GetNode()->GetWrongList();
+
+ if(pWrongs && !pWrongs->empty())
+ {
+ size_t nStart = nIndex, nEnd = 0;
+ bool bWrong = pWrongs->NextWrong(nStart, nEnd);
+ const size_t nMaxEnd(nIndex + rTextPortion.GetLen());
+
+ while(bWrong)
+ {
+ if(nStart >= nMaxEnd)
+ {
+ break;
+ }
+
+ if(nStart < o3tl::make_unsigned(nIndex))
+ {
+ nStart = nIndex;
+ }
+
+ if(nEnd > nMaxEnd)
+ {
+ nEnd = nMaxEnd;
+ }
+
+ // add to vector
+ aWrongSpellVector.emplace_back(nStart, nEnd);
+
+ // goto next index
+ nStart = nEnd + 1;
+
+ if(nEnd < nMaxEnd)
+ {
+ bWrong = pWrongs->NextWrong(nStart, nEnd);
+ }
+ else
+ {
+ bWrong = false;
+ }
+ }
+ }
+ }
+
+ const SvxFieldData* pFieldData = nullptr;
+
+ if(PortionKind::FIELD == rTextPortion.GetKind())
+ {
+ const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex);
+ const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
+
+ if(pFieldItem)
+ {
+ pFieldData = pFieldItem->GetField();
+ }
+ }
+
+ // support for EOC, EOW, EOS TEXT comments. To support that,
+ // the locale is needed. With the locale and a XBreakIterator it is
+ // possible to re-create the text marking info on primitive level
+ const lang::Locale aLocale(GetLocale(EditPaM(pPortion->GetNode(), nIndex + 1)));
+
+ // create EOL and EOP bools
+ const bool bEndOfLine(nPortion == pLine->GetEndPortion());
+ const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines);
+
+ // get Overline color (from ((const SvxOverlineItem*)GetItem())->GetColor() in
+ // consequence, but also already set at rOutDev)
+ const Color aOverlineColor(rOutDev.GetOverlineColor());
+
+ // get TextLine color (from ((const SvxUnderlineItem*)GetItem())->GetColor() in
+ // consequence, but also already set at rOutDev)
+ const Color aTextLineColor(rOutDev.GetTextLineColor());
+
+ // Unicode code points conversion according to ctl text numeral setting
+ aText = convertDigits(aText, nTextStart, nTextLen,
+ ImplCalcDigitLang(aTmpFont.GetLanguage()));
+
+ // StripPortions() data callback
+ GetEditEnginePtr()->DrawingText( aOutPos, aText, nTextStart, nTextLen, pDXArray, pKashidaArray,
+ aTmpFont, n, rTextPortion.GetRightToLeftLevel(),
+ !aWrongSpellVector.empty() ? &aWrongSpellVector : nullptr,
+ pFieldData,
+ bEndOfLine, bEndOfParagraph, // support for EOL/EOP TEXT comments
+ &aLocale,
+ aOverlineColor,
+ aTextLineColor);
+
+ // #108052# remember that EOP is written already for this ParaPortion
+ if(bEndOfParagraph)
+ {
+ bEndOfParagraphWritten = true;
+ }
+ }
+ else
+ {
+ short nEsc = aTmpFont.GetEscapement();
+ if ( nOrientation )
+ {
+ // In case of high/low do it yourself:
+ if ( aTmpFont.GetEscapement() )
+ {
+ tools::Long nDiff = aTmpFont.GetFontSize().Height() * aTmpFont.GetEscapement() / 100L;
+ adjustYDirectionAware(aOutPos, -nDiff);
+ aRedLineTmpPos = aOutPos;
+ aTmpFont.SetEscapement( 0 );
+ }
+
+ aOrigin.RotateAround(aOutPos, nOrientation);
+ aTmpFont.SetOrientation( aTmpFont.GetOrientation()+nOrientation );
+ aTmpFont.SetPhysFont(rOutDev);
+
+ }
+
+ // Take only what begins in the visible range:
+ // Important, because of a bug in some graphic cards
+ // when transparent font, output when negative
+ if ( nOrientation || ( !IsEffectivelyVertical() && ( ( aTmpPos.X() + nTxtWidth ) >= nFirstVisXPos ) )
+ || ( IsEffectivelyVertical() && ( ( aTmpPos.Y() + nTxtWidth ) >= nFirstVisYPos ) ) )
+ {
+ if ( nEsc && ( aTmpFont.GetUnderline() != LINESTYLE_NONE ) )
+ {
+ // Paint the high/low without underline,
+ // Display the Underline on the
+ // base line of the original font height...
+ // But only if there was something underlined before!
+ bool bSpecialUnderline = false;
+ EditCharAttrib* pPrev = pPortion->GetNode()->GetCharAttribs().FindAttrib( EE_CHAR_ESCAPEMENT, nIndex );
+ if ( pPrev )
+ {
+ SvxFont aDummy;
+ // Underscore in front?
+ if ( pPrev->GetStart() )
+ {
+ SeekCursor( pPortion->GetNode(), pPrev->GetStart(), aDummy );
+ if ( aDummy.GetUnderline() != LINESTYLE_NONE )
+ bSpecialUnderline = true;
+ }
+ if ( !bSpecialUnderline && ( pPrev->GetEnd() < pPortion->GetNode()->Len() ) )
+ {
+ SeekCursor( pPortion->GetNode(), pPrev->GetEnd()+1, aDummy );
+ if ( aDummy.GetUnderline() != LINESTYLE_NONE )
+ bSpecialUnderline = true;
+ }
+ }
+ if ( bSpecialUnderline )
+ {
+ Size aSz = aTmpFont.GetPhysTxtSize( &rOutDev, aText, nTextStart, nTextLen );
+ sal_uInt8 nProp = aTmpFont.GetPropr();
+ aTmpFont.SetEscapement( 0 );
+ aTmpFont.SetPropr( 100 );
+ aTmpFont.SetPhysFont(rOutDev);
+ OUStringBuffer aBlanks(nTextLen);
+ comphelper::string::padToLength( aBlanks, nTextLen, ' ' );
+ Point aUnderlinePos( aOutPos );
+ if ( nOrientation )
+ {
+ aUnderlinePos = aTmpPos;
+ aOrigin.RotateAround(aUnderlinePos, nOrientation);
+ }
+ rOutDev.DrawStretchText( aUnderlinePos, aSz.Width(), aBlanks.makeStringAndClear(), 0, nTextLen );
+
+ aTmpFont.SetUnderline( LINESTYLE_NONE );
+ if ( !nOrientation )
+ aTmpFont.SetEscapement( nEsc );
+ aTmpFont.SetPropr( nProp );
+ aTmpFont.SetPhysFont(rOutDev);
+ }
+ }
+ Point aRealOutPos( aOutPos );
+ if ( ( rTextPortion.GetKind() == PortionKind::TEXT )
+ && rTextPortion.GetExtraInfos() && rTextPortion.GetExtraInfos()->bCompressed
+ && rTextPortion.GetExtraInfos()->bFirstCharIsRightPunktuation )
+ {
+ aRealOutPos.AdjustX(rTextPortion.GetExtraInfos()->nPortionOffsetX );
+ }
+
+ // RTL portions with (#i37132#)
+ // compressed blank should not paint this blank:
+ if ( rTextPortion.IsRightToLeft() && nTextLen >= 2 &&
+ pDXArray[ nTextLen - 1 ] ==
+ pDXArray[ nTextLen - 2 ] &&
+ ' ' == aText[nTextStart + nTextLen - 1] )
+ --nTextLen;
+
+ // output directly
+ aTmpFont.QuickDrawText( &rOutDev, aRealOutPos, aText, nTextStart, nTextLen, pDXArray, pKashidaArray );
+
+ if ( bDrawFrame )
+ {
+ Point aTopLeft( aTmpPos );
+ aTopLeft.AdjustY( -(pLine->GetMaxAscent()) );
+ if ( nOrientation )
+ aOrigin.RotateAround(aTopLeft, nOrientation);
+ tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() );
+ rOutDev.DrawRect( aRect );
+ }
+
+ // PDF export:
+ if ( pPDFExtOutDevData )
+ {
+ if ( rTextPortion.GetKind() == PortionKind::FIELD )
+ {
+ const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex);
+ const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
+ if( pFieldItem )
+ {
+ const SvxFieldData* pFieldData = pFieldItem->GetField();
+ if ( auto pUrlField = dynamic_cast< const SvxURLField* >( pFieldData ) )
+ {
+ Point aTopLeft( aTmpPos );
+ aTopLeft.AdjustY( -(pLine->GetMaxAscent()) );
+
+ tools::Rectangle aRect( aTopLeft, rTextPortion.GetSize() );
+ vcl::PDFExtOutDevBookmarkEntry aBookmark;
+ aBookmark.nLinkId = pPDFExtOutDevData->CreateLink(aRect, pUrlField->GetRepresentation());
+ aBookmark.aBookmark = pUrlField->GetURL();
+ std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFExtOutDevData->GetBookmarks();
+ rBookmarks.push_back( aBookmark );
+ }
+ }
+ }
+ }
+ }
+
+ const WrongList* const pWrongList = pPortion->GetNode()->GetWrongList();
+ if ( GetStatus().DoOnlineSpelling() && pWrongList && !pWrongList->empty() && rTextPortion.GetLen() )
+ {
+ {//#105750# adjust LinePos for superscript or subscript text
+ short _nEsc = aTmpFont.GetEscapement();
+ if( _nEsc )
+ {
+ tools::Long nShift = (_nEsc * aTmpFont.GetFontSize().Height()) / 100L;
+ adjustYDirectionAware(aRedLineTmpPos, -nShift);
+ }
+ }
+ Color aOldColor( rOutDev.GetLineColor() );
+ rOutDev.SetLineColor( GetColorConfig().GetColorValue( svtools::SPELL ).nColor );
+ lcl_DrawRedLines( rOutDev, aTmpFont.GetFontSize().Height(), aRedLineTmpPos, static_cast<size_t>(nIndex), static_cast<size_t>(nIndex) + rTextPortion.GetLen(), pDXArray, pPortion->GetNode()->GetWrongList(), nOrientation, aOrigin, IsEffectivelyVertical(), rTextPortion.IsRightToLeft() );
+ rOutDev.SetLineColor( aOldColor );
+ }
+ }
+
+ rOutDev.Pop();
+
+ if ( rTextPortion.GetKind() == PortionKind::FIELD )
+ {
+ // add a meta file comment if we record to a metafile
+ if( bMetafileValid )
+ {
+ const EditCharAttrib* pAttr = pPortion->GetNode()->GetCharAttribs().FindFeature(nIndex);
+ assert( pAttr && "Field not found" );
+
+ const SvxFieldItem* pFieldItem = dynamic_cast<const SvxFieldItem*>(pAttr->GetItem());
+ DBG_ASSERT( pFieldItem != nullptr, "Wrong type of field!" );
+
+ if( pFieldItem )
+ {
+ const SvxFieldData* pFieldData = pFieldItem->GetField();
+ if( pFieldData )
+ pMtf->AddAction( SvxFieldData::createEndComment() );
+ }
+ }
+
+ }
+
+ }
+ break;
+ case PortionKind::TAB:
+ {
+ if ( rTextPortion.GetExtraValue() && ( rTextPortion.GetExtraValue() != ' ' ) )
+ {
+ SeekCursor( pPortion->GetNode(), nIndex+1, aTmpFont, &rOutDev );
+ aTmpFont.SetTransparent( false );
+ aTmpFont.SetEscapement( 0 );
+ aTmpFont.SetPhysFont(rOutDev);
+ tools::Long nCharWidth = aTmpFont.QuickGetTextSize( &rOutDev,
+ OUString(rTextPortion.GetExtraValue()), 0, 1, {} ).Width();
+ sal_Int32 nChars = 2;
+ if( nCharWidth )
+ nChars = rTextPortion.GetSize().Width() / nCharWidth;
+ if ( nChars < 2 )
+ nChars = 2; // is compressed by DrawStretchText.
+ else if ( nChars == 2 )
+ nChars = 3; // looks better
+
+ OUStringBuffer aBuf(nChars);
+ comphelper::string::padToLength(aBuf, nChars, rTextPortion.GetExtraValue());
+ OUString aText(aBuf.makeStringAndClear());
+ aTmpFont.QuickDrawText( &rOutDev, aTmpPos, aText, 0, aText.getLength(), {} );
+ rOutDev.DrawStretchText( aTmpPos, rTextPortion.GetSize().Width(), aText );
+
+ if ( bStripOnly )
+ {
+ // create EOL and EOP bools
+ const bool bEndOfLine(nPortion == pLine->GetEndPortion());
+ const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines);
+
+ const Color aOverlineColor(rOutDev.GetOverlineColor());
+ const Color aTextLineColor(rOutDev.GetTextLineColor());
+
+ // StripPortions() data callback
+ GetEditEnginePtr()->DrawingTab( aTmpPos,
+ rTextPortion.GetSize().Width(),
+ OUString(rTextPortion.GetExtraValue()),
+ aTmpFont, n, rTextPortion.GetRightToLeftLevel(),
+ bEndOfLine, bEndOfParagraph,
+ aOverlineColor, aTextLineColor);
+ }
+ }
+ else if ( bStripOnly )
+ {
+ // #i108052# When stripping, a callback for _empty_ paragraphs is also needed.
+ // This was optimized away (by not rendering the space-only tab portion), so do
+ // it manually here.
+ const bool bEndOfLine(nPortion == pLine->GetEndPortion());
+ const bool bEndOfParagraph(bEndOfLine && nLine + 1 == nLines);
+
+ const Color aOverlineColor(rOutDev.GetOverlineColor());
+ const Color aTextLineColor(rOutDev.GetTextLineColor());
+
+ GetEditEnginePtr()->DrawingText(
+ aTmpPos, OUString(), 0, 0, {}, {},
+ aTmpFont, n, 0,
+ nullptr,
+ nullptr,
+ bEndOfLine, bEndOfParagraph,
+ nullptr,
+ aOverlineColor,
+ aTextLineColor);
+ }
+ }
+ break;
+ case PortionKind::LINEBREAK: break;
+ }
+ if( bParsingFields )
+ nPortion--;
+ else
+ nIndex = nIndex + rTextPortion.GetLen();
+
+ }
+ }
+
+ if ((nLine != nLastLine ) && !maStatus.IsOutliner())
+ {
+ adjustYDirectionAware(aStartPos, nSBL);
+ }
+
+ // no more visible actions?
+ if (getYOverflowDirectionAware(aStartPos, aClipRect))
+ break;
+ }
+
+ if (!maStatus.IsOutliner())
+ {
+ const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem( EE_PARA_ULSPACE );
+ tools::Long nUL = scaleYSpacingValue(rULItem.GetLower());
+ adjustYDirectionAware(aStartPos, nUL);
+ }
+
+ // #108052# Safer way for #i108052# and #i118881#: If for the current ParaPortion
+ // EOP is not written, do it now. This will be safer than before. It has shown
+ // that the reason for #i108052# was fixed/removed again, so this is a try to fix
+ // the number of paragraphs (and counting empty ones) now independent from the
+ // changes in EditEngine behaviour.
+ if(!bEndOfParagraphWritten && !bPaintBullet && bStripOnly)
+ {
+ const Color aOverlineColor(rOutDev.GetOverlineColor());
+ const Color aTextLineColor(rOutDev.GetTextLineColor());
+
+ GetEditEnginePtr()->DrawingText(
+ aTmpPos, OUString(), 0, 0, {}, {},
+ aTmpFont, n, 0,
+ nullptr,
+ nullptr,
+ false, true, // support for EOL/EOP TEXT comments
+ nullptr,
+ aOverlineColor,
+ aTextLineColor);
+ }
+ }
+ else
+ {
+ adjustYDirectionAware(aStartPos, nParaHeight);
+ }
+
+ if ( pPDFExtOutDevData )
+ pPDFExtOutDevData->EndStructureElement();
+
+ // no more visible actions?
+ if (getYOverflowDirectionAware(aStartPos, aClipRect))
+ break;
+ }
+}
+
+void ImpEditEngine::Paint( ImpEditView* pView, const tools::Rectangle& rRect, OutputDevice* pTargetDevice )
+{
+ if ( !IsUpdateLayout() || IsInUndo() )
+ return;
+
+ assert( pView && "No View - No Paint!" );
+
+ // Intersection of paint area and output area.
+ tools::Rectangle aClipRect( pView->GetOutputArea() );
+ aClipRect.Intersection( rRect );
+
+ OutputDevice& rTarget = pTargetDevice ? *pTargetDevice : *pView->GetWindow()->GetOutDev();
+
+ Point aStartPos;
+ if ( !IsEffectivelyVertical() )
+ aStartPos = pView->GetOutputArea().TopLeft();
+ else
+ {
+ if( IsTopToBottom() )
+ aStartPos = pView->GetOutputArea().TopRight();
+ else
+ aStartPos = pView->GetOutputArea().BottomLeft();
+ }
+ adjustXDirectionAware(aStartPos, -(pView->GetVisDocLeft()));
+ adjustYDirectionAware(aStartPos, -(pView->GetVisDocTop()));
+
+ // If Doc-width < Output Area,Width and not wrapped fields,
+ // the fields usually protrude if > line.
+ // (Not at the top, since there the Doc-width from formatting is already
+ // there)
+ if ( !IsEffectivelyVertical() && ( pView->GetOutputArea().GetWidth() > GetPaperSize().Width() ) )
+ {
+ tools::Long nMaxX = pView->GetOutputArea().Left() + GetPaperSize().Width();
+ if ( aClipRect.Left() > nMaxX )
+ return;
+ if ( aClipRect.Right() > nMaxX )
+ aClipRect.SetRight( nMaxX );
+ }
+
+ bool bClipRegion = rTarget.IsClipRegion();
+ vcl::Region aOldRegion = rTarget.GetClipRegion();
+ rTarget.IntersectClipRegion( aClipRect );
+
+ Paint(rTarget, aClipRect, aStartPos);
+
+ if ( bClipRegion )
+ rTarget.SetClipRegion( aOldRegion );
+ else
+ rTarget.SetClipRegion();
+
+ pView->DrawSelectionXOR(pView->GetEditSelection(), nullptr, &rTarget);
+}
+
+void ImpEditEngine::InsertContent( ContentNode* pNode, sal_Int32 nPos )
+{
+ DBG_ASSERT( pNode, "NULL-Pointer in InsertContent! " );
+ DBG_ASSERT( IsInUndo(), "InsertContent only for Undo()!" );
+ GetParaPortions().Insert(nPos, std::make_unique<ParaPortion>( pNode ));
+ maEditDoc.Insert(nPos, pNode);
+ if ( IsCallParaInsertedOrDeleted() )
+ GetEditEnginePtr()->ParagraphInserted( nPos );
+}
+
+EditPaM ImpEditEngine::SplitContent( sal_Int32 nNode, sal_Int32 nSepPos )
+{
+ ContentNode* pNode = maEditDoc.GetObject( nNode );
+ DBG_ASSERT( pNode, "Invalid Node in SplitContent" );
+ DBG_ASSERT( IsInUndo(), "SplitContent only for Undo()!" );
+ DBG_ASSERT( nSepPos <= pNode->Len(), "Index out of range: SplitContent" );
+ EditPaM aPaM( pNode, nSepPos );
+ return ImpInsertParaBreak( aPaM );
+}
+
+EditPaM ImpEditEngine::ConnectContents( sal_Int32 nLeftNode, bool bBackward )
+{
+ ContentNode* pLeftNode = maEditDoc.GetObject( nLeftNode );
+ ContentNode* pRightNode = maEditDoc.GetObject( nLeftNode+1 );
+ DBG_ASSERT( pLeftNode, "Invalid left node in ConnectContents ");
+ DBG_ASSERT( pRightNode, "Invalid right node in ConnectContents ");
+ return ImpConnectParagraphs( pLeftNode, pRightNode, bBackward );
+}
+
+bool ImpEditEngine::SetUpdateLayout( bool bUp, EditView* pCurView, bool bForceUpdate )
+{
+ const bool bPrevUpdateLayout = mbUpdateLayout;
+ const bool mbChanged = (mbUpdateLayout != bUp);
+
+ // When switching from true to false, all selections were visible,
+ // => paint over
+ // the other hand, were all invisible => paint
+ // If !bFormatted, e.g. after SetText, then if UpdateMode=true
+ // formatting is not needed immediately, probably because more text is coming.
+ // At latest it is formatted at a Paint/CalcTextWidth.
+ mbUpdateLayout = bUp;
+ if ( mbUpdateLayout && ( mbChanged || bForceUpdate ) )
+ FormatAndLayout( pCurView );
+ return bPrevUpdateLayout;
+}
+
+void ImpEditEngine::ShowParagraph( sal_Int32 nParagraph, bool bShow )
+{
+ ParaPortion* pPPortion = GetParaPortions().SafeGetObject( nParagraph );
+ DBG_ASSERT( pPPortion, "ShowParagraph: Paragraph does not exist! ");
+ if ( !(pPPortion && ( pPPortion->IsVisible() != bShow )) )
+ return;
+
+ pPPortion->SetVisible( bShow );
+
+ if ( !bShow )
+ {
+ // Mark as deleted, so that no selection will end or begin at
+ // this paragraph...
+ aDeletedNodes.push_back(std::make_unique<DeletedNodeInfo>( pPPortion->GetNode(), nParagraph ));
+ UpdateSelections();
+ // The region below will not be invalidated if UpdateMode = sal_False!
+ // If anyway, then save as sal_False before SetVisible !
+ }
+
+ if ( bShow && ( pPPortion->IsInvalid() || !pPPortion->nHeight ) )
+ {
+ if ( !GetTextRanger() )
+ {
+ if ( pPPortion->IsInvalid() )
+ {
+ CreateLines( nParagraph, 0 ); // 0: No TextRanger
+ }
+ else
+ {
+ CalcHeight( pPPortion );
+ }
+ nCurTextHeight += pPPortion->GetHeight();
+ }
+ else
+ {
+ nCurTextHeight = 0x7fffffff;
+ }
+ }
+
+ pPPortion->SetMustRepaint( true );
+ if ( IsUpdateLayout() && !IsInUndo() && !GetTextRanger() )
+ {
+ aInvalidRect = tools::Rectangle( Point( 0, GetParaPortions().GetYOffset( pPPortion ) ),
+ Point( GetPaperSize().Width(), nCurTextHeight ) );
+ UpdateViews( GetActiveView() );
+ }
+}
+
+EditSelection ImpEditEngine::MoveParagraphs( Range aOldPositions, sal_Int32 nNewPos, EditView* pCurView )
+{
+ DBG_ASSERT( GetParaPortions().Count() != 0, "No paragraphs found: MoveParagraphs" );
+ if ( GetParaPortions().Count() == 0 )
+ return EditSelection();
+ aOldPositions.Normalize();
+
+ EditSelection aSel( ImpMoveParagraphs( aOldPositions, nNewPos ) );
+
+ if ( nNewPos >= GetParaPortions().Count() )
+ nNewPos = GetParaPortions().Count() - 1;
+
+ // Where the paragraph was inserted it has to be properly redrawn:
+ // Where the paragraph was removed it has to be properly redrawn:
+ // ( and correspondingly in between as well...)
+ if ( pCurView && IsUpdateLayout() )
+ {
+ // in this case one can redraw directly without invalidating the
+ // Portions
+ sal_Int32 nFirstPortion = std::min( static_cast<sal_Int32>(aOldPositions.Min()), nNewPos );
+ sal_Int32 nLastPortion = std::max( static_cast<sal_Int32>(aOldPositions.Max()), nNewPos );
+
+ ParaPortion* pUpperPortion = GetParaPortions().SafeGetObject( nFirstPortion );
+ ParaPortion* pLowerPortion = GetParaPortions().SafeGetObject( nLastPortion );
+ if (pUpperPortion && pLowerPortion)
+ {
+ aInvalidRect = tools::Rectangle(); // make empty
+ aInvalidRect.SetLeft( 0 );
+ aInvalidRect.SetRight(GetColumnWidth(maPaperSize));
+ aInvalidRect.SetTop( GetParaPortions().GetYOffset( pUpperPortion ) );
+ aInvalidRect.SetBottom( GetParaPortions().GetYOffset( pLowerPortion ) + pLowerPortion->GetHeight() );
+
+ UpdateViews( pCurView );
+ }
+ }
+ else
+ {
+ // redraw from the upper invalid position
+ sal_Int32 nFirstInvPara = std::min( static_cast<sal_Int32>(aOldPositions.Min()), nNewPos );
+ InvalidateFromParagraph( nFirstInvPara );
+ }
+ return aSel;
+}
+
+void ImpEditEngine::InvalidateFromParagraph( sal_Int32 nFirstInvPara )
+{
+ // The following paragraphs are not invalidated, since ResetHeight()
+ // => size change => all the following are re-issued anyway.
+ ParaPortion* pTmpPortion;
+ if ( nFirstInvPara != 0 )
+ {
+ pTmpPortion = GetParaPortions()[nFirstInvPara-1];
+ pTmpPortion->MarkInvalid( pTmpPortion->GetNode()->Len(), 0 );
+ }
+ else
+ {
+ pTmpPortion = GetParaPortions()[0];
+ pTmpPortion->MarkSelectionInvalid( 0 );
+ }
+ pTmpPortion->ResetHeight();
+}
+
+IMPL_LINK_NOARG(ImpEditEngine, StatusTimerHdl, Timer *, void)
+{
+ CallStatusHdl();
+}
+
+void ImpEditEngine::CallStatusHdl()
+{
+ if ( aStatusHdlLink.IsSet() && bool(maStatus.GetStatusWord()) )
+ {
+ // The Status has to be reset before the Call,
+ // since other Flags might be set in the handler...
+ EditStatus aTmpStatus( maStatus );
+ maStatus.Clear();
+ aStatusHdlLink.Call( aTmpStatus );
+ aStatusTimer.Stop(); // If called by hand...
+ }
+}
+
+ContentNode* ImpEditEngine::GetPrevVisNode( ContentNode const * pCurNode )
+{
+ const ParaPortion* pPortion = FindParaPortion( pCurNode );
+ DBG_ASSERT( pPortion, "GetPrevVisibleNode: No matching portion!" );
+ pPortion = GetPrevVisPortion( pPortion );
+ if ( pPortion )
+ return pPortion->GetNode();
+ return nullptr;
+}
+
+ContentNode* ImpEditEngine::GetNextVisNode( ContentNode const * pCurNode )
+{
+ const ParaPortion* pPortion = FindParaPortion( pCurNode );
+ DBG_ASSERT( pPortion, "GetNextVisibleNode: No matching portion!" );
+ pPortion = GetNextVisPortion( pPortion );
+ if ( pPortion )
+ return pPortion->GetNode();
+ return nullptr;
+}
+
+const ParaPortion* ImpEditEngine::GetPrevVisPortion( const ParaPortion* pCurPortion ) const
+{
+ sal_Int32 nPara = GetParaPortions().GetPos( pCurPortion );
+ DBG_ASSERT( nPara < GetParaPortions().Count() , "Portion not found: GetPrevVisPortion" );
+ const ParaPortion* pPortion = nPara ? GetParaPortions()[--nPara] : nullptr;
+ while ( pPortion && !pPortion->IsVisible() )
+ pPortion = nPara ? GetParaPortions()[--nPara] : nullptr;
+
+ return pPortion;
+}
+
+const ParaPortion* ImpEditEngine::GetNextVisPortion( const ParaPortion* pCurPortion ) const
+{
+ sal_Int32 nPara = GetParaPortions().GetPos( pCurPortion );
+ DBG_ASSERT( nPara < GetParaPortions().Count() , "Portion not found: GetPrevVisNode" );
+ const ParaPortion* pPortion = GetParaPortions().SafeGetObject( ++nPara );
+ while ( pPortion && !pPortion->IsVisible() )
+ pPortion = GetParaPortions().SafeGetObject( ++nPara );
+
+ return pPortion;
+}
+
+tools::Long ImpEditEngine::CalcVertLineSpacing(Point& rStartPos) const
+{
+ tools::Long nTotalOccupiedHeight = 0;
+ sal_Int32 nTotalLineCount = 0;
+ const ParaPortionList& rParaPortions = GetParaPortions();
+ sal_Int32 nParaCount = rParaPortions.Count();
+
+ for (sal_Int32 i = 0; i < nParaCount; ++i)
+ {
+ if (GetVerJustification(i) != SvxCellVerJustify::Block)
+ // All paragraphs must have the block justification set.
+ return 0;
+
+ const ParaPortion* pPortion = rParaPortions[i];
+ nTotalOccupiedHeight += pPortion->GetFirstLineOffset();
+
+ const SvxLineSpacingItem& rLSItem = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_SBL);
+ sal_uInt16 nSBL = ( rLSItem.GetInterLineSpaceRule() == SvxInterLineSpaceRule::Fix )
+ ? scaleYSpacingValue(rLSItem.GetInterLineSpace()) : 0;
+
+ const SvxULSpaceItem& rULItem = pPortion->GetNode()->GetContentAttribs().GetItem(EE_PARA_ULSPACE);
+ tools::Long nUL = scaleYSpacingValue(rULItem.GetLower());
+
+ const EditLineList& rLines = pPortion->GetLines();
+ sal_Int32 nLineCount = rLines.Count();
+ nTotalLineCount += nLineCount;
+ for (sal_Int32 j = 0; j < nLineCount; ++j)
+ {
+ const EditLine& rLine = rLines[j];
+ nTotalOccupiedHeight += rLine.GetHeight();
+ if (j < nLineCount-1)
+ nTotalOccupiedHeight += nSBL;
+ nTotalOccupiedHeight += nUL;
+ }
+ }
+
+ tools::Long nTotalSpace = getHeightDirectionAware(maPaperSize);
+ nTotalSpace -= nTotalOccupiedHeight;
+ if (nTotalSpace <= 0 || nTotalLineCount <= 1)
+ return 0;
+
+ // Shift the text to the right for the asian layout mode.
+ if (IsEffectivelyVertical())
+ adjustYDirectionAware(rStartPos, -nTotalSpace);
+
+ return nTotalSpace / (nTotalLineCount-1);
+}
+
+EditPaM ImpEditEngine::InsertParagraph( sal_Int32 nPara )
+{
+ EditPaM aPaM;
+ if ( nPara != 0 )
+ {
+ ContentNode* pNode = GetEditDoc().GetObject( nPara-1 );
+ if ( !pNode )
+ pNode = GetEditDoc().GetObject( GetEditDoc().Count() - 1 );
+ assert(pNode && "Not a single paragraph in InsertParagraph ?");
+ aPaM = EditPaM( pNode, pNode->Len() );
+ }
+ else
+ {
+ ContentNode* pNode = GetEditDoc().GetObject( 0 );
+ aPaM = EditPaM( pNode, 0 );
+ }
+
+ return ImpInsertParaBreak( aPaM );
+}
+
+std::optional<EditSelection> ImpEditEngine::SelectParagraph( sal_Int32 nPara )
+{
+ std::optional<EditSelection> pSel;
+ ContentNode* pNode = GetEditDoc().GetObject( nPara );
+ SAL_WARN_IF( !pNode, "editeng", "Paragraph does not exist: SelectParagraph" );
+ if ( pNode )
+ pSel.emplace( EditPaM( pNode, 0 ), EditPaM( pNode, pNode->Len() ) );
+
+ return pSel;
+}
+
+void ImpEditEngine::FormatAndLayout( EditView* pCurView, bool bCalledFromUndo )
+{
+ if (mbDowning)
+ return;
+
+ if ( IsInUndo() )
+ IdleFormatAndLayout( pCurView );
+ else
+ {
+ if (bCalledFromUndo)
+ // in order to make bullet points that have had their styles changed, redraw themselves
+ for ( sal_Int32 nPortion = 0; nPortion < GetParaPortions().Count(); nPortion++ )
+ GetParaPortions()[nPortion]->MarkInvalid( 0, 0 );
+ FormatDoc();
+ UpdateViews( pCurView );
+ }
+
+ EENotify aNotify(EE_NOTIFY_PROCESSNOTIFICATIONS);
+ GetNotifyHdl().Call(aNotify);
+}
+
+void ImpEditEngine::SetFlatMode( bool bFlat )
+{
+ if ( bFlat != maStatus.UseCharAttribs() )
+ return;
+
+ if ( !bFlat )
+ maStatus.TurnOnFlags( EEControlBits::USECHARATTRIBS );
+ else
+ maStatus.TurnOffFlags( EEControlBits::USECHARATTRIBS );
+
+ maEditDoc.CreateDefFont( !bFlat );
+
+ FormatFullDoc();
+ UpdateViews();
+ if ( pActiveView )
+ pActiveView->ShowCursor();
+}
+
+void ImpEditEngine::setScale(double fFontScaleX, double fFontScaleY, double fSpacingScaleX, double fSpacingScaleY)
+{
+ bool bChanged;
+
+ if (!IsEffectivelyVertical())
+ {
+ bChanged = mfFontScaleX != fFontScaleX || mfFontScaleY != fFontScaleY ||
+ mfSpacingScaleX != fSpacingScaleX || mfSpacingScaleY != fSpacingScaleY;
+ mfFontScaleX = fFontScaleX;
+ mfFontScaleY = fFontScaleY;
+ mfSpacingScaleX = fSpacingScaleX;
+ mfSpacingScaleY = fSpacingScaleY;
+ }
+ else
+ {
+ bChanged = mfFontScaleX != fFontScaleY || mfFontScaleY != fFontScaleX ||
+ mfSpacingScaleX != fSpacingScaleY || mfSpacingScaleY != fSpacingScaleX;
+ mfFontScaleX = fFontScaleY;
+ mfFontScaleY = fFontScaleX;
+ mfSpacingScaleX = fSpacingScaleY;
+ mfSpacingScaleY = fSpacingScaleX;
+ }
+
+ if (bChanged && maStatus.DoStretch())
+ {
+ FormatFullDoc();
+ // (potentially) need everything redrawn
+ aInvalidRect = tools::Rectangle(0, 0, 1000000, 1000000);
+ UpdateViews(GetActiveView());
+ }
+}
+
+const SvxNumberFormat* ImpEditEngine::GetNumberFormat( const ContentNode *pNode ) const
+{
+ const SvxNumberFormat *pRes = nullptr;
+
+ if (pNode)
+ {
+ // get index of paragraph
+ sal_Int32 nPara = GetEditDoc().GetPos( pNode );
+ DBG_ASSERT( nPara < EE_PARA_NOT_FOUND, "node not found in array" );
+ if (nPara < EE_PARA_NOT_FOUND)
+ {
+ // the called function may be overridden by an OutlinerEditEng
+ // object to provide
+ // access to the SvxNumberFormat of the Outliner.
+ // The EditEngine implementation will just return 0.
+ pRes = pEditEngine->GetNumberFormat( nPara );
+ }
+ }
+
+ return pRes;
+}
+
+sal_Int32 ImpEditEngine::GetSpaceBeforeAndMinLabelWidth(
+ const ContentNode *pNode,
+ sal_Int32 *pnSpaceBefore, sal_Int32 *pnMinLabelWidth ) const
+{
+ // nSpaceBefore matches the ODF attribute text:space-before
+ // nMinLabelWidth matches the ODF attribute text:min-label-width
+
+ const SvxNumberFormat *pNumFmt = GetNumberFormat( pNode );
+
+ // if no number format was found we have no Outliner or the numbering level
+ // within the Outliner is -1 which means no number format should be applied.
+ // Thus the default values to be returned are 0.
+ sal_Int32 nSpaceBefore = 0;
+ sal_Int32 nMinLabelWidth = 0;
+
+ if (pNumFmt)
+ {
+ nMinLabelWidth = -pNumFmt->GetFirstLineOffset();
+ nSpaceBefore = pNumFmt->GetAbsLSpace() - nMinLabelWidth;
+ DBG_ASSERT( nMinLabelWidth >= 0, "ImpEditEngine::GetSpaceBeforeAndMinLabelWidth: min-label-width < 0 encountered" );
+ }
+ if (pnSpaceBefore)
+ *pnSpaceBefore = nSpaceBefore;
+ if (pnMinLabelWidth)
+ *pnMinLabelWidth = nMinLabelWidth;
+
+ return nSpaceBefore + nMinLabelWidth;
+}
+
+const SvxLRSpaceItem& ImpEditEngine::GetLRSpaceItem( ContentNode* pNode )
+{
+ return pNode->GetContentAttribs().GetItem( maStatus.IsOutliner() ? EE_PARA_OUTLLRSPACE : EE_PARA_LRSPACE );
+}
+
+// select a representative text language for the digit type according to the
+// text numeral setting:
+LanguageType ImpEditEngine::ImplCalcDigitLang(LanguageType eCurLang)
+{
+ if (utl::ConfigManager::IsFuzzing())
+ return LANGUAGE_ENGLISH_US;
+
+ // #114278# Also setting up digit language from Svt options
+ // (cannot reliably inherit the outdev's setting)
+
+ LanguageType eLang = eCurLang;
+ const SvtCTLOptions::TextNumerals nCTLTextNumerals = SvtCTLOptions::GetCTLTextNumerals();
+
+ if ( SvtCTLOptions::NUMERALS_HINDI == nCTLTextNumerals )
+ eLang = LANGUAGE_ARABIC_SAUDI_ARABIA;
+ else if ( SvtCTLOptions::NUMERALS_ARABIC == nCTLTextNumerals )
+ eLang = LANGUAGE_ENGLISH;
+ else if ( SvtCTLOptions::NUMERALS_SYSTEM == nCTLTextNumerals )
+ eLang = Application::GetSettings().GetLanguageTag().getLanguageType();
+
+ return eLang;
+}
+
+OUString ImpEditEngine::convertDigits(std::u16string_view rString, sal_Int32 nStt, sal_Int32 nLen, LanguageType eDigitLang)
+{
+ OUStringBuffer aBuf(rString);
+ for (sal_Int32 nIdx = nStt, nEnd = nStt + nLen; nIdx < nEnd; ++nIdx)
+ {
+ sal_Unicode cChar = aBuf[nIdx];
+ if (cChar >= '0' && cChar <= '9')
+ aBuf[nIdx] = GetLocalizedChar(cChar, eDigitLang);
+ }
+ return aBuf.makeStringAndClear();
+}
+
+// Either sets the digit mode at the output device
+void ImpEditEngine::ImplInitDigitMode(OutputDevice& rOutDev, LanguageType eCurLang)
+{
+ rOutDev.SetDigitLanguage(ImplCalcDigitLang(eCurLang));
+}
+
+void ImpEditEngine::ImplInitLayoutMode(OutputDevice& rOutDev, sal_Int32 nPara, sal_Int32 nIndex)
+{
+ bool bCTL = false;
+ bool bR2L = false;
+ if ( nIndex == -1 )
+ {
+ bCTL = HasScriptType( nPara, i18n::ScriptType::COMPLEX );
+ bR2L = IsRightToLeft( nPara );
+ }
+ else
+ {
+ ContentNode* pNode = GetEditDoc().GetObject( nPara );
+ short nScriptType = GetI18NScriptType( EditPaM( pNode, nIndex+1 ) );
+ bCTL = nScriptType == i18n::ScriptType::COMPLEX;
+ // this change was discussed in issue 37190
+ bR2L = (GetRightToLeft( nPara, nIndex + 1) % 2) != 0;
+ // it also works for issue 55927
+ }
+
+ vcl::text::ComplexTextLayoutFlags nLayoutMode = rOutDev.GetLayoutMode();
+
+ // We always use the left position for DrawText()
+ nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags::BiDiRtl;
+
+ if ( !bCTL && !bR2L)
+ {
+ // No Bidi checking necessary
+ nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiStrong;
+ }
+ else
+ {
+ // Bidi checking necessary
+ // Don't use BIDI_STRONG, VCL must do some checks.
+ nLayoutMode &= ~vcl::text::ComplexTextLayoutFlags::BiDiStrong;
+
+ if ( bR2L )
+ nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl|vcl::text::ComplexTextLayoutFlags::TextOriginLeft;
+ }
+
+ rOutDev.SetLayoutMode( nLayoutMode );
+
+ // #114278# Also setting up digit language from Svt options
+ // (cannot reliably inherit the outdev's setting)
+ LanguageType eLang = Application::GetSettings().GetLanguageTag().getLanguageType();
+ ImplInitDigitMode(rOutDev, eLang);
+}
+
+Reference < i18n::XBreakIterator > const & ImpEditEngine::ImplGetBreakIterator() const
+{
+ if ( !xBI.is() )
+ {
+ Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
+ xBI = i18n::BreakIterator::create( xContext );
+ }
+ return xBI;
+}
+
+Reference < i18n::XExtendedInputSequenceChecker > const & ImpEditEngine::ImplGetInputSequenceChecker() const
+{
+ if ( !xISC.is() )
+ {
+ Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() );
+ xISC = i18n::InputSequenceChecker::create( xContext );
+ }
+ return xISC;
+}
+
+Color ImpEditEngine::GetAutoColor() const
+{
+ Color aColor;
+
+ if (comphelper::LibreOfficeKit::isActive() && SfxViewShell::Current())
+ {
+ // Get document background color from current view instead
+ aColor = SfxViewShell::Current()->GetColorConfigColor(svtools::DOCCOLOR);
+ if (aColor.IsDark())
+ aColor = COL_WHITE;
+ else
+ aColor = COL_BLACK;
+ }
+ else
+ {
+ aColor = GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor;
+
+ if ( GetBackgroundColor() != COL_AUTO )
+ {
+ if ( GetBackgroundColor().IsDark() && aColor.IsDark() )
+ aColor = COL_WHITE;
+ else if ( GetBackgroundColor().IsBright() && aColor.IsBright() )
+ aColor = COL_BLACK;
+ }
+ }
+
+ return aColor;
+}
+
+bool ImpEditEngine::ImplCalcAsianCompression(ContentNode* pNode,
+ TextPortion* pTextPortion, sal_Int32 nStartPos,
+ sal_Int32* pDXArray, sal_uInt16 n100thPercentFromMax,
+ bool bManipulateDXArray)
+{
+ DBG_ASSERT( GetAsianCompressionMode() != CharCompressType::NONE, "ImplCalcAsianCompression - Why?" );
+ DBG_ASSERT( pTextPortion->GetLen(), "ImplCalcAsianCompression - Empty Portion?" );
+
+ // Percent is 1/100 Percent...
+ if ( n100thPercentFromMax == 10000 )
+ pTextPortion->SetExtraInfos( nullptr );
+
+ bool bCompressed = false;
+
+ if ( GetI18NScriptType( EditPaM( pNode, nStartPos+1 ) ) == i18n::ScriptType::ASIAN )
+ {
+ tools::Long nNewPortionWidth = pTextPortion->GetSize().Width();
+ sal_Int32 nPortionLen = pTextPortion->GetLen();
+ for ( sal_Int32 n = 0; n < nPortionLen; n++ )
+ {
+ AsianCompressionFlags nType = GetCharTypeForCompression( pNode->GetChar( n+nStartPos ) );
+
+ bool bCompressPunctuation = ( nType == AsianCompressionFlags::PunctuationLeft ) || ( nType == AsianCompressionFlags::PunctuationRight );
+ bool bCompressKana = ( nType == AsianCompressionFlags::Kana ) && ( GetAsianCompressionMode() == CharCompressType::PunctuationAndKana );
+
+ // create Extra infos only if needed...
+ if ( bCompressPunctuation || bCompressKana )
+ {
+ if ( !pTextPortion->GetExtraInfos() )
+ {
+ ExtraPortionInfo* pExtraInfos = new ExtraPortionInfo;
+ pTextPortion->SetExtraInfos( pExtraInfos );
+ pExtraInfos->nOrgWidth = pTextPortion->GetSize().Width();
+ pExtraInfos->nAsianCompressionTypes = AsianCompressionFlags::Normal;
+ }
+ pTextPortion->GetExtraInfos()->nMaxCompression100thPercent = n100thPercentFromMax;
+ pTextPortion->GetExtraInfos()->nAsianCompressionTypes |= nType;
+
+ tools::Long nOldCharWidth;
+ if ( (n+1) < nPortionLen )
+ {
+ nOldCharWidth = pDXArray[n];
+ }
+ else
+ {
+ if ( bManipulateDXArray )
+ nOldCharWidth = nNewPortionWidth - pTextPortion->GetExtraInfos()->nPortionOffsetX;
+ else
+ nOldCharWidth = pTextPortion->GetExtraInfos()->nOrgWidth;
+ }
+ nOldCharWidth -= ( n ? pDXArray[n-1] : 0 );
+
+ tools::Long nCompress = 0;
+
+ if ( bCompressPunctuation )
+ {
+ nCompress = nOldCharWidth / 2;
+ }
+ else // Kana
+ {
+ nCompress = nOldCharWidth / 10;
+ }
+
+ if ( n100thPercentFromMax != 10000 )
+ {
+ nCompress *= n100thPercentFromMax;
+ nCompress /= 10000;
+ }
+
+ if ( nCompress )
+ {
+ bCompressed = true;
+ nNewPortionWidth -= nCompress;
+ pTextPortion->GetExtraInfos()->bCompressed = true;
+
+
+ // Special handling for rightpunctuation: For the 'compression' we must
+ // start the output before the normal char position...
+ if ( bManipulateDXArray && ( pTextPortion->GetLen() > 1 ) )
+ {
+ if ( !pTextPortion->GetExtraInfos()->pOrgDXArray )
+ pTextPortion->GetExtraInfos()->SaveOrgDXArray( pDXArray, pTextPortion->GetLen()-1 );
+
+ if ( nType == AsianCompressionFlags::PunctuationRight )
+ {
+ // If it's the first char, I must handle it in Paint()...
+ if ( n )
+ {
+ // -1: No entry for the last character
+ for ( sal_Int32 i = n-1; i < (nPortionLen-1); i++ )
+ pDXArray[i] -= nCompress;
+ }
+ else
+ {
+ pTextPortion->GetExtraInfos()->bFirstCharIsRightPunktuation = true;
+ pTextPortion->GetExtraInfos()->nPortionOffsetX = -nCompress;
+ }
+ }
+ else
+ {
+ // -1: No entry for the last character
+ for ( sal_Int32 i = n; i < (nPortionLen-1); i++ )
+ pDXArray[i] -= nCompress;
+ }
+ }
+ }
+ }
+ }
+
+ if ( bCompressed && ( n100thPercentFromMax == 10000 ) )
+ pTextPortion->GetExtraInfos()->nWidthFullCompression = nNewPortionWidth;
+
+ pTextPortion->setWidth(nNewPortionWidth);
+
+ if ( pTextPortion->GetExtraInfos() && ( n100thPercentFromMax != 10000 ) )
+ {
+ // Maybe rounding errors in nNewPortionWidth, assure that width not bigger than expected
+ tools::Long nShrink = pTextPortion->GetExtraInfos()->nOrgWidth - pTextPortion->GetExtraInfos()->nWidthFullCompression;
+ nShrink *= n100thPercentFromMax;
+ nShrink /= 10000;
+ tools::Long nNewWidth = pTextPortion->GetExtraInfos()->nOrgWidth - nShrink;
+ if ( nNewWidth < pTextPortion->GetSize().Width() )
+ pTextPortion->setWidth(nNewWidth);
+ }
+ }
+ return bCompressed;
+}
+
+
+void ImpEditEngine::ImplExpandCompressedPortions( EditLine* pLine, ParaPortion* pParaPortion, tools::Long nRemainingWidth )
+{
+ bool bFoundCompressedPortion = false;
+ tools::Long nCompressed = 0;
+ std::vector<TextPortion*> aCompressedPortions;
+
+ sal_Int32 nPortion = pLine->GetEndPortion();
+ TextPortion* pTP = &pParaPortion->GetTextPortions()[ nPortion ];
+ while ( pTP && ( pTP->GetKind() == PortionKind::TEXT ) )
+ {
+ if ( pTP->GetExtraInfos() && pTP->GetExtraInfos()->bCompressed )
+ {
+ bFoundCompressedPortion = true;
+ nCompressed += pTP->GetExtraInfos()->nOrgWidth - pTP->GetSize().Width();
+ aCompressedPortions.push_back(pTP);
+ }
+ pTP = ( nPortion > pLine->GetStartPortion() ) ? &pParaPortion->GetTextPortions()[ --nPortion ] : nullptr;
+ }
+
+ if ( !bFoundCompressedPortion )
+ return;
+
+ tools::Long nCompressPercent = 0;
+ if ( nCompressed > nRemainingWidth )
+ {
+ nCompressPercent = nCompressed - nRemainingWidth;
+ DBG_ASSERT( nCompressPercent < 200000, "ImplExpandCompressedPortions - Overflow!" );
+ nCompressPercent *= 10000;
+ nCompressPercent /= nCompressed;
+ }
+
+ for (TextPortion* pTP2 : aCompressedPortions)
+ {
+ pTP = pTP2;
+ pTP->GetExtraInfos()->bCompressed = false;
+ pTP->setWidth(pTP->GetExtraInfos()->nOrgWidth);
+ if ( nCompressPercent )
+ {
+ sal_Int32 nTxtPortion = pParaPortion->GetTextPortions().GetPos( pTP );
+ sal_Int32 nTxtPortionStart = pParaPortion->GetTextPortions().GetStartPos( nTxtPortion );
+ DBG_ASSERT( nTxtPortionStart >= pLine->GetStart(), "Portion doesn't belong to the line!!!" );
+ sal_Int32* pDXArray = pLine->GetCharPosArray().data() + (nTxtPortionStart - pLine->GetStart());
+ if ( pTP->GetExtraInfos()->pOrgDXArray )
+ memcpy( pDXArray, pTP->GetExtraInfos()->pOrgDXArray.get(), (pTP->GetLen()-1)*sizeof(sal_Int32) );
+ ImplCalcAsianCompression( pParaPortion->GetNode(), pTP, nTxtPortionStart, pDXArray, static_cast<sal_uInt16>(nCompressPercent), true );
+ }
+ }
+}
+
+void ImpEditEngine::ImplUpdateOverflowingParaNum(tools::Long nPaperHeight)
+{
+ tools::Long nY = 0;
+ tools::Long nPH;
+
+ for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ ) {
+ ParaPortion* pPara = GetParaPortions()[nPara];
+ nPH = pPara->GetHeight();
+ nY += nPH;
+ if ( nY > nPaperHeight /*nCurTextHeight*/ ) // found first paragraph overflowing
+ {
+ mnOverflowingPara = nPara;
+ SAL_INFO("editeng.chaining", "[CHAINING] Setting first overflowing #Para#: " << nPara);
+ ImplUpdateOverflowingLineNum( nPaperHeight, nPara, nY-nPH);
+ return;
+ }
+ }
+}
+
+void ImpEditEngine::ImplUpdateOverflowingLineNum(tools::Long nPaperHeight,
+ sal_uInt32 nOverflowingPara,
+ tools::Long nHeightBeforeOverflowingPara)
+{
+ tools::Long nY = nHeightBeforeOverflowingPara;
+ tools::Long nLH;
+
+ ParaPortion *pPara = GetParaPortions()[nOverflowingPara];
+
+ // Like UpdateOverflowingParaNum but for each line in the first
+ // overflowing paragraph.
+ for ( sal_Int32 nLine = 0; nLine < pPara->GetLines().Count(); nLine++ ) {
+ // XXX: We must use a reference here because the copy constructor resets the height
+ EditLine &aLine = pPara->GetLines()[nLine];
+ nLH = aLine.GetHeight();
+ nY += nLH;
+
+ // Debugging output
+ if (nLine == 0) {
+ SAL_INFO("editeng.chaining", "[CHAINING] First line has height " << nLH);
+ }
+
+ if ( nY > nPaperHeight ) // found first line overflowing
+ {
+ mnOverflowingLine = nLine;
+ SAL_INFO("editeng.chaining", "[CHAINING] Setting first overflowing -Line- to: " << nLine);
+ return;
+ }
+ }
+
+ assert(false && "You should never get here");
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/impedit4.cxx b/editeng/source/editeng/impedit4.cxx
new file mode 100644
index 0000000000..57b3d65c54
--- /dev/null
+++ b/editeng/source/editeng/impedit4.cxx
@@ -0,0 +1,3141 @@
+/* -*- 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 <svl/srchitem.hxx>
+#include <editeng/adjustitem.hxx>
+#include <editeng/cmapitem.hxx>
+#include <editeng/lspcitem.hxx>
+#include <editeng/tstpitem.hxx>
+
+#include "eertfpar.hxx"
+#include <editeng/editeng.hxx>
+#include "impedit.hxx"
+#include <editeng/editview.hxx>
+#include "eehtml.hxx"
+#include "editobj2.hxx"
+#include <i18nlangtag/lang.h>
+#include <sal/log.hxx>
+#include <o3tl/safeint.hxx>
+#include <osl/diagnose.h>
+#include <osl/thread.h>
+
+#include <editxml.hxx>
+
+#include <editeng/autokernitem.hxx>
+#include <editeng/contouritem.hxx>
+#include <editeng/colritem.hxx>
+#include <editeng/crossedoutitem.hxx>
+#include <editeng/escapementitem.hxx>
+#include <editeng/fhgtitem.hxx>
+#include <editeng/fontitem.hxx>
+#include <editeng/kernitem.hxx>
+#include <editeng/lrspitem.hxx>
+#include <editeng/postitem.hxx>
+#include <editeng/shdditem.hxx>
+#include <editeng/udlnitem.hxx>
+#include <editeng/ulspitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/charreliefitem.hxx>
+#include <editeng/frmdiritem.hxx>
+#include <editeng/emphasismarkitem.hxx>
+#include "textconv.hxx"
+#include <rtl/tencinfo.h>
+#include <svtools/rtfout.hxx>
+#include <tools/stream.hxx>
+#include <edtspell.hxx>
+#include <editeng/unolingu.hxx>
+#include <com/sun/star/linguistic2/XThesaurus.hpp>
+#include <com/sun/star/i18n/ScriptType.hpp>
+#include <com/sun/star/i18n/WordType.hpp>
+#include <unotools/transliterationwrapper.hxx>
+#include <unotools/textsearch.hxx>
+#include <comphelper/processfactory.hxx>
+#include <vcl/help.hxx>
+#include <vcl/metric.hxx>
+#include <svtools/rtfkeywd.hxx>
+#include <editeng/edtdlg.hxx>
+
+#include <cstddef>
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::linguistic2;
+
+
+EditPaM ImpEditEngine::Read(SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat, const EditSelection& rSel, SvKeyValueIterator* pHTTPHeaderAttrs)
+{
+ bool _bUpdate = SetUpdateLayout( false );
+ EditPaM aPaM;
+ if ( eFormat == EETextFormat::Text )
+ aPaM = ReadText( rInput, rSel );
+ else if ( eFormat == EETextFormat::Rtf )
+ aPaM = ReadRTF( rInput, rSel );
+ else if ( eFormat == EETextFormat::Xml )
+ aPaM = ReadXML( rInput, rSel );
+ else if ( eFormat == EETextFormat::Html )
+ aPaM = ReadHTML( rInput, rBaseURL, rSel, pHTTPHeaderAttrs );
+ else
+ {
+ OSL_FAIL( "Read: Unknown Format" );
+ }
+
+ FormatFullDoc(); // perhaps a simple format is enough?
+ SetUpdateLayout( _bUpdate );
+
+ return aPaM;
+}
+
+EditPaM ImpEditEngine::ReadText( SvStream& rInput, EditSelection aSel )
+{
+ if ( aSel.HasRange() )
+ aSel = ImpDeleteSelection( aSel );
+ EditPaM aPaM = aSel.Max();
+
+ OUString aTmpStr;
+ bool bDone = rInput.ReadByteStringLine( aTmpStr, rInput.GetStreamCharSet() );
+ while ( bDone )
+ {
+ aPaM = ImpInsertText( EditSelection( aPaM, aPaM ), aTmpStr );
+ aPaM = ImpInsertParaBreak( aPaM );
+ bDone = rInput.ReadByteStringLine( aTmpStr, rInput.GetStreamCharSet() );
+ }
+ return aPaM;
+}
+
+EditPaM ImpEditEngine::ReadXML( SvStream& rInput, EditSelection aSel )
+{
+ if ( aSel.HasRange() )
+ aSel = ImpDeleteSelection( aSel );
+
+ ESelection aESel = CreateESel( aSel );
+
+ return ::SvxReadXML( *GetEditEnginePtr(), rInput, aESel );
+}
+
+EditPaM ImpEditEngine::ReadRTF( SvStream& rInput, EditSelection aSel )
+{
+ if ( aSel.HasRange() )
+ aSel = ImpDeleteSelection( aSel );
+
+ // The SvRTF parser expects the Which-mapping passed on in the pool, not
+ // dependent on a secondary.
+ SfxItemPool* pPool = &maEditDoc.GetItemPool();
+ while (pPool->GetSecondaryPool() && pPool->GetName() != "EditEngineItemPool")
+ {
+ pPool = pPool->GetSecondaryPool();
+ }
+
+ DBG_ASSERT(pPool && pPool->GetName() == "EditEngineItemPool",
+ "ReadRTF: no EditEnginePool!");
+
+ EditRTFParserRef xPrsr = new EditRTFParser(rInput, aSel, *pPool, pEditEngine);
+ SvParserState eState = xPrsr->CallParser();
+ if ( ( eState != SvParserState::Accepted ) && ( !rInput.GetError() ) )
+ {
+ rInput.SetError( EE_READWRITE_WRONGFORMAT );
+ return aSel.Min();
+ }
+ return xPrsr->GetCurPaM();
+}
+
+EditPaM ImpEditEngine::ReadHTML( SvStream& rInput, const OUString& rBaseURL, EditSelection aSel, SvKeyValueIterator* pHTTPHeaderAttrs )
+{
+ if ( aSel.HasRange() )
+ aSel = ImpDeleteSelection( aSel );
+
+ EditHTMLParserRef xPrsr = new EditHTMLParser( rInput, rBaseURL, pHTTPHeaderAttrs );
+ SvParserState eState = xPrsr->CallParser(pEditEngine, aSel.Max());
+ if ( ( eState != SvParserState::Accepted ) && ( !rInput.GetError() ) )
+ {
+ rInput.SetError( EE_READWRITE_WRONGFORMAT );
+ return aSel.Min();
+ }
+ return xPrsr->GetCurSelection().Max();
+}
+
+void ImpEditEngine::Write(SvStream& rOutput, EETextFormat eFormat, const EditSelection& rSel)
+{
+ if ( !rOutput.IsWritable() )
+ rOutput.SetError( SVSTREAM_WRITE_ERROR );
+
+ if ( rOutput.GetError() )
+ return;
+
+ if ( eFormat == EETextFormat::Text )
+ WriteText( rOutput, rSel );
+ else if ( eFormat == EETextFormat::Rtf )
+ WriteRTF( rOutput, rSel );
+ else if ( eFormat == EETextFormat::Xml )
+ WriteXML( rOutput, rSel );
+ else if ( eFormat == EETextFormat::Html )
+ ;
+ else
+ {
+ OSL_FAIL( "Write: Unknown Format" );
+ }
+}
+
+ErrCode ImpEditEngine::WriteText( SvStream& rOutput, EditSelection aSel )
+{
+ sal_Int32 nStartNode, nEndNode;
+ bool bRange = aSel.HasRange();
+ if ( bRange )
+ {
+ aSel.Adjust( maEditDoc );
+ nStartNode = maEditDoc.GetPos( aSel.Min().GetNode() );
+ nEndNode = maEditDoc.GetPos( aSel.Max().GetNode() );
+ }
+ else
+ {
+ nStartNode = 0;
+ nEndNode = maEditDoc.Count()-1;
+ }
+
+ // iterate over the paragraphs ...
+ for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ )
+ {
+ ContentNode* pNode = maEditDoc.GetObject( nNode );
+ DBG_ASSERT( pNode, "Node not found: Search&Replace" );
+
+ sal_Int32 nStartPos = 0;
+ sal_Int32 nEndPos = pNode->Len();
+ if ( bRange )
+ {
+ if ( nNode == nStartNode )
+ nStartPos = aSel.Min().GetIndex();
+ if ( nNode == nEndNode ) // can also be == nStart!
+ nEndPos = aSel.Max().GetIndex();
+ }
+ OUString aTmpStr = EditDoc::GetParaAsString( pNode, nStartPos, nEndPos );
+ rOutput.WriteByteStringLine( aTmpStr, rOutput.GetStreamCharSet() );
+ }
+
+ return rOutput.GetError();
+}
+
+bool ImpEditEngine::WriteItemListAsRTF( ItemList& rLst, SvStream& rOutput, sal_Int32 nPara, sal_Int32 nPos,
+ std::vector<std::unique_ptr<SvxFontItem>>& rFontTable, SvxColorList& rColorList )
+{
+ const SfxPoolItem* pAttrItem = rLst.First();
+ while ( pAttrItem )
+ {
+ WriteItemAsRTF( *pAttrItem, rOutput, nPara, nPos,rFontTable, rColorList );
+ pAttrItem = rLst.Next();
+ }
+ return rLst.Count() != 0;
+}
+
+static void lcl_FindValidAttribs( ItemList& rLst, ContentNode* pNode, sal_Int32 nIndex, sal_uInt16 nScriptType )
+{
+ std::size_t nAttr = 0;
+ EditCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
+ while ( pAttr && ( pAttr->GetStart() <= nIndex ) )
+ {
+ // Start is checked in while ...
+ if ( pAttr->GetEnd() > nIndex )
+ {
+ if ( IsScriptItemValid( pAttr->GetItem()->Which(), nScriptType ) )
+ rLst.Insert( pAttr->GetItem() );
+ }
+ nAttr++;
+ pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
+ }
+}
+
+void ImpEditEngine::WriteXML(SvStream& rOutput, const EditSelection& rSel)
+{
+ ESelection aESel = CreateESel(rSel);
+
+ SvxWriteXML( *GetEditEnginePtr(), rOutput, aESel );
+}
+
+ErrCode ImpEditEngine::WriteRTF( SvStream& rOutput, EditSelection aSel )
+{
+ assert( IsUpdateLayout() && "WriteRTF for UpdateMode = sal_False!" );
+ CheckIdleFormatter();
+ if ( !IsFormatted() )
+ FormatDoc();
+
+ sal_Int32 nStartNode, nEndNode;
+ aSel.Adjust( maEditDoc );
+
+ nStartNode = maEditDoc.GetPos( aSel.Min().GetNode() );
+ nEndNode = maEditDoc.GetPos( aSel.Max().GetNode() );
+
+ // RTF header ...
+ rOutput.WriteChar( '{' ) ;
+
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_RTF );
+
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_ANSI );
+ rtl_TextEncoding eDestEnc = RTL_TEXTENCODING_MS_1252;
+
+ // Generate and write out Font table ...
+ std::vector<std::unique_ptr<SvxFontItem>> aFontTable;
+ // default font must be up front, so DEF font in RTF
+ aFontTable.emplace_back( new SvxFontItem( maEditDoc.GetItemPool().GetDefaultItem( EE_CHAR_FONTINFO ) ) );
+ aFontTable.emplace_back( new SvxFontItem( maEditDoc.GetItemPool().GetDefaultItem( EE_CHAR_FONTINFO_CJK ) ) );
+ aFontTable.emplace_back( new SvxFontItem( maEditDoc.GetItemPool().GetDefaultItem( EE_CHAR_FONTINFO_CTL ) ) );
+ for ( sal_uInt16 nScriptType = 0; nScriptType < 3; nScriptType++ )
+ {
+ sal_uInt16 nWhich = EE_CHAR_FONTINFO;
+ if ( nScriptType == 1 )
+ nWhich = EE_CHAR_FONTINFO_CJK;
+ else if ( nScriptType == 2 )
+ nWhich = EE_CHAR_FONTINFO_CTL;
+
+ for (const SfxPoolItem* pItem : maEditDoc.GetItemPool().GetItemSurrogates(nWhich))
+ {
+ SvxFontItem const*const pFontItem = static_cast<const SvxFontItem*>(pItem);
+ bool bAlreadyExist = false;
+ size_t nTestMax = nScriptType ? aFontTable.size() : 1;
+ for ( size_t nTest = 0; !bAlreadyExist && ( nTest < nTestMax ); nTest++ )
+ {
+ bAlreadyExist = *aFontTable[ nTest ] == *pFontItem;
+ }
+
+ if ( !bAlreadyExist )
+ aFontTable.emplace_back( new SvxFontItem( *pFontItem ) );
+ }
+ }
+
+ rOutput << endl;
+ rOutput.WriteChar( '{' ).WriteOString( OOO_STRING_SVTOOLS_RTF_FONTTBL );
+ for ( std::vector<SvxFontItem*>::size_type j = 0; j < aFontTable.size(); j++ )
+ {
+ SvxFontItem* pFontItem = aFontTable[ j ].get();
+ rOutput.WriteChar( '{' );
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_F );
+ rOutput.WriteNumberAsString( j );
+ switch ( pFontItem->GetFamily() )
+ {
+ case FAMILY_DONTKNOW: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FNIL );
+ break;
+ case FAMILY_DECORATIVE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FDECOR );
+ break;
+ case FAMILY_MODERN: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FMODERN );
+ break;
+ case FAMILY_ROMAN: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FROMAN );
+ break;
+ case FAMILY_SCRIPT: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FSCRIPT );
+ break;
+ case FAMILY_SWISS: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FSWISS );
+ break;
+ default:
+ break;
+ }
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FPRQ );
+ sal_uInt16 nVal = 0;
+ switch( pFontItem->GetPitch() )
+ {
+ case PITCH_FIXED: nVal = 1; break;
+ case PITCH_VARIABLE: nVal = 2; break;
+ default:
+ break;
+ }
+ rOutput.WriteNumberAsString( nVal );
+
+ rtl_TextEncoding eChrSet = pFontItem->GetCharSet();
+ // tdf#47679 OpenSymbol is not encoded in Symbol Encoding
+ // and anyway we always attempt to write as eDestEnc
+ // of RTL_TEXTENCODING_MS_1252 and pay no attention
+ // on export what encoding we claim to use for these
+ // fonts.
+ if (IsOpenSymbol(pFontItem->GetFamilyName()))
+ {
+ SAL_WARN_IF(eChrSet == RTL_TEXTENCODING_SYMBOL, "editeng", "OpenSymbol should not have charset of RTL_TEXTENCODING_SYMBOL in new documents");
+ eChrSet = RTL_TEXTENCODING_UTF8;
+ }
+ DBG_ASSERT( eChrSet != 9, "SystemCharSet?!" );
+ if( RTL_TEXTENCODING_DONTKNOW == eChrSet )
+ eChrSet = osl_getThreadTextEncoding();
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FCHARSET );
+ rOutput.WriteNumberAsString( rtl_getBestWindowsCharsetFromTextEncoding( eChrSet ) );
+
+ rOutput.WriteChar( ' ' );
+ RTFOutFuncs::Out_String( rOutput, pFontItem->GetFamilyName(), eDestEnc );
+ rOutput.WriteOString( ";}" );
+ }
+ rOutput.WriteChar( '}' );
+ rOutput << endl;
+
+ // Write out ColorList ...
+ SvxColorList aColorList;
+ // COL_AUTO should be the default color, always put it first
+ aColorList.emplace_back(COL_AUTO);
+ SvxColorItem const& rDefault(maEditDoc.GetItemPool().GetDefaultItem(EE_CHAR_COLOR));
+ if (rDefault.GetValue() != COL_AUTO) // is the default always AUTO?
+ {
+ aColorList.push_back(rDefault.GetValue());
+ }
+ for (const SfxPoolItem* pItem : maEditDoc.GetItemPool().GetItemSurrogates(EE_CHAR_COLOR))
+ {
+ auto pColorItem(dynamic_cast<SvxColorItem const*>(pItem));
+ if (pColorItem && pColorItem->GetValue() != COL_AUTO) // may be null!
+ {
+ aColorList.push_back(pColorItem->GetValue());
+ }
+ }
+
+ rOutput.WriteChar( '{' ).WriteOString( OOO_STRING_SVTOOLS_RTF_COLORTBL );
+ for ( SvxColorList::size_type j = 0; j < aColorList.size(); j++ )
+ {
+ Color const color = aColorList[j];
+ if (color != COL_AUTO) // auto is represented by "empty" element
+ {
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_RED );
+ rOutput.WriteNumberAsString( color.GetRed() );
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_GREEN );
+ rOutput.WriteNumberAsString( color.GetGreen() );
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_BLUE );
+ rOutput.WriteNumberAsString( color.GetBlue() );
+ }
+ rOutput.WriteChar( ';' );
+ }
+ rOutput.WriteChar( '}' );
+ rOutput << endl;
+
+ std::unordered_map<SfxStyleSheetBase*, sal_uInt32> aStyleSheetToIdMap;
+ // StyleSheets...
+ if ( GetStyleSheetPool() )
+ {
+ std::shared_ptr<SfxStyleSheetIterator> aSSSIterator = std::make_shared<SfxStyleSheetIterator>(GetStyleSheetPool(),
+ SfxStyleFamily::All);
+ // fill aStyleSheetToIdMap
+ sal_uInt32 nId = 1;
+ for ( SfxStyleSheetBase* pStyle = aSSSIterator->First(); pStyle;
+ pStyle = aSSSIterator->Next() )
+ {
+ aStyleSheetToIdMap[pStyle] = nId;
+ nId++;
+ }
+
+ if ( aSSSIterator->Count() )
+ {
+
+ sal_uInt32 nStyle = 0;
+ rOutput.WriteChar( '{' ).WriteOString( OOO_STRING_SVTOOLS_RTF_STYLESHEET );
+
+ for ( SfxStyleSheetBase* pStyle = aSSSIterator->First(); pStyle;
+ pStyle = aSSSIterator->Next() )
+ {
+
+ rOutput << endl;
+ rOutput.WriteChar( '{' ).WriteOString( OOO_STRING_SVTOOLS_RTF_S );
+ sal_uInt32 nNumber = nStyle + 1;
+ rOutput.WriteNumberAsString( nNumber );
+
+ // Attribute, also from Parent!
+ for ( sal_uInt16 nParAttr = EE_PARA_START; nParAttr <= EE_CHAR_END; nParAttr++ )
+ {
+ if ( pStyle->GetItemSet().GetItemState( nParAttr ) == SfxItemState::SET )
+ {
+ const SfxPoolItem& rItem = pStyle->GetItemSet().Get( nParAttr );
+ WriteItemAsRTF( rItem, rOutput, 0, 0, aFontTable, aColorList );
+ }
+ }
+
+ // Parent ... (only if necessary)
+ if ( !pStyle->GetParent().isEmpty() && ( pStyle->GetParent() != pStyle->GetName() ) )
+ {
+ SfxStyleSheet* pParent = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( pStyle->GetParent(), pStyle->GetFamily() ));
+ DBG_ASSERT( pParent, "Parent not found!" );
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_SBASEDON );
+ nNumber = aStyleSheetToIdMap.find(pParent)->second;
+ rOutput.WriteNumberAsString( nNumber );
+ }
+
+ // Next Style... (more)
+ // we assume that we have only SfxStyleSheet in the pool
+ SfxStyleSheet* pNext = static_cast<SfxStyleSheet*>(pStyle);
+ if ( !pStyle->GetFollow().isEmpty() && ( pStyle->GetFollow() != pStyle->GetName() ) )
+ pNext = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( pStyle->GetFollow(), pStyle->GetFamily() ));
+
+ DBG_ASSERT( pNext, "Next not found!" );
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_SNEXT );
+ nNumber = aStyleSheetToIdMap.find(pNext)->second;
+ rOutput.WriteNumberAsString( nNumber );
+
+ // Name of the template...
+ rOutput.WriteOString( " " );
+ RTFOutFuncs::Out_String( rOutput, pStyle->GetName(), eDestEnc );
+ rOutput.WriteOString( ";}" );
+ nStyle++;
+ }
+ rOutput.WriteChar( '}' );
+ rOutput << endl;
+ }
+ }
+
+ // Write the pool defaults in advance ...
+ rOutput.WriteChar( '{' ).WriteOString( OOO_STRING_SVTOOLS_RTF_IGNORE ).WriteOString( "\\EditEnginePoolDefaults" );
+ for ( sal_uInt16 nPoolDefItem = EE_PARA_START; nPoolDefItem <= EE_CHAR_END; nPoolDefItem++)
+ {
+ const SfxPoolItem& rItem = maEditDoc.GetItemPool().GetDefaultItem( nPoolDefItem );
+ WriteItemAsRTF( rItem, rOutput, 0, 0, aFontTable, aColorList );
+ }
+ rOutput.WriteChar( '}' ) << endl;
+
+ // DefTab:
+ MapMode aTwpMode( MapUnit::MapTwip );
+ sal_uInt16 nDefTabTwps = static_cast<sal_uInt16>(GetRefDevice()->LogicToLogic(
+ Point( maEditDoc.GetDefTab(), 0 ),
+ &GetRefMapMode(), &aTwpMode ).X());
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_DEFTAB );
+ rOutput.WriteNumberAsString( nDefTabTwps );
+ rOutput << endl;
+
+ // iterate over the paragraphs ...
+ rOutput.WriteChar( '{' ) << endl;
+ for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ )
+ {
+ ContentNode* pNode = maEditDoc.GetObject( nNode );
+ DBG_ASSERT( pNode, "Node not found: Search&Replace" );
+
+ // The paragraph attributes in advance ...
+ bool bAttr = false;
+
+ // Template?
+ if ( pNode->GetStyleSheet() )
+ {
+ // Number of template
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_S );
+ sal_uInt32 nNumber = aStyleSheetToIdMap.find(pNode->GetStyleSheet())->second;
+ rOutput.WriteNumberAsString( nNumber );
+
+ // All Attribute
+ // Attribute, also from Parent!
+ for ( sal_uInt16 nParAttr = EE_PARA_START; nParAttr <= EE_CHAR_END; nParAttr++ )
+ {
+ if ( pNode->GetStyleSheet()->GetItemSet().GetItemState( nParAttr ) == SfxItemState::SET )
+ {
+ const SfxPoolItem& rItem = pNode->GetStyleSheet()->GetItemSet().Get( nParAttr );
+ WriteItemAsRTF( rItem, rOutput, nNode, 0, aFontTable, aColorList );
+ bAttr = true;
+ }
+ }
+ }
+
+ for ( sal_uInt16 nParAttr = EE_PARA_START; nParAttr <= EE_CHAR_END; nParAttr++ )
+ {
+ // Now where stylesheet processing, only hard paragraph attributes!
+ if ( pNode->GetContentAttribs().GetItems().GetItemState( nParAttr ) == SfxItemState::SET )
+ {
+ const SfxPoolItem& rItem = pNode->GetContentAttribs().GetItems().Get( nParAttr );
+ WriteItemAsRTF( rItem, rOutput, nNode, 0, aFontTable, aColorList );
+ bAttr = true;
+ }
+ }
+ if ( bAttr )
+ rOutput.WriteChar( ' ' ); // Separator
+
+ ItemList aAttribItems;
+ ParaPortion* pParaPortion = FindParaPortion( pNode );
+ DBG_ASSERT( pParaPortion, "Portion not found: WriteRTF" );
+
+ sal_Int32 nIndex = 0;
+ sal_Int32 nStartPos = 0;
+ sal_Int32 nEndPos = pNode->Len();
+ sal_Int32 nStartPortion = 0;
+ sal_Int32 nEndPortion = pParaPortion->GetTextPortions().Count() - 1;
+ bool bFinishPortion = false;
+ sal_Int32 nPortionStart;
+
+ if ( nNode == nStartNode )
+ {
+ nStartPos = aSel.Min().GetIndex();
+ nStartPortion = pParaPortion->GetTextPortions().FindPortion( nStartPos, nPortionStart );
+ if ( nStartPos != 0 )
+ {
+ aAttribItems.Clear();
+ lcl_FindValidAttribs( aAttribItems, pNode, nStartPos, GetI18NScriptType( EditPaM( pNode, 0 ) ) );
+ if ( aAttribItems.Count() )
+ {
+ // These attributes may not apply to the entire paragraph:
+ rOutput.WriteChar( '{' );
+ WriteItemListAsRTF( aAttribItems, rOutput, nNode, nStartPos, aFontTable, aColorList );
+ bFinishPortion = true;
+ }
+ aAttribItems.Clear();
+ }
+ }
+ if ( nNode == nEndNode ) // can also be == nStart!
+ {
+ nEndPos = aSel.Max().GetIndex();
+ nEndPortion = pParaPortion->GetTextPortions().FindPortion( nEndPos, nPortionStart );
+ }
+
+ const EditCharAttrib* pNextFeature = pNode->GetCharAttribs().FindFeature(nIndex);
+ // start at 0, so the index is right ...
+ for ( sal_Int32 n = 0; n <= nEndPortion; n++ )
+ {
+ const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[n];
+ if ( n < nStartPortion )
+ {
+ nIndex = nIndex + rTextPortion.GetLen();
+ continue;
+ }
+
+ if ( pNextFeature && ( pNextFeature->GetStart() == nIndex ) && ( pNextFeature->GetItem()->Which() != EE_FEATURE_FIELD ) )
+ {
+ WriteItemAsRTF( *pNextFeature->GetItem(), rOutput, nNode, nIndex, aFontTable, aColorList );
+ pNextFeature = pNode->GetCharAttribs().FindFeature( pNextFeature->GetStart() + 1 );
+ }
+ else
+ {
+ aAttribItems.Clear();
+ sal_uInt16 nScriptTypeI18N = GetI18NScriptType( EditPaM( pNode, nIndex+1 ) );
+ SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N);
+ if ( !n || IsScriptChange( EditPaM( pNode, nIndex ) ) )
+ {
+ SfxItemSet aAttribs = GetAttribs( nNode, nIndex+1, nIndex+1 );
+ aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_FONTINFO, nScriptType ) ) );
+ aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType ) ) );
+ aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_WEIGHT, nScriptType ) ) );
+ aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_ITALIC, nScriptType ) ) );
+ aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ) ) );
+ }
+ // Insert hard attribs AFTER CJK attribs...
+ lcl_FindValidAttribs( aAttribItems, pNode, nIndex, nScriptTypeI18N );
+
+ rOutput.WriteChar( '{' );
+ if ( WriteItemListAsRTF( aAttribItems, rOutput, nNode, nIndex, aFontTable, aColorList ) )
+ rOutput.WriteChar( ' ' );
+
+ sal_Int32 nS = nIndex;
+ sal_Int32 nE = nIndex + rTextPortion.GetLen();
+ if ( n == nStartPortion )
+ nS = nStartPos;
+ if ( n == nEndPortion )
+ nE = nEndPos;
+
+ OUString aRTFStr = EditDoc::GetParaAsString( pNode, nS, nE);
+ RTFOutFuncs::Out_String( rOutput, aRTFStr, eDestEnc );
+ rOutput.WriteChar( '}' );
+ }
+ if ( bFinishPortion )
+ {
+ rOutput.WriteChar( '}' );
+ bFinishPortion = false;
+ }
+
+ nIndex = nIndex + rTextPortion.GetLen();
+ }
+
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_PAR ).WriteOString( OOO_STRING_SVTOOLS_RTF_PARD ).WriteOString( OOO_STRING_SVTOOLS_RTF_PLAIN );
+ rOutput << endl;
+ }
+ // RTF-trailer ...
+ rOutput.WriteOString( "}}" ); // 1xparentheses paragraphs, 1xparentheses RTF document
+
+ aFontTable.clear();
+
+ return rOutput.GetError();
+}
+
+
+void ImpEditEngine::WriteItemAsRTF( const SfxPoolItem& rItem, SvStream& rOutput, sal_Int32 nPara, sal_Int32 nPos,
+ std::vector<std::unique_ptr<SvxFontItem>>& rFontTable, SvxColorList& rColorList )
+{
+ sal_uInt16 nWhich = rItem.Which();
+ switch ( nWhich )
+ {
+ case EE_PARA_WRITINGDIR:
+ {
+ const SvxFrameDirectionItem& rWritingMode = static_cast<const SvxFrameDirectionItem&>(rItem);
+ if ( rWritingMode.GetValue() == SvxFrameDirection::Horizontal_RL_TB )
+ rOutput.WriteOString( "\\rtlpar" );
+ else
+ rOutput.WriteOString( "\\ltrpar" );
+ }
+ break;
+ case EE_PARA_OUTLLEVEL:
+ {
+ sal_Int32 nLevel = static_cast<const SfxInt16Item&>(rItem).GetValue();
+ if( nLevel >= 0 )
+ {
+ rOutput.WriteOString( "\\level" );
+ rOutput.WriteNumberAsString( nLevel );
+ }
+ }
+ break;
+ case EE_PARA_OUTLLRSPACE:
+ case EE_PARA_LRSPACE:
+ {
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FI );
+ sal_Int32 nTxtFirst = static_cast<const SvxLRSpaceItem&>(rItem).GetTextFirstLineOffset();
+ nTxtFirst = LogicToTwips( nTxtFirst );
+ rOutput.WriteNumberAsString( nTxtFirst );
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_LI );
+ sal_uInt32 nTxtLeft = static_cast< sal_uInt32 >(static_cast<const SvxLRSpaceItem&>(rItem).GetTextLeft());
+ nTxtLeft = static_cast<sal_uInt32>(LogicToTwips( nTxtLeft ));
+ rOutput.WriteNumberAsString( nTxtLeft );
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_RI );
+ sal_uInt32 nTxtRight = static_cast<const SvxLRSpaceItem&>(rItem).GetRight();
+ nTxtRight = LogicToTwips( nTxtRight);
+ rOutput.WriteNumberAsString( nTxtRight );
+ }
+ break;
+ case EE_PARA_ULSPACE:
+ {
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_SB );
+ sal_uInt32 nUpper = static_cast<const SvxULSpaceItem&>(rItem).GetUpper();
+ nUpper = static_cast<sal_uInt32>(LogicToTwips( nUpper ));
+ rOutput.WriteNumberAsString( nUpper );
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_SA );
+ sal_uInt32 nLower = static_cast<const SvxULSpaceItem&>(rItem).GetLower();
+ nLower = LogicToTwips( nLower );
+ rOutput.WriteNumberAsString( nLower );
+ }
+ break;
+ case EE_PARA_SBL:
+ {
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_SL );
+ sal_Int32 nVal = static_cast<const SvxLineSpacingItem&>(rItem).GetLineHeight();
+ char cMult = '0';
+ if ( static_cast<const SvxLineSpacingItem&>(rItem).GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop )
+ {
+ // From where do I get the value now?
+ // The SwRTF parser is based on a 240 Font!
+ nVal = static_cast<const SvxLineSpacingItem&>(rItem).GetPropLineSpace();
+ nVal *= 240;
+ nVal /= 100;
+ cMult = '1';
+ }
+ rOutput.WriteNumberAsString( nVal );
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_SLMULT ).WriteChar( cMult );
+ }
+ break;
+ case EE_PARA_JUST:
+ {
+ SvxAdjust eJustification = static_cast<const SvxAdjustItem&>(rItem).GetAdjust();
+ switch ( eJustification )
+ {
+ case SvxAdjust::Center: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_QC );
+ break;
+ case SvxAdjust::Right: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_QR );
+ break;
+ default: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_QL );
+ break;
+ }
+ }
+ break;
+ case EE_PARA_TABS:
+ {
+ const SvxTabStopItem& rTabs = static_cast<const SvxTabStopItem&>(rItem);
+ for ( sal_uInt16 i = 0; i < rTabs.Count(); i++ )
+ {
+ const SvxTabStop& rTab = rTabs[i];
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_TX );
+ rOutput.WriteNumberAsString( LogicToTwips( rTab.GetTabPos() ) );
+ }
+ }
+ break;
+ case EE_CHAR_COLOR:
+ {
+ SvxColorList::const_iterator const iter = std::find(
+ rColorList.begin(), rColorList.end(),
+ static_cast<SvxColorItem const&>(rItem).GetValue());
+ assert(iter != rColorList.end());
+ sal_uInt32 const n = iter - rColorList.begin();
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_CF );
+ rOutput.WriteNumberAsString( n );
+ }
+ break;
+ case EE_CHAR_FONTINFO:
+ case EE_CHAR_FONTINFO_CJK:
+ case EE_CHAR_FONTINFO_CTL:
+ {
+ sal_uInt32 n = 0;
+ for (size_t i = 0; i < rFontTable.size(); ++i)
+ {
+ if (*rFontTable[i] == rItem)
+ {
+ n = i;
+ break;
+ }
+ }
+
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_F );
+ rOutput.WriteNumberAsString( n );
+ }
+ break;
+ case EE_CHAR_FONTHEIGHT:
+ case EE_CHAR_FONTHEIGHT_CJK:
+ case EE_CHAR_FONTHEIGHT_CTL:
+ {
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_FS );
+ sal_Int32 nHeight = static_cast<const SvxFontHeightItem&>(rItem).GetHeight();
+ nHeight = LogicToTwips( nHeight );
+ // Twips => HalfPoints
+ nHeight /= 10;
+ rOutput.WriteNumberAsString( nHeight );
+ }
+ break;
+ case EE_CHAR_WEIGHT:
+ case EE_CHAR_WEIGHT_CJK:
+ case EE_CHAR_WEIGHT_CTL:
+ {
+ FontWeight e = static_cast<const SvxWeightItem&>(rItem).GetWeight();
+ switch ( e )
+ {
+ case WEIGHT_BOLD: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_B ); break;
+ default: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_B ).WriteChar( '0' ); break;
+ }
+ }
+ break;
+ case EE_CHAR_UNDERLINE:
+ {
+ // Must underlined if in WordLineMode, but the information is
+ // missing here
+ FontLineStyle e = static_cast<const SvxUnderlineItem&>(rItem).GetLineStyle();
+ switch ( e )
+ {
+ case LINESTYLE_NONE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_ULNONE ); break;
+ case LINESTYLE_SINGLE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_UL ); break;
+ case LINESTYLE_DOUBLE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_ULDB ); break;
+ case LINESTYLE_DOTTED: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_ULD ); break;
+ default:
+ break;
+ }
+ }
+ break;
+ case EE_CHAR_OVERLINE:
+ {
+ FontLineStyle e = static_cast<const SvxOverlineItem&>(rItem).GetLineStyle();
+ switch ( e )
+ {
+ case LINESTYLE_NONE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_OLNONE ); break;
+ case LINESTYLE_SINGLE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_OL ); break;
+ case LINESTYLE_DOUBLE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_OLDB ); break;
+ case LINESTYLE_DOTTED: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_OLD ); break;
+ default:
+ break;
+ }
+ }
+ break;
+ case EE_CHAR_STRIKEOUT:
+ {
+ FontStrikeout e = static_cast<const SvxCrossedOutItem&>(rItem).GetStrikeout();
+ switch ( e )
+ {
+ case STRIKEOUT_SINGLE:
+ case STRIKEOUT_DOUBLE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_STRIKE ); break;
+ case STRIKEOUT_NONE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_STRIKE ).WriteChar( '0' ); break;
+ default:
+ break;
+ }
+ }
+ break;
+ case EE_CHAR_ITALIC:
+ case EE_CHAR_ITALIC_CJK:
+ case EE_CHAR_ITALIC_CTL:
+ {
+ FontItalic e = static_cast<const SvxPostureItem&>(rItem).GetPosture();
+ switch ( e )
+ {
+ case ITALIC_OBLIQUE:
+ case ITALIC_NORMAL: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_I ); break;
+ case ITALIC_NONE: rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_I ).WriteChar( '0' ); break;
+ default:
+ break;
+ }
+ }
+ break;
+ case EE_CHAR_OUTLINE:
+ {
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_OUTL );
+ if ( !static_cast<const SvxContourItem&>(rItem).GetValue() )
+ rOutput.WriteChar( '0' );
+ }
+ break;
+ case EE_CHAR_RELIEF:
+ {
+ FontRelief nRelief = static_cast<const SvxCharReliefItem&>(rItem).GetValue();
+ if ( nRelief == FontRelief::Embossed )
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_EMBO );
+ if ( nRelief == FontRelief::Engraved )
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_IMPR );
+ }
+ break;
+ case EE_CHAR_EMPHASISMARK:
+ {
+ FontEmphasisMark nMark = static_cast<const SvxEmphasisMarkItem&>(rItem).GetEmphasisMark();
+ if ( nMark == FontEmphasisMark::NONE )
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_ACCNONE );
+ else if ( nMark == (FontEmphasisMark::Accent | FontEmphasisMark::PosAbove) )
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_ACCCOMMA );
+ else
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_ACCDOT );
+ }
+ break;
+ case EE_CHAR_SHADOW:
+ {
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_SHAD );
+ if ( !static_cast<const SvxShadowedItem&>(rItem).GetValue() )
+ rOutput.WriteChar( '0' );
+ }
+ break;
+ case EE_FEATURE_TAB:
+ {
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_TAB );
+ }
+ break;
+ case EE_FEATURE_LINEBR:
+ {
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_SL );
+ }
+ break;
+ case EE_CHAR_KERNING:
+ {
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_EXPNDTW );
+ rOutput.WriteNumberAsString( LogicToTwips(
+ static_cast<const SvxKerningItem&>(rItem).GetValue() ) );
+ }
+ break;
+ case EE_CHAR_PAIRKERNING:
+ {
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_KERNING );
+ rOutput.WriteNumberAsString( static_cast<const SvxAutoKernItem&>(rItem).GetValue() ? 1 : 0 );
+ }
+ break;
+ case EE_CHAR_ESCAPEMENT:
+ {
+ SvxFont aFont;
+ ContentNode* pNode = maEditDoc.GetObject( nPara );
+ SeekCursor( pNode, nPos, aFont );
+ MapMode aPntMode( MapUnit::MapPoint );
+ tools::Long nFontHeight = GetRefDevice()->LogicToLogic(
+ aFont.GetFontSize(), &GetRefMapMode(), &aPntMode ).Height();
+ nFontHeight *=2; // Half Points
+ sal_uInt16 const nProp = static_cast<const SvxEscapementItem&>(rItem).GetProportionalHeight();
+ sal_uInt16 nProp100 = nProp*100; // For SWG-Token Prop in 100th percent.
+ short nEsc = static_cast<const SvxEscapementItem&>(rItem).GetEsc();
+ const FontMetric& rFontMetric = GetRefDevice()->GetFontMetric();
+ double fFontHeight = rFontMetric.GetAscent() + rFontMetric.GetDescent();
+ double fAutoAscent = .8;
+ double fAutoDescent = .2;
+ if ( fFontHeight )
+ {
+ fAutoAscent = rFontMetric.GetAscent() / fFontHeight;
+ fAutoDescent = rFontMetric.GetDescent() / fFontHeight;
+ }
+ if ( nEsc == DFLT_ESC_AUTO_SUPER )
+ {
+ nEsc = fAutoAscent * (100 - nProp);
+ nProp100++; // A 1 afterwards means 'automatic'.
+ }
+ else if ( nEsc == DFLT_ESC_AUTO_SUB )
+ {
+ nEsc = fAutoDescent * -(100 - nProp);
+ nProp100++;
+ }
+ // SWG:
+ if ( nEsc )
+ {
+ rOutput.WriteOString( "{\\*\\updnprop" ).WriteNumberAsString(
+ nProp100 ).WriteChar( '}' );
+ }
+ tools::Long nUpDown = nFontHeight * std::abs( nEsc ) / 100;
+ if ( nEsc < 0 )
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_DN );
+ else if ( nEsc > 0 )
+ rOutput.WriteOString( OOO_STRING_SVTOOLS_RTF_UP );
+ rOutput.WriteNumberAsString(nUpDown);
+ }
+ break;
+ case EE_CHAR_CASEMAP:
+ {
+ const SvxCaseMapItem& rCaseMap = static_cast<const SvxCaseMapItem&>(rItem);
+ switch (rCaseMap.GetValue())
+ {
+ case SvxCaseMap::SmallCaps:
+ rOutput.WriteOString(OOO_STRING_SVTOOLS_RTF_SCAPS);
+ break;
+ case SvxCaseMap::Uppercase:
+ rOutput.WriteOString(OOO_STRING_SVTOOLS_RTF_CAPS);
+ break;
+ default: // Something that rtf does not support
+ rOutput.WriteOString(OOO_STRING_SVTOOLS_RTF_SCAPS);
+ rOutput.WriteNumberAsString(0);
+ rOutput.WriteOString(OOO_STRING_SVTOOLS_RTF_CAPS);
+ rOutput.WriteNumberAsString(0);
+ break;
+ }
+ }
+ break;
+ }
+}
+
+std::unique_ptr<EditTextObject> ImpEditEngine::GetEmptyTextObject()
+{
+ EditSelection aEmptySel;
+ aEmptySel.Min() = maEditDoc.GetStartPaM();
+ aEmptySel.Max() = maEditDoc.GetStartPaM();
+
+ return CreateTextObject( aEmptySel );
+}
+
+std::unique_ptr<EditTextObject> ImpEditEngine::CreateTextObject()
+{
+ EditSelection aCompleteSelection;
+ aCompleteSelection.Min() = maEditDoc.GetStartPaM();
+ aCompleteSelection.Max() = maEditDoc.GetEndPaM();
+
+ return CreateTextObject( aCompleteSelection );
+}
+
+std::unique_ptr<EditTextObject> ImpEditEngine::CreateTextObject(const EditSelection& rSel)
+{
+ return CreateTextObject(rSel, GetEditTextObjectPool(), maStatus.AllowBigObjects(), mnBigTextObjectStart);
+}
+
+std::unique_ptr<EditTextObject> ImpEditEngine::CreateTextObject( EditSelection aSel, SfxItemPool* pPool, bool bAllowBigObjects, sal_Int32 nBigObjectStart )
+{
+ sal_Int32 nStartNode, nEndNode;
+ sal_Int32 nTextPortions = 0;
+
+ aSel.Adjust( maEditDoc );
+ nStartNode = maEditDoc.GetPos( aSel.Min().GetNode() );
+ nEndNode = maEditDoc.GetPos( aSel.Max().GetNode() );
+
+ bool bOnlyFullParagraphs = !( aSel.Min().GetIndex() ||
+ ( aSel.Max().GetIndex() < aSel.Max().GetNode()->Len() ) );
+
+ // Templates are not saved!
+ // (Only the name and family, template itself must be in App!)
+
+ const MapUnit eMapUnit = maEditDoc.GetItemPool().GetMetric(DEF_METRIC);
+ auto pTxtObj(std::make_unique<EditTextObjectImpl>(pPool, eMapUnit, GetVertical(), GetRotation(),
+ GetItemScriptType(aSel)));
+
+ // iterate over the paragraphs ...
+ sal_Int32 nNode;
+ for ( nNode = nStartNode; nNode <= nEndNode; nNode++ )
+ {
+ ContentNode* pNode = maEditDoc.GetObject( nNode );
+ DBG_ASSERT( pNode, "Node not found: Search&Replace" );
+
+ if ( bOnlyFullParagraphs )
+ {
+ const ParaPortion* pParaPortion = GetParaPortions()[nNode];
+ nTextPortions += pParaPortion->GetTextPortions().Count();
+ }
+
+ sal_Int32 nStartPos = 0;
+ sal_Int32 nEndPos = pNode->Len();
+
+ bool bEmptyPara = nEndPos == 0;
+
+ if ( ( nNode == nStartNode ) && !bOnlyFullParagraphs )
+ nStartPos = aSel.Min().GetIndex();
+ if ( ( nNode == nEndNode ) && !bOnlyFullParagraphs )
+ nEndPos = aSel.Max().GetIndex();
+
+
+ ContentInfo *pC = pTxtObj->CreateAndInsertContent();
+
+ // The paragraph attributes ...
+ pC->GetParaAttribs().Set( pNode->GetContentAttribs().GetItems() );
+
+ // The StyleSheet...
+ if ( pNode->GetStyleSheet() )
+ {
+ pC->SetStyle(pNode->GetStyleSheet()->GetName());
+ pC->SetFamily(pNode->GetStyleSheet()->GetFamily());
+ }
+
+ // The Text...
+ pC->SetText(pNode->Copy(nStartPos, nEndPos-nStartPos));
+ auto& rCAttriblist = pC->GetCharAttribs();
+
+ // and the Attribute...
+ std::size_t nAttr = 0;
+ EditCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
+ rCAttriblist.reserve(rCAttriblist.size() + pNode->GetCharAttribs().GetAttribs().size());
+ while ( pAttr )
+ {
+ // In a blank paragraph keep the attributes!
+ if ( bEmptyPara ||
+ ( ( pAttr->GetEnd() > nStartPos ) && ( pAttr->GetStart() < nEndPos ) ) )
+ {
+ XEditAttribute aX = pTxtObj->CreateAttrib(*pAttr->GetItem(), pAttr->GetStart(), pAttr->GetEnd());
+ // Possibly Correct ...
+ if ( ( nNode == nStartNode ) && ( nStartPos != 0 ) )
+ {
+ aX.GetStart() = ( aX.GetStart() > nStartPos ) ? aX.GetStart()-nStartPos : 0;
+ aX.GetEnd() = aX.GetEnd() - nStartPos;
+
+ }
+ if ( nNode == nEndNode )
+ {
+ if ( aX.GetEnd() > (nEndPos-nStartPos) )
+ aX.GetEnd() = nEndPos-nStartPos;
+ }
+ DBG_ASSERT( aX.GetEnd() <= (nEndPos-nStartPos), "CreateTextObject: Attribute too long!" );
+ if ( aX.GetLen() || bEmptyPara )
+ rCAttriblist.push_back(std::move(aX));
+ }
+ nAttr++;
+ pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
+ }
+
+ // If possible online spelling
+ if ( bAllowBigObjects && bOnlyFullParagraphs && pNode->GetWrongList() )
+ pC->SetWrongList( pNode->GetWrongList()->Clone() );
+
+ }
+
+ // Remember the portions info in case of large text objects:
+ // sleeper set up when Olli paragraphs not hacked!
+ if ( bAllowBigObjects && bOnlyFullParagraphs && IsFormatted() && IsUpdateLayout() && ( nTextPortions >= nBigObjectStart ) )
+ {
+ XParaPortionList* pXList = new XParaPortionList(GetRefDevice(), GetColumnWidth(maPaperSize), mfFontScaleX, mfFontScaleY, mfSpacingScaleX, mfSpacingScaleY);
+ pTxtObj->SetPortionInfo(std::unique_ptr<XParaPortionList>(pXList));
+ for ( nNode = nStartNode; nNode <= nEndNode; nNode++ )
+ {
+ const ParaPortion* pParaPortion = GetParaPortions()[nNode];
+ XParaPortion* pX = new XParaPortion;
+ pXList->push_back(pX);
+
+ pX->nHeight = pParaPortion->GetHeight();
+ pX->nFirstLineOffset = pParaPortion->GetFirstLineOffset();
+
+ // The TextPortions
+ sal_uInt16 nCount = pParaPortion->GetTextPortions().Count();
+ sal_uInt16 n;
+ for ( n = 0; n < nCount; n++ )
+ {
+ const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[n];
+ TextPortion* pNew = new TextPortion( rTextPortion );
+ pX->aTextPortions.Append(pNew);
+ }
+
+ // The lines
+ nCount = pParaPortion->GetLines().Count();
+ for ( n = 0; n < nCount; n++ )
+ {
+ const EditLine& rLine = pParaPortion->GetLines()[n];
+ EditLine* pNew = rLine.Clone();
+ pX->aLines.Append(pNew);
+ }
+#ifdef DBG_UTIL
+ sal_uInt16 nTest;
+ int nTPLen = 0, nTxtLen = 0;
+ for ( nTest = pParaPortion->GetTextPortions().Count(); nTest; )
+ nTPLen += pParaPortion->GetTextPortions()[--nTest].GetLen();
+ for ( nTest = pParaPortion->GetLines().Count(); nTest; )
+ nTxtLen += pParaPortion->GetLines()[--nTest].GetLen();
+ DBG_ASSERT( ( nTPLen == pParaPortion->GetNode()->Len() ) && ( nTxtLen == pParaPortion->GetNode()->Len() ), "CreateBinTextObject: ParaPortion not completely formatted!" );
+#endif
+ }
+ }
+ return pTxtObj;
+}
+
+void ImpEditEngine::SetText( const EditTextObject& rTextObject )
+{
+ // Since setting a text object is not undo-able!
+ ResetUndoManager();
+ bool _bUpdate = IsUpdateLayout();
+ bool _bUndo = IsUndoEnabled();
+
+ SetText( OUString() );
+ EditPaM aPaM = maEditDoc.GetStartPaM();
+
+ SetUpdateLayout( false );
+ EnableUndo( false );
+
+ InsertText( rTextObject, EditSelection( aPaM, aPaM ) );
+ SetVertical(rTextObject.GetVertical());
+ SetRotation(rTextObject.GetRotation());
+
+ DBG_ASSERT( !HasUndoManager() || !GetUndoManager().GetUndoActionCount(), "From where comes the Undo in SetText ?!" );
+ SetUpdateLayout( _bUpdate );
+ EnableUndo( _bUndo );
+}
+
+EditSelection ImpEditEngine::InsertText( const EditTextObject& rTextObject, EditSelection aSel )
+{
+ aSel.Adjust( maEditDoc );
+ if ( aSel.HasRange() )
+ aSel = ImpDeleteSelection( aSel );
+ EditSelection aNewSel = InsertTextObject( rTextObject, aSel.Max() );
+ return aNewSel;
+}
+
+EditSelection ImpEditEngine::InsertTextObject( const EditTextObject& rTextObject, EditPaM aPaM )
+{
+ // Optimize: No getPos undFindParaportion, instead calculate index!
+ EditSelection aSel( aPaM, aPaM );
+ DBG_ASSERT( !aSel.DbgIsBuggy( maEditDoc ), "InsertBibTextObject: Selection broken!(1)" );
+
+ bool bUsePortionInfo = false;
+ const EditTextObjectImpl& rTextObjectImpl = toImpl(rTextObject);
+ XParaPortionList* pPortionInfo = rTextObjectImpl.GetPortionInfo();
+
+ if (pPortionInfo && ( static_cast<tools::Long>(pPortionInfo->GetPaperWidth()) == GetColumnWidth(maPaperSize))
+ && pPortionInfo->GetRefMapMode() == GetRefDevice()->GetMapMode()
+ && pPortionInfo->getFontScaleX() == mfFontScaleX
+ && pPortionInfo->getFontScaleY() == mfFontScaleY
+ && pPortionInfo->getSpacingScaleX() == mfSpacingScaleX
+ && pPortionInfo->getSpacingScaleY() == mfSpacingScaleY)
+ {
+ if ( (pPortionInfo->GetRefDevPtr() == GetRefDevice()) ||
+ (pPortionInfo->RefDevIsVirtual() && GetRefDevice()->IsVirtual()) )
+ bUsePortionInfo = true;
+ }
+
+ bool bConvertMetricOfItems = false;
+ MapUnit eSourceUnit = MapUnit(), eDestUnit = MapUnit();
+ if (rTextObjectImpl.HasMetric())
+ {
+ eSourceUnit = rTextObjectImpl.GetMetric();
+ eDestUnit = maEditDoc.GetItemPool().GetMetric( DEF_METRIC );
+ if ( eSourceUnit != eDestUnit )
+ bConvertMetricOfItems = true;
+ }
+
+ // Before, paragraph count was of type sal_uInt16 so if nContents exceeded
+ // 0xFFFF this wouldn't have worked anyway, given that nPara is used to
+ // number paragraphs and is fearlessly incremented.
+ sal_Int32 nContents = static_cast<sal_Int32>(rTextObjectImpl.GetContents().size());
+ SAL_WARN_IF( nContents < 0, "editeng", "ImpEditEngine::InsertTextObject - contents overflow " << nContents);
+ sal_Int32 nPara = maEditDoc.GetPos( aPaM.GetNode() );
+
+ for (sal_Int32 n = 0; n < nContents; ++n, ++nPara)
+ {
+ const ContentInfo* pC = rTextObjectImpl.GetContents()[n].get();
+ bool bNewContent = aPaM.GetNode()->Len() == 0;
+ const sal_Int32 nStartPos = aPaM.GetIndex();
+
+ aPaM = ImpFastInsertText( aPaM, pC->GetText() );
+
+ ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() );
+ DBG_ASSERT( pPortion, "Blind Portion in FastInsertText" );
+ pPortion->MarkInvalid( nStartPos, pC->GetText().getLength() );
+
+ // Character attributes ...
+ bool bAllreadyHasAttribs = aPaM.GetNode()->GetCharAttribs().Count() != 0;
+ size_t nNewAttribs = pC->GetCharAttribs().size();
+ if ( nNewAttribs )
+ {
+ bool bUpdateFields = false;
+ for (size_t nAttr = 0; nAttr < nNewAttribs; ++nAttr)
+ {
+ const XEditAttribute& rX = pC->GetCharAttribs()[nAttr];
+ // Can happen when paragraphs > 16K, it is simply wrapped.
+ //TODO! Still true, still needed?
+ if ( rX.GetEnd() <= aPaM.GetNode()->Len() )
+ {
+ if ( !bAllreadyHasAttribs || rX.IsFeature() )
+ {
+ // Normal attributes then go faster ...
+ // Features shall not be inserted through
+ // EditDoc:: InsertAttrib, using FastInsertText they are
+ // already in the flow
+ DBG_ASSERT( rX.GetEnd() <= aPaM.GetNode()->Len(), "InsertBinTextObject: Attribute too large!" );
+ EditCharAttrib* pAttr;
+ if ( !bConvertMetricOfItems )
+ pAttr = MakeCharAttrib( maEditDoc.GetItemPool(), *(rX.GetItem()), rX.GetStart()+nStartPos, rX.GetEnd()+nStartPos );
+ else
+ {
+ std::unique_ptr<SfxPoolItem> pNew(rX.GetItem()->Clone());
+ ConvertItem( pNew, eSourceUnit, eDestUnit );
+ pAttr = MakeCharAttrib( maEditDoc.GetItemPool(), *pNew, rX.GetStart()+nStartPos, rX.GetEnd()+nStartPos );
+ }
+ DBG_ASSERT( pAttr->GetEnd() <= aPaM.GetNode()->Len(), "InsertBinTextObject: Attribute does not fit! (1)" );
+ aPaM.GetNode()->GetCharAttribs().InsertAttrib( pAttr );
+ if ( pAttr->Which() == EE_FEATURE_FIELD )
+ bUpdateFields = true;
+ }
+ else
+ {
+ DBG_ASSERT( rX.GetEnd()+nStartPos <= aPaM.GetNode()->Len(), "InsertBinTextObject: Attribute does not fit! (2)" );
+ // Tabs and other Features can not be inserted through InsertAttrib:
+ maEditDoc.InsertAttrib( aPaM.GetNode(), rX.GetStart()+nStartPos, rX.GetEnd()+nStartPos, *rX.GetItem() );
+ }
+ }
+ }
+ if ( bUpdateFields )
+ UpdateFields();
+
+ // Otherwise, quick format => no attributes!
+ pPortion->MarkSelectionInvalid( nStartPos );
+ }
+
+#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
+ CharAttribList::DbgCheckAttribs(aPaM.GetNode()->GetCharAttribs());
+#endif
+
+ bool bParaAttribs = false;
+ if ( bNewContent || ( ( n > 0 ) && ( n < (nContents-1) ) ) )
+ {
+ // only style and ParaAttribs when new paragraph, or
+ // completely internal ...
+ bParaAttribs = pC->GetParaAttribs().Count() != 0;
+ if ( GetStyleSheetPool() && pC->GetStyle().getLength() )
+ {
+ SfxStyleSheet* pStyle = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( pC->GetStyle(), pC->GetFamily() ));
+ DBG_ASSERT( pStyle, "InsertBinTextObject - Style not found!" );
+ SetStyleSheet( nPara, pStyle );
+ }
+ if ( !bConvertMetricOfItems )
+ SetParaAttribs( maEditDoc.GetPos( aPaM.GetNode() ), pC->GetParaAttribs() );
+ else
+ {
+ SfxItemSet aAttribs( GetEmptyItemSet() );
+ ConvertAndPutItems( aAttribs, pC->GetParaAttribs(), &eSourceUnit, &eDestUnit );
+ SetParaAttribs( maEditDoc.GetPos( aPaM.GetNode() ), aAttribs );
+ }
+ if ( bNewContent && bUsePortionInfo )
+ {
+ const XParaPortion& rXP = (*pPortionInfo)[n];
+ ParaPortion* pParaPortion = GetParaPortions()[ nPara ];
+ DBG_ASSERT( pParaPortion, "InsertBinTextObject: ParaPortion?" );
+ pParaPortion->nHeight = rXP.nHeight;
+ pParaPortion->nFirstLineOffset = rXP.nFirstLineOffset;
+ pParaPortion->bForceRepaint = true;
+ pParaPortion->SetValid(); // Do not format
+
+ // The Text Portions
+ pParaPortion->GetTextPortions().Reset();
+ sal_uInt16 nCount = rXP.aTextPortions.Count();
+ for ( sal_uInt16 _n = 0; _n < nCount; _n++ )
+ {
+ const TextPortion& rTextPortion = rXP.aTextPortions[_n];
+ TextPortion* pNew = new TextPortion( rTextPortion );
+ pParaPortion->GetTextPortions().Append(pNew);
+ }
+
+ // The lines
+ pParaPortion->GetLines().Reset();
+ nCount = rXP.aLines.Count();
+ for ( sal_uInt16 m = 0; m < nCount; m++ )
+ {
+ const EditLine& rLine = rXP.aLines[m];
+ EditLine* pNew = rLine.Clone();
+ pNew->SetInvalid(); // Paint again!
+ pParaPortion->GetLines().Append(pNew);
+ }
+#ifdef DBG_UTIL
+ sal_uInt16 nTest;
+ int nTPLen = 0, nTxtLen = 0;
+ for ( nTest = pParaPortion->GetTextPortions().Count(); nTest; )
+ nTPLen += pParaPortion->GetTextPortions()[--nTest].GetLen();
+ for ( nTest = pParaPortion->GetLines().Count(); nTest; )
+ nTxtLen += pParaPortion->GetLines()[--nTest].GetLen();
+ DBG_ASSERT( ( nTPLen == pParaPortion->GetNode()->Len() ) && ( nTxtLen == pParaPortion->GetNode()->Len() ), "InsertTextObject: ParaPortion not completely formatted!" );
+#endif
+ }
+ }
+ if ( !bParaAttribs ) // DefFont is not calculated for FastInsertParagraph
+ {
+ aPaM.GetNode()->GetCharAttribs().GetDefFont() = maEditDoc.GetDefFont();
+ if (maStatus.UseCharAttribs())
+ aPaM.GetNode()->CreateDefFont();
+ }
+
+ if ( bNewContent && GetStatus().DoOnlineSpelling() && pC->GetWrongList() )
+ {
+ aPaM.GetNode()->SetWrongList( pC->GetWrongList()->Clone() );
+ }
+
+ // Wrap when followed by other ...
+ if ( n < ( nContents-1) )
+ {
+ if ( bNewContent )
+ aPaM = ImpFastInsertParagraph( nPara+1 );
+ else
+ aPaM = ImpInsertParaBreak( aPaM, false );
+ }
+ }
+
+ aSel.Max() = aPaM;
+ DBG_ASSERT( !aSel.DbgIsBuggy( maEditDoc ), "InsertBibTextObject: Selection broken!(1)" );
+ return aSel;
+}
+
+void ImpEditEngine::GetAllMisspellRanges( std::vector<editeng::MisspellRanges>& rRanges ) const
+{
+ std::vector<editeng::MisspellRanges> aRanges;
+ const EditDoc& rDoc = GetEditDoc();
+ for (sal_Int32 i = 0, n = rDoc.Count(); i < n; ++i)
+ {
+ const ContentNode* pNode = rDoc.GetObject(i);
+ const WrongList* pWrongList = pNode->GetWrongList();
+ if (!pWrongList)
+ continue;
+
+ aRanges.emplace_back(i, std::vector(pWrongList->GetRanges()));
+ }
+
+ aRanges.swap(rRanges);
+}
+
+void ImpEditEngine::SetAllMisspellRanges( const std::vector<editeng::MisspellRanges>& rRanges )
+{
+ EditDoc& rDoc = GetEditDoc();
+ for (auto const& rParaRanges : rRanges)
+ {
+ ContentNode* pNode = rDoc.GetObject(rParaRanges.mnParagraph);
+ if (!pNode)
+ continue;
+
+ pNode->CreateWrongList();
+ WrongList* pWrongList = pNode->GetWrongList();
+ pWrongList->SetRanges(std::vector(rParaRanges.maRanges));
+ }
+}
+
+editeng::LanguageSpan ImpEditEngine::GetLanguage( const EditPaM& rPaM, sal_Int32* pEndPos ) const
+{
+ short nScriptTypeI18N = GetI18NScriptType( rPaM, pEndPos ); // pEndPos will be valid now, pointing to ScriptChange or NodeLen
+ SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N);
+ sal_uInt16 nLangId = GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType );
+ const SvxLanguageItem* pLangItem = &static_cast<const SvxLanguageItem&>(rPaM.GetNode()->GetContentAttribs().GetItem( nLangId ));
+ const EditCharAttrib* pAttr = rPaM.GetNode()->GetCharAttribs().FindAttrib( nLangId, rPaM.GetIndex() );
+
+ editeng::LanguageSpan aLang;
+
+ if ( pAttr )
+ {
+ pLangItem = static_cast<const SvxLanguageItem*>(pAttr->GetItem());
+ aLang.nStart = pAttr->GetStart();
+ aLang.nEnd = pAttr->GetEnd();
+ }
+
+ if ( pEndPos && pAttr && ( pAttr->GetEnd() < *pEndPos ) )
+ *pEndPos = pAttr->GetEnd();
+
+ aLang.nLang = pLangItem->GetLanguage();
+
+ return aLang;
+}
+
+css::lang::Locale ImpEditEngine::GetLocale( const EditPaM& rPaM ) const
+{
+ return LanguageTag( GetLanguage( rPaM ).nLang ).getLocale();
+}
+
+Reference< XSpellChecker1 > const & ImpEditEngine::GetSpeller()
+{
+ if ( !xSpeller.is() )
+ xSpeller = LinguMgr::GetSpellChecker();
+ return xSpeller;
+}
+
+
+void ImpEditEngine::CreateSpellInfo( bool bMultipleDocs )
+{
+ if (!pSpellInfo)
+ pSpellInfo.reset( new SpellInfo );
+ else
+ *pSpellInfo = SpellInfo(); // reset to default values
+
+ pSpellInfo->bMultipleDoc = bMultipleDocs;
+ // always spell draw objects completely, starting at the top.
+ // (spelling in only a selection or not starting with the top requires
+ // further changes elsewhere to work properly)
+ pSpellInfo->aSpellStart = EPaM();
+ pSpellInfo->aSpellTo = EPaM( EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND );
+}
+
+
+EESpellState ImpEditEngine::Spell(EditView* pEditView, weld::Widget* pDialogParent, bool bMultipleDoc)
+{
+ SAL_WARN_IF( !xSpeller.is(), "editeng", "No Spell checker set!" );
+
+ if ( !xSpeller.is() )
+ return EESpellState::NoSpeller;
+
+ aOnlineSpellTimer.Stop();
+
+ // In MultipleDoc always from the front / rear ...
+ if ( bMultipleDoc )
+ {
+ pEditView->pImpEditView->SetEditSelection( maEditDoc.GetStartPaM() );
+ }
+
+ EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() );
+ CreateSpellInfo( bMultipleDoc );
+
+ bool bIsStart = false;
+ if ( bMultipleDoc )
+ bIsStart = true; // Accessible from the front or from behind ...
+ else if ( CreateEPaM( maEditDoc.GetStartPaM() ) == pSpellInfo->aSpellStart )
+ bIsStart = true;
+
+ {
+ EditSpellWrapper aWrp(pDialogParent, bIsStart, pEditView );
+ aWrp.SpellDocument();
+ }
+
+ if ( !bMultipleDoc )
+ {
+ pEditView->pImpEditView->DrawSelectionXOR();
+ if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() )
+ aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() );
+ aCurSel.Min() = aCurSel.Max();
+ pEditView->pImpEditView->SetEditSelection( aCurSel );
+ pEditView->pImpEditView->DrawSelectionXOR();
+ pEditView->ShowCursor( true, false );
+ }
+ EESpellState eState = pSpellInfo->eState;
+ pSpellInfo.reset();
+ return eState;
+}
+
+
+bool ImpEditEngine::HasConvertibleTextPortion( LanguageType nSrcLang )
+{
+ bool bHasConvTxt = false;
+
+ sal_Int32 nParas = pEditEngine->GetParagraphCount();
+ for (sal_Int32 k = 0; k < nParas; ++k)
+ {
+ std::vector<sal_Int32> aPortions;
+ pEditEngine->GetPortions( k, aPortions );
+ for ( size_t nPos = 0; nPos < aPortions.size(); ++nPos )
+ {
+ sal_Int32 nEnd = aPortions[ nPos ];
+ sal_Int32 nStart = nPos > 0 ? aPortions[ nPos - 1 ] : 0;
+
+ // if the paragraph is not empty we need to increase the index
+ // by one since the attribute of the character left to the
+ // specified position is evaluated.
+ if (nEnd > nStart) // empty para?
+ ++nStart;
+ LanguageType nLangFound = pEditEngine->GetLanguage( k, nStart ).nLang;
+#ifdef DEBUG
+ lang::Locale aLocale( LanguageTag::convertToLocale( nLangFound ) );
+#endif
+ bHasConvTxt = (nSrcLang == nLangFound) ||
+ (editeng::HangulHanjaConversion::IsChinese( nLangFound ) &&
+ editeng::HangulHanjaConversion::IsChinese( nSrcLang ));
+ if (bHasConvTxt)
+ return bHasConvTxt;
+ }
+ }
+
+ return bHasConvTxt;
+}
+
+void ImpEditEngine::Convert( EditView* pEditView, weld::Widget* pDialogParent,
+ LanguageType nSrcLang, LanguageType nDestLang, const vcl::Font *pDestFont,
+ sal_Int32 nOptions, bool bIsInteractive, bool bMultipleDoc )
+{
+ // modified version of ImpEditEngine::Spell
+
+ // In MultipleDoc always from the front / rear ...
+ if ( bMultipleDoc )
+ pEditView->pImpEditView->SetEditSelection( maEditDoc.GetStartPaM() );
+
+
+ // initialize pConvInfo
+ EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() );
+ aCurSel.Adjust( maEditDoc );
+ pConvInfo.reset(new ConvInfo);
+ pConvInfo->bMultipleDoc = bMultipleDoc;
+ pConvInfo->aConvStart = CreateEPaM( aCurSel.Min() );
+
+ // if it is not just a selection and we are about to begin
+ // with the current conversion for the very first time
+ // we need to find the start of the current (initial)
+ // convertible unit in order for the text conversion to give
+ // the correct result for that. Since it is easier to obtain
+ // the start of the word we use that though.
+ if (!aCurSel.HasRange() && ImplGetBreakIterator().is())
+ {
+ EditPaM aWordStartPaM( SelectWord( aCurSel, i18n::WordType::DICTIONARY_WORD ).Min() );
+
+ // since #118246 / #117803 still occurs if the cursor is placed
+ // between the two chinese characters to be converted (because both
+ // of them are words on their own!) using the word boundary here does
+ // not work. Thus since chinese conversion is not interactive we start
+ // at the begin of the paragraph to solve the problem, i.e. have the
+ // TextConversion service get those characters together in the same call.
+ pConvInfo->aConvStart.nIndex = editeng::HangulHanjaConversion::IsChinese( nSrcLang )
+ ? 0 : aWordStartPaM.GetIndex();
+ }
+
+ pConvInfo->aConvContinue = pConvInfo->aConvStart;
+
+ bool bIsStart = false;
+ if ( bMultipleDoc )
+ bIsStart = true; // Accessible from the front or from behind ...
+ else if ( CreateEPaM( maEditDoc.GetStartPaM() ) == pConvInfo->aConvStart )
+ bIsStart = true;
+
+ TextConvWrapper aWrp( pDialogParent,
+ ::comphelper::getProcessComponentContext(),
+ LanguageTag::convertToLocale( nSrcLang ),
+ LanguageTag::convertToLocale( nDestLang ),
+ pDestFont,
+ nOptions, bIsInteractive,
+ bIsStart, pEditView );
+
+
+ //!! optimization does not work since when update mode is false
+ //!! the object is 'lying' about it portions, paragraphs,
+ //!! EndPaM... later on.
+ //!! Should not be a great problem since text boxes or cells in
+ //!! Calc usually have only a rather short text.
+ //
+ // disallow formatting, updating the view, ... while
+ // non-interactively converting the document. (saves time)
+ //if (!bIsInteractive)
+ // SetUpdateMode( sal_False );
+
+ aWrp.Convert();
+
+ //if (!bIsInteractive)
+ //SetUpdateMode( sal_True, 0, sal_True );
+
+ if ( !bMultipleDoc )
+ {
+ pEditView->pImpEditView->DrawSelectionXOR();
+ if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() )
+ aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() );
+ aCurSel.Min() = aCurSel.Max();
+ pEditView->pImpEditView->SetEditSelection( aCurSel );
+ pEditView->pImpEditView->DrawSelectionXOR();
+ pEditView->ShowCursor( true, false );
+ }
+ pConvInfo.reset();
+}
+
+
+void ImpEditEngine::SetLanguageAndFont(
+ const ESelection &rESel,
+ LanguageType nLang, sal_uInt16 nLangWhichId,
+ const vcl::Font *pFont, sal_uInt16 nFontWhichId )
+{
+ ESelection aOldSel = pActiveView->GetSelection();
+ pActiveView->SetSelection( rESel );
+
+ // set new language attribute
+ SfxItemSet aNewSet( pActiveView->GetEmptyItemSet() );
+ aNewSet.Put( SvxLanguageItem( nLang, nLangWhichId ) );
+
+ // new font to be set?
+ DBG_ASSERT( pFont, "target font missing?" );
+ if (pFont)
+ {
+ // set new font attribute
+ SvxFontItem aFontItem = static_cast<const SvxFontItem&>( aNewSet.Get( nFontWhichId ) );
+ aFontItem.SetFamilyName( pFont->GetFamilyName());
+ aFontItem.SetFamily( pFont->GetFamilyType());
+ aFontItem.SetStyleName( pFont->GetStyleName());
+ aFontItem.SetPitch( pFont->GetPitch());
+ aFontItem.SetCharSet( pFont->GetCharSet() );
+ aNewSet.Put( aFontItem );
+ }
+
+ // apply new attributes
+ pActiveView->SetAttribs( aNewSet );
+
+ pActiveView->SetSelection( aOldSel );
+}
+
+
+void ImpEditEngine::ImpConvert( OUString &rConvTxt, LanguageType &rConvTxtLang,
+ EditView* pEditView, LanguageType nSrcLang, const ESelection &rConvRange,
+ bool bAllowImplicitChangesForNotConvertibleText,
+ LanguageType nTargetLang, const vcl::Font *pTargetFont )
+{
+ // modified version of ImpEditEngine::ImpSpell
+
+ // looks for next convertible text portion to be passed on to the wrapper
+
+ OUString aRes;
+ LanguageType nResLang = LANGUAGE_NONE;
+
+ EditPaM aPos( CreateEditPaM( pConvInfo->aConvContinue ) );
+ EditSelection aCurSel( aPos, aPos );
+
+ OUString aWord;
+
+ while (aRes.isEmpty())
+ {
+ // empty paragraph found that needs to have language and font set?
+ if (bAllowImplicitChangesForNotConvertibleText &&
+ pEditEngine->GetText( pConvInfo->aConvContinue.nPara ).isEmpty())
+ {
+ sal_Int32 nPara = pConvInfo->aConvContinue.nPara;
+ ESelection aESel( nPara, 0, nPara, 0 );
+ // see comment for below same function call
+ SetLanguageAndFont( aESel,
+ nTargetLang, EE_CHAR_LANGUAGE_CJK,
+ pTargetFont, EE_CHAR_FONTINFO_CJK );
+ }
+
+
+ if (pConvInfo->aConvContinue.nPara == pConvInfo->aConvTo.nPara &&
+ pConvInfo->aConvContinue.nIndex >= pConvInfo->aConvTo.nIndex)
+ break;
+
+ sal_Int32 nAttribStart = -1;
+ sal_Int32 nAttribEnd = -1;
+ sal_Int32 nCurPos = -1;
+ EPaM aCurStart = CreateEPaM( aCurSel.Min() );
+ std::vector<sal_Int32> aPortions;
+ pEditEngine->GetPortions( aCurStart.nPara, aPortions );
+ for ( size_t nPos = 0; nPos < aPortions.size(); ++nPos )
+ {
+ const sal_Int32 nEnd = aPortions[ nPos ];
+ const sal_Int32 nStart = nPos > 0 ? aPortions[ nPos - 1 ] : 0;
+
+ // the language attribute is obtained from the left character
+ // (like usually all other attributes)
+ // thus we usually have to add 1 in order to get the language
+ // of the text right to the cursor position
+ const sal_Int32 nLangIdx = nEnd > nStart ? nStart + 1 : nStart;
+ LanguageType nLangFound = pEditEngine->GetLanguage( aCurStart.nPara, nLangIdx ).nLang;
+#ifdef DEBUG
+ lang::Locale aLocale( LanguageTag::convertToLocale( nLangFound ) );
+#endif
+ bool bLangOk = (nLangFound == nSrcLang) ||
+ (editeng::HangulHanjaConversion::IsChinese( nLangFound ) &&
+ editeng::HangulHanjaConversion::IsChinese( nSrcLang ));
+
+ if (nAttribEnd>=0) // start already found?
+ {
+ DBG_ASSERT(nEnd >= aCurStart.nIndex, "error while scanning attributes (a)" );
+ DBG_ASSERT(nEnd >= nAttribEnd, "error while scanning attributes (b)" );
+ if (/*nEnd >= aCurStart.nIndex &&*/ nLangFound == nResLang)
+ nAttribEnd = nEnd;
+ else // language attrib has changed
+ break;
+ }
+ if (nAttribStart<0 && // start not yet found?
+ nEnd > aCurStart.nIndex && bLangOk)
+ {
+ nAttribStart = nStart;
+ nAttribEnd = nEnd;
+ nResLang = nLangFound;
+ }
+ //! the list of portions may have changed compared to the previous
+ //! call to this function (because of possibly changed language
+ //! attribute!)
+ //! But since we don't want to start in the already processed part
+ //! we clip the start accordingly.
+ if (nAttribStart >= 0 && nAttribStart < aCurStart.nIndex)
+ {
+ nAttribStart = aCurStart.nIndex;
+ }
+
+ // check script type to the right of the start of the current portion
+ EditPaM aPaM( CreateEditPaM( EPaM(aCurStart.nPara, nLangIdx) ) );
+ bool bIsAsianScript = (i18n::ScriptType::ASIAN == GetI18NScriptType( aPaM ));
+ // not yet processed text part with for conversion
+ // not suitable language found that needs to be changed?
+ if (bAllowImplicitChangesForNotConvertibleText &&
+ !bLangOk && !bIsAsianScript && nEnd > aCurStart.nIndex)
+ {
+ ESelection aESel( aCurStart.nPara, nStart, aCurStart.nPara, nEnd );
+ // set language and font to target language and font of conversion
+ //! Now this especially includes all non convertible text e.g.
+ //! spaces, empty paragraphs and western text.
+ // This is in order for every *new* text entered at *any* position to
+ // have the correct language and font attributes set.
+ SetLanguageAndFont( aESel,
+ nTargetLang, EE_CHAR_LANGUAGE_CJK,
+ pTargetFont, EE_CHAR_FONTINFO_CJK );
+ }
+
+ nCurPos = nEnd;
+ }
+
+ if (nAttribStart>=0 && nAttribEnd>=0)
+ {
+ aCurSel.Min().SetIndex( nAttribStart );
+ aCurSel.Max().SetIndex( nAttribEnd );
+ }
+ else if (nCurPos>=0)
+ {
+ // set selection to end of scanned text
+ // (used to set the position where to continue from later on)
+ aCurSel.Min().SetIndex( nCurPos );
+ aCurSel.Max().SetIndex( nCurPos );
+ }
+
+ if ( !pConvInfo->bConvToEnd )
+ {
+ EPaM aEPaM( CreateEPaM( aCurSel.Min() ) );
+ if ( !( aEPaM < pConvInfo->aConvTo ) )
+ break;
+ }
+
+ // clip selected word to the converted area
+ // (main use when conversion starts/ends **within** a word)
+ EditPaM aPaM( CreateEditPaM( pConvInfo->aConvStart ) );
+ if (pConvInfo->bConvToEnd &&
+ aCurSel.Min().GetNode() == aPaM.GetNode() &&
+ aCurSel.Min().GetIndex() < aPaM.GetIndex())
+ aCurSel.Min().SetIndex( aPaM.GetIndex() );
+ aPaM = CreateEditPaM( pConvInfo->aConvContinue );
+ if (aCurSel.Min().GetNode() == aPaM.GetNode() &&
+ aCurSel.Min().GetIndex() < aPaM.GetIndex())
+ aCurSel.Min().SetIndex( aPaM.GetIndex() );
+ aPaM = CreateEditPaM( pConvInfo->aConvTo );
+ if ((!pConvInfo->bConvToEnd || rConvRange.HasRange())&&
+ aCurSel.Max().GetNode() == aPaM.GetNode() &&
+ aCurSel.Max().GetIndex() > aPaM.GetIndex())
+ aCurSel.Max().SetIndex( aPaM.GetIndex() );
+
+ aWord = GetSelected( aCurSel );
+
+ if ( !aWord.isEmpty() /* && bLangOk */)
+ aRes = aWord;
+
+ // move to next word/paragraph if necessary
+ if ( aRes.isEmpty() )
+ aCurSel = WordRight( aCurSel.Min(), css::i18n::WordType::DICTIONARY_WORD );
+
+ pConvInfo->aConvContinue = CreateEPaM( aCurSel.Max() );
+ }
+
+ pEditView->pImpEditView->DrawSelectionXOR();
+ pEditView->pImpEditView->SetEditSelection( aCurSel );
+ pEditView->pImpEditView->DrawSelectionXOR();
+ pEditView->ShowCursor( true, false );
+
+ rConvTxt = aRes;
+ if ( !rConvTxt.isEmpty() )
+ rConvTxtLang = nResLang;
+}
+
+
+Reference< XSpellAlternatives > ImpEditEngine::ImpSpell( EditView* pEditView )
+{
+ DBG_ASSERT( xSpeller.is(), "No spell checker set!" );
+
+ ContentNode* pLastNode = maEditDoc.GetObject( maEditDoc.Count()-1 );
+ EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() );
+ aCurSel.Min() = aCurSel.Max();
+
+ Reference< XSpellAlternatives > xSpellAlt;
+ Sequence< PropertyValue > aEmptySeq;
+ while (!xSpellAlt.is())
+ {
+ // Known (most likely) bug: If SpellToCurrent, the current has to be
+ // corrected at each replacement, otherwise it may not fit exactly in
+ // the end ...
+ if ( pSpellInfo->bSpellToEnd || pSpellInfo->bMultipleDoc )
+ {
+ if ( aCurSel.Max().GetNode() == pLastNode )
+ {
+ if ( aCurSel.Max().GetIndex() >= pLastNode->Len() )
+ break;
+ }
+ }
+ else if ( !pSpellInfo->bSpellToEnd )
+ {
+ EPaM aEPaM( CreateEPaM( aCurSel.Max() ) );
+ if ( !( aEPaM < pSpellInfo->aSpellTo ) )
+ break;
+ }
+
+ aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD );
+ OUString aWord = GetSelected( aCurSel );
+
+ // If afterwards a dot, this must be handed over!
+ // If an abbreviation ...
+ if ( !aWord.isEmpty() && ( aCurSel.Max().GetIndex() < aCurSel.Max().GetNode()->Len() ) )
+ {
+ sal_Unicode cNext = aCurSel.Max().GetNode()->GetChar( aCurSel.Max().GetIndex() );
+ if ( cNext == '.' )
+ {
+ aCurSel.Max().SetIndex( aCurSel.Max().GetIndex()+1 );
+ aWord += OUStringChar(cNext);
+ }
+ }
+
+ if ( !aWord.isEmpty() )
+ {
+ LanguageType eLang = GetLanguage( aCurSel.Max() ).nLang;
+ SvxSpellWrapper::CheckSpellLang( xSpeller, eLang );
+ xSpellAlt = xSpeller->spell( aWord, static_cast<sal_uInt16>(eLang), aEmptySeq );
+ }
+
+ if ( !xSpellAlt.is() )
+ aCurSel = WordRight( aCurSel.Min(), css::i18n::WordType::DICTIONARY_WORD );
+ else
+ pSpellInfo->eState = EESpellState::ErrorFound;
+ }
+
+ pEditView->pImpEditView->DrawSelectionXOR();
+ pEditView->pImpEditView->SetEditSelection( aCurSel );
+ pEditView->pImpEditView->DrawSelectionXOR();
+ pEditView->ShowCursor( true, false );
+ return xSpellAlt;
+}
+
+Reference< XSpellAlternatives > ImpEditEngine::ImpFindNextError(EditSelection& rSelection)
+{
+ EditSelection aCurSel( rSelection.Min() );
+
+ Reference< XSpellAlternatives > xSpellAlt;
+ Sequence< PropertyValue > aEmptySeq;
+ while (!xSpellAlt.is())
+ {
+ //check if the end of the selection has been reached
+ {
+ EPaM aEPaM( CreateEPaM( aCurSel.Max() ) );
+ if ( !( aEPaM < CreateEPaM( rSelection.Max()) ) )
+ break;
+ }
+
+ aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD );
+ OUString aWord = GetSelected( aCurSel );
+
+ // If afterwards a dot, this must be handed over!
+ // If an abbreviation ...
+ if ( !aWord.isEmpty() && ( aCurSel.Max().GetIndex() < aCurSel.Max().GetNode()->Len() ) )
+ {
+ sal_Unicode cNext = aCurSel.Max().GetNode()->GetChar( aCurSel.Max().GetIndex() );
+ if ( cNext == '.' )
+ {
+ aCurSel.Max().SetIndex( aCurSel.Max().GetIndex()+1 );
+ aWord += OUStringChar(cNext);
+ }
+ }
+
+ if ( !aWord.isEmpty() )
+ xSpellAlt = xSpeller->spell( aWord, static_cast<sal_uInt16>(GetLanguage( aCurSel.Max() ).nLang), aEmptySeq );
+
+ if ( !xSpellAlt.is() )
+ aCurSel = WordRight( aCurSel.Min(), css::i18n::WordType::DICTIONARY_WORD );
+ else
+ {
+ pSpellInfo->eState = EESpellState::ErrorFound;
+ rSelection = aCurSel;
+ }
+ }
+ return xSpellAlt;
+}
+
+bool ImpEditEngine::SpellSentence(EditView const & rEditView,
+ svx::SpellPortions& rToFill )
+{
+ bool bRet = false;
+ EditSelection aCurSel( rEditView.pImpEditView->GetEditSelection() );
+ if(!pSpellInfo)
+ CreateSpellInfo( true );
+ pSpellInfo->aCurSentenceStart = aCurSel.Min();
+ DBG_ASSERT( xSpeller.is(), "No spell checker set!" );
+ pSpellInfo->aLastSpellPortions.clear();
+ pSpellInfo->aLastSpellContentSelections.clear();
+ rToFill.clear();
+ //if no selection previously exists the range is extended to the end of the object
+ if (!aCurSel.HasRange())
+ {
+ ContentNode* pLastNode = maEditDoc.GetObject( maEditDoc.Count()-1);
+ aCurSel.Max() = EditPaM(pLastNode, pLastNode->Len());
+ }
+ // check for next error in aCurSel and set aCurSel to that one if any was found
+ Reference< XSpellAlternatives > xAlt = ImpFindNextError(aCurSel);
+ if (xAlt.is())
+ {
+ bRet = true;
+ //find the sentence boundaries
+ EditSelection aSentencePaM = SelectSentence(aCurSel);
+ //make sure that the sentence is never smaller than the error range!
+ if(aSentencePaM.Max().GetIndex() < aCurSel.Max().GetIndex())
+ aSentencePaM.Max() = aCurSel.Max();
+ //add the portion preceding the error
+ EditSelection aStartSelection(aSentencePaM.Min(), aCurSel.Min());
+ if(aStartSelection.HasRange())
+ AddPortionIterated(rEditView, aStartSelection, nullptr, rToFill);
+ //add the error portion
+ AddPortionIterated(rEditView, aCurSel, xAlt, rToFill);
+ //find the end of the sentence
+ //search for all errors in the rest of the sentence and add all the portions
+ do
+ {
+ EditSelection aNextSel(aCurSel.Max(), aSentencePaM.Max());
+ xAlt = ImpFindNextError(aNextSel);
+ if(xAlt.is())
+ {
+ //add the part between the previous and the current error
+ AddPortionIterated(rEditView, EditSelection(aCurSel.Max(), aNextSel.Min()), nullptr, rToFill);
+ //add the current error
+ AddPortionIterated(rEditView, aNextSel, xAlt, rToFill);
+ }
+ else
+ AddPortionIterated(rEditView, EditSelection(aCurSel.Max(), aSentencePaM.Max()), xAlt, rToFill);
+ aCurSel = aNextSel;
+ }
+ while( xAlt.is() );
+
+ //set the selection to the end of the current sentence
+ rEditView.pImpEditView->SetEditSelection(aSentencePaM.Max());
+ }
+ return bRet;
+}
+
+// Adds one portion to the SpellPortions
+void ImpEditEngine::AddPortion(
+ const EditSelection& rSel,
+ const uno::Reference< XSpellAlternatives >& xAlt,
+ svx::SpellPortions& rToFill,
+ bool bIsField)
+{
+ if(!rSel.HasRange())
+ return;
+
+ svx::SpellPortion aPortion;
+ aPortion.sText = GetSelected( rSel );
+ aPortion.eLanguage = GetLanguage( rSel.Min() ).nLang;
+ aPortion.xAlternatives = xAlt;
+ aPortion.bIsField = bIsField;
+ rToFill.push_back(aPortion);
+
+ //save the spelled portions for later use
+ pSpellInfo->aLastSpellPortions.push_back(aPortion);
+ pSpellInfo->aLastSpellContentSelections.push_back(rSel);
+}
+
+// Adds one or more portions of text to the SpellPortions depending on language changes
+void ImpEditEngine::AddPortionIterated(
+ EditView const & rEditView,
+ const EditSelection& rSel,
+ const Reference< XSpellAlternatives >& xAlt,
+ svx::SpellPortions& rToFill)
+{
+ if (!rSel.HasRange())
+ return;
+
+ if(xAlt.is())
+ {
+ AddPortion(rSel, xAlt, rToFill, false);
+ }
+ else
+ {
+ //iterate and search for language attribute changes
+ //save the start and end positions
+ bool bTest = rSel.Min().GetIndex() <= rSel.Max().GetIndex();
+ EditPaM aStart(bTest ? rSel.Min() : rSel.Max());
+ EditPaM aEnd(bTest ? rSel.Max() : rSel.Min());
+ //iterate over the text to find changes in language
+ //set the mark equal to the point
+ EditPaM aCursor(aStart);
+ rEditView.pImpEditView->SetEditSelection( aCursor );
+ LanguageType eStartLanguage = GetLanguage( aCursor ).nLang;
+ //search for a field attribute at the beginning - only the end position
+ //of this field is kept to end a portion at that position
+ const EditCharAttrib* pFieldAttr = aCursor.GetNode()->GetCharAttribs().
+ FindFeature( aCursor.GetIndex() );
+ bool bIsField = pFieldAttr &&
+ pFieldAttr->GetStart() == aCursor.GetIndex() &&
+ pFieldAttr->GetStart() != pFieldAttr->GetEnd() &&
+ pFieldAttr->Which() == EE_FEATURE_FIELD;
+ sal_Int32 nEndField = bIsField ? pFieldAttr->GetEnd() : -1;
+ do
+ {
+ aCursor = CursorRight( aCursor);
+ //determine whether a field and has been reached
+ bool bIsEndField = nEndField == aCursor.GetIndex();
+ //search for a new field attribute
+ const EditCharAttrib* _pFieldAttr = aCursor.GetNode()->GetCharAttribs().
+ FindFeature( aCursor.GetIndex() );
+ bIsField = _pFieldAttr &&
+ _pFieldAttr->GetStart() == aCursor.GetIndex() &&
+ _pFieldAttr->GetStart() != _pFieldAttr->GetEnd() &&
+ _pFieldAttr->Which() == EE_FEATURE_FIELD;
+ //on every new field move the end position
+ if (bIsField)
+ nEndField = _pFieldAttr->GetEnd();
+
+ LanguageType eCurLanguage = GetLanguage( aCursor ).nLang;
+ if(eCurLanguage != eStartLanguage || bIsField || bIsEndField)
+ {
+ eStartLanguage = eCurLanguage;
+ //go one step back - the cursor currently selects the first character
+ //with a different language
+ //create a selection from start to the current Cursor
+ EditSelection aSelection(aStart, aCursor);
+ AddPortion(aSelection, xAlt, rToFill, bIsEndField);
+ aStart = aCursor;
+ }
+ }
+ while(aCursor.GetIndex() < aEnd.GetIndex());
+ EditSelection aSelection(aStart, aCursor);
+ AddPortion(aSelection, xAlt, rToFill, bIsField);
+ }
+}
+
+void ImpEditEngine::ApplyChangedSentence(EditView const & rEditView,
+ const svx::SpellPortions& rNewPortions,
+ bool bRecheck )
+{
+ // Note: rNewPortions.size() == 0 is valid and happens when the whole
+ // sentence got removed in the dialog
+
+ DBG_ASSERT(pSpellInfo, "pSpellInfo not initialized");
+ if (!pSpellInfo || pSpellInfo->aLastSpellPortions.empty()) // no portions -> no text to be changed
+ return;
+
+ // get current paragraph length to calculate later on how the sentence length changed,
+ // in order to place the cursor at the end of the sentence again
+ EditSelection aOldSel( rEditView.pImpEditView->GetEditSelection() );
+ sal_Int32 nOldLen = aOldSel.Max().GetNode()->Len();
+
+ UndoActionStart( EDITUNDO_INSERT );
+ if(pSpellInfo->aLastSpellPortions.size() == rNewPortions.size())
+ {
+ DBG_ASSERT( !rNewPortions.empty(), "rNewPortions should not be empty here" );
+ DBG_ASSERT( pSpellInfo->aLastSpellPortions.size() == pSpellInfo->aLastSpellContentSelections.size(),
+ "aLastSpellPortions and aLastSpellContentSelections size mismatch" );
+
+ //the simple case: the same number of elements on both sides
+ //each changed element has to be applied to the corresponding source element
+ svx::SpellPortions::const_iterator aCurrentNewPortion = rNewPortions.end();
+ svx::SpellPortions::const_iterator aCurrentOldPortion = pSpellInfo->aLastSpellPortions.end();
+ SpellContentSelections::const_iterator aCurrentOldPosition = pSpellInfo->aLastSpellContentSelections.end();
+ bool bSetToEnd = false;
+ do
+ {
+ --aCurrentNewPortion;
+ --aCurrentOldPortion;
+ --aCurrentOldPosition;
+ //set the cursor to the end of the sentence - necessary to
+ //resume there at the next step
+ if(!bSetToEnd)
+ {
+ bSetToEnd = true;
+ rEditView.pImpEditView->SetEditSelection( aCurrentOldPosition->Max() );
+ }
+
+ SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( aCurrentNewPortion->eLanguage );
+ sal_uInt16 nLangWhichId = EE_CHAR_LANGUAGE;
+ switch(nScriptType)
+ {
+ case SvtScriptType::ASIAN : nLangWhichId = EE_CHAR_LANGUAGE_CJK; break;
+ case SvtScriptType::COMPLEX : nLangWhichId = EE_CHAR_LANGUAGE_CTL; break;
+ default: break;
+ }
+ if(aCurrentNewPortion->sText != aCurrentOldPortion->sText)
+ {
+ //change text and apply language
+ SfxItemSet aSet( maEditDoc.GetItemPool(), nLangWhichId, nLangWhichId );
+ aSet.Put(SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId));
+ SetAttribs( *aCurrentOldPosition, aSet );
+ ImpInsertText( *aCurrentOldPosition, aCurrentNewPortion->sText );
+ }
+ else if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage)
+ {
+ //apply language
+ SfxItemSet aSet( maEditDoc.GetItemPool(), nLangWhichId, nLangWhichId);
+ aSet.Put(SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId));
+ SetAttribs( *aCurrentOldPosition, aSet );
+ }
+ }
+ while(aCurrentNewPortion != rNewPortions.begin());
+ }
+ else
+ {
+ DBG_ASSERT( !pSpellInfo->aLastSpellContentSelections.empty(), "aLastSpellContentSelections should not be empty here" );
+
+ //select the complete sentence
+ SpellContentSelections::const_iterator aCurrentEndPosition = pSpellInfo->aLastSpellContentSelections.end();
+ --aCurrentEndPosition;
+ SpellContentSelections::const_iterator aCurrentStartPosition = pSpellInfo->aLastSpellContentSelections.begin();
+ EditSelection aAllSentence(aCurrentStartPosition->Min(), aCurrentEndPosition->Max());
+
+ //delete the sentence completely
+ ImpDeleteSelection( aAllSentence );
+ EditPaM aCurrentPaM = aAllSentence.Min();
+ for(const auto& rCurrentNewPortion : rNewPortions)
+ {
+ //set the language attribute
+ LanguageType eCurLanguage = GetLanguage( aCurrentPaM ).nLang;
+ if(eCurLanguage != rCurrentNewPortion.eLanguage)
+ {
+ SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( rCurrentNewPortion.eLanguage );
+ sal_uInt16 nLangWhichId = EE_CHAR_LANGUAGE;
+ switch(nScriptType)
+ {
+ case SvtScriptType::ASIAN : nLangWhichId = EE_CHAR_LANGUAGE_CJK; break;
+ case SvtScriptType::COMPLEX : nLangWhichId = EE_CHAR_LANGUAGE_CTL; break;
+ default: break;
+ }
+ SfxItemSet aSet( maEditDoc.GetItemPool(), nLangWhichId, nLangWhichId);
+ aSet.Put(SvxLanguageItem(rCurrentNewPortion.eLanguage, nLangWhichId));
+ SetAttribs( aCurrentPaM, aSet );
+ }
+ //insert the new string and set the cursor to the end of the inserted string
+ aCurrentPaM = ImpInsertText( aCurrentPaM , rCurrentNewPortion.sText );
+ }
+ }
+ UndoActionEnd();
+
+ EditPaM aNext;
+ if (bRecheck)
+ aNext = pSpellInfo->aCurSentenceStart;
+ else
+ {
+ // restore cursor position to the end of the modified sentence.
+ // (This will define the continuation position for spell/grammar checking)
+ // First: check if the sentence/para length changed
+ const sal_Int32 nDelta = rEditView.pImpEditView->GetEditSelection().Max().GetNode()->Len() - nOldLen;
+ const sal_Int32 nEndOfSentence = aOldSel.Max().GetIndex() + nDelta;
+ aNext = EditPaM( aOldSel.Max().GetNode(), nEndOfSentence );
+ }
+ rEditView.pImpEditView->SetEditSelection( aNext );
+
+ if (IsUpdateLayout())
+ FormatAndLayout();
+ maEditDoc.SetModified(true);
+}
+
+void ImpEditEngine::PutSpellingToSentenceStart( EditView const & rEditView )
+{
+ if( pSpellInfo && !pSpellInfo->aLastSpellContentSelections.empty() )
+ {
+ rEditView.pImpEditView->SetEditSelection( pSpellInfo->aLastSpellContentSelections.begin()->Min() );
+ }
+}
+
+
+void ImpEditEngine::DoOnlineSpelling( ContentNode* pThisNodeOnly, bool bSpellAtCursorPos, bool bInterruptible )
+{
+ /*
+ It will iterate over all the paragraphs, paragraphs with only
+ invalidated wrong list will be checked ...
+
+ All the words are checked in the invalidated region. Is a word wrong,
+ but not in the wrong list, or vice versa, the range of the word will be
+ invalidated
+ (no Invalidate, but if only transitions wrong from right =>, simple Paint,
+ even out properly with VDev on transitions from wrong => right)
+ */
+
+ if ( !xSpeller.is() )
+ return;
+
+ EditPaM aCursorPos;
+ if( pActiveView && !bSpellAtCursorPos )
+ {
+ aCursorPos = pActiveView->pImpEditView->GetEditSelection().Max();
+ }
+
+ bool bRestartTimer = false;
+
+ ContentNode* pLastNode = maEditDoc.GetObject( maEditDoc.Count() - 1 );
+ sal_Int32 nNodes = GetEditDoc().Count();
+ sal_Int32 nInvalids = 0;
+ Sequence< PropertyValue > aEmptySeq;
+ for ( sal_Int32 n = 0; n < nNodes; n++ )
+ {
+ ContentNode* pNode = GetEditDoc().GetObject( n );
+ if ( pThisNodeOnly )
+ pNode = pThisNodeOnly;
+
+ pNode->EnsureWrongList();
+ if (!pNode->GetWrongList()->IsValid())
+ {
+ WrongList* pWrongList = pNode->GetWrongList();
+ const size_t nInvStart = pWrongList->GetInvalidStart();
+ const size_t nInvEnd = pWrongList->GetInvalidEnd();
+
+ sal_Int32 nPaintFrom = -1;
+ sal_Int32 nPaintTo = 0;
+ bool bSimpleRepaint = true;
+
+ pWrongList->SetValid();
+
+ EditPaM aPaM( pNode, nInvStart );
+ EditSelection aSel( aPaM, aPaM );
+ while ( aSel.Max().GetNode() == pNode )
+ {
+ if ( ( o3tl::make_unsigned(aSel.Min().GetIndex()) > nInvEnd )
+ || ( ( aSel.Max().GetNode() == pLastNode ) && ( aSel.Max().GetIndex() >= pLastNode->Len() ) ) )
+ break; // Document end or end of invalid region
+
+ aSel = SelectWord( aSel, i18n::WordType::DICTIONARY_WORD );
+ // If afterwards a dot, this must be handed over!
+ // If an abbreviation ...
+ bool bDottAdded = false;
+ if ( aSel.Max().GetIndex() < aSel.Max().GetNode()->Len() )
+ {
+ sal_Unicode cNext = aSel.Max().GetNode()->GetChar( aSel.Max().GetIndex() );
+ if ( cNext == '.' )
+ {
+ aSel.Max().SetIndex( aSel.Max().GetIndex()+1 );
+ bDottAdded = true;
+ }
+ }
+ OUString aWord = GetSelected(aSel);
+
+ bool bChanged = false;
+ if (!aWord.isEmpty())
+ {
+ const sal_Int32 nWStart = aSel.Min().GetIndex();
+ const sal_Int32 nWEnd = aSel.Max().GetIndex();
+ if ( !xSpeller->isValid( aWord, static_cast<sal_uInt16>(GetLanguage( EditPaM( aSel.Min().GetNode(), nWStart+1 ) ).nLang), aEmptySeq ) )
+ {
+ // Check if already marked correctly...
+ const sal_Int32 nXEnd = bDottAdded ? nWEnd -1 : nWEnd;
+ if ( !pWrongList->HasWrong( nWStart, nXEnd ) )
+ {
+ // Mark Word as wrong...
+ // But only when not at Cursor-Position...
+ bool bCursorPos = false;
+ if ( aCursorPos.GetNode() == pNode )
+ {
+ if ( ( nWStart <= aCursorPos.GetIndex() ) && nWEnd >= aCursorPos.GetIndex() )
+ bCursorPos = true;
+ }
+ if ( bCursorPos )
+ {
+ // Then continue to mark as invalid ...
+ pWrongList->ResetInvalidRange(nWStart, nWEnd);
+ bRestartTimer = true;
+ }
+ else
+ {
+ // It may be that the Wrongs in the list are not
+ // spanning exactly over words because the
+ // WordDelimiters during expansion are not
+ // evaluated.
+ pWrongList->InsertWrong(nWStart, nXEnd);
+ bChanged = true;
+ }
+ }
+ }
+ else
+ {
+ // Check if not marked as wrong
+ if ( pWrongList->HasAnyWrong( nWStart, nWEnd ) )
+ {
+ pWrongList->ClearWrongs( nWStart, nWEnd, pNode );
+ bSimpleRepaint = false;
+ bChanged = true;
+ }
+ }
+ if ( bChanged )
+ {
+ if ( nPaintFrom<0 )
+ nPaintFrom = nWStart;
+ nPaintTo = nWEnd;
+ }
+ }
+
+ EditPaM aLastEnd( aSel.Max() );
+ aSel = WordRight( aSel.Max(), i18n::WordType::DICTIONARY_WORD );
+ if ( bChanged && ( aSel.Min().GetNode() == pNode ) &&
+ ( aSel.Min().GetIndex()-aLastEnd.GetIndex() > 1 ) )
+ {
+ // If two words are separated by more than one blank, it
+ // can happen that when splitting a Wrongs the start of
+ // the second word is before the actually word
+ pWrongList->ClearWrongs( aLastEnd.GetIndex(), aSel.Min().GetIndex(), pNode );
+ }
+ }
+
+ // Invalidate?
+ if ( nPaintFrom>=0 )
+ {
+ maStatus.GetStatusWord() |= EditStatusFlags::WRONGWORDCHANGED;
+ CallStatusHdl();
+
+ if (!aEditViews.empty())
+ {
+ // For SimpleRepaint one was painted over a range without
+ // reaching VDEV, but then one would have to intersect, c
+ // clipping, ... over all views. Probably not worthwhile.
+ EditPaM aStartPaM( pNode, nPaintFrom );
+ EditPaM aEndPaM( pNode, nPaintTo );
+ tools::Rectangle aStartCursor( PaMtoEditCursor( aStartPaM ) );
+ tools::Rectangle aEndCursor( PaMtoEditCursor( aEndPaM ) );
+ DBG_ASSERT( aInvalidRect.IsEmpty(), "InvalidRect set!" );
+ aInvalidRect.SetLeft( 0 );
+ aInvalidRect.SetRight( GetPaperSize().Width() );
+ aInvalidRect.SetTop( aStartCursor.Top() );
+ aInvalidRect.SetBottom( aEndCursor.Bottom() );
+ if ( pActiveView && pActiveView->HasSelection() )
+ {
+ // Then no output through VDev.
+ UpdateViews();
+ }
+ else if ( bSimpleRepaint )
+ {
+ for (EditView* pView : aEditViews)
+ {
+ tools::Rectangle aClipRect( aInvalidRect );
+ aClipRect.Intersection( pView->GetVisArea() );
+ if ( !aClipRect.IsEmpty() )
+ {
+ // convert to window coordinates...
+ aClipRect.SetPos( pView->pImpEditView->GetWindowPos( aClipRect.TopLeft() ) );
+ pView->pImpEditView->InvalidateAtWindow(aClipRect);
+ }
+ }
+ }
+ else
+ {
+ UpdateViews( pActiveView );
+ }
+ aInvalidRect = tools::Rectangle();
+ }
+ }
+ // After two corrected nodes give up the control...
+ nInvalids++;
+ if ( bInterruptible && ( nInvalids >= 2 ) )
+ {
+ bRestartTimer = true;
+ break;
+ }
+ }
+
+ if ( pThisNodeOnly )
+ break;
+ }
+ if ( bRestartTimer )
+ aOnlineSpellTimer.Start();
+}
+
+
+EESpellState ImpEditEngine::HasSpellErrors()
+{
+ DBG_ASSERT( xSpeller.is(), "No spell checker set!" );
+
+ ContentNode* pLastNode = maEditDoc.GetObject( maEditDoc.Count() - 1 );
+ EditSelection aCurSel( maEditDoc.GetStartPaM() );
+
+ OUString aWord;
+ Reference< XSpellAlternatives > xSpellAlt;
+ Sequence< PropertyValue > aEmptySeq;
+ while ( !xSpellAlt.is() )
+ {
+ if ( ( aCurSel.Max().GetNode() == pLastNode ) &&
+ ( aCurSel.Max().GetIndex() >= pLastNode->Len() ) )
+ {
+ return EESpellState::Ok;
+ }
+
+ aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD );
+ aWord = GetSelected( aCurSel );
+ if ( !aWord.isEmpty() )
+ {
+ LanguageType eLang = GetLanguage( aCurSel.Max() ).nLang;
+ SvxSpellWrapper::CheckSpellLang( xSpeller, eLang );
+ xSpellAlt = xSpeller->spell( aWord, static_cast<sal_uInt16>(eLang), aEmptySeq );
+ }
+ aCurSel = WordRight( aCurSel.Max(), css::i18n::WordType::DICTIONARY_WORD );
+ }
+
+ return EESpellState::ErrorFound;
+}
+
+void ImpEditEngine::ClearSpellErrors()
+{
+ maEditDoc.ClearSpellErrors();
+}
+
+EESpellState ImpEditEngine::StartThesaurus(EditView* pEditView, weld::Widget* pDialogParent)
+{
+ EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() );
+ if ( !aCurSel.HasRange() )
+ aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD );
+ OUString aWord( GetSelected( aCurSel ) );
+
+ Reference< XThesaurus > xThes( LinguMgr::GetThesaurus() );
+ if (!xThes.is())
+ return EESpellState::ErrorFound;
+
+ EditAbstractDialogFactory* pFact = EditAbstractDialogFactory::Create();
+ ScopedVclPtr<AbstractThesaurusDialog> xDlg(pFact->CreateThesaurusDialog(pDialogParent, xThes,
+ aWord, GetLanguage( aCurSel.Max() ).nLang ));
+ if (xDlg->Execute() == RET_OK)
+ {
+ // Replace Word...
+ pEditView->pImpEditView->DrawSelectionXOR();
+ pEditView->pImpEditView->SetEditSelection( aCurSel );
+ pEditView->pImpEditView->DrawSelectionXOR();
+ pEditView->InsertText(xDlg->GetWord());
+ pEditView->ShowCursor(true, false);
+ }
+
+ return EESpellState::Ok;
+}
+
+sal_Int32 ImpEditEngine::StartSearchAndReplace( EditView* pEditView, const SvxSearchItem& rSearchItem )
+{
+ sal_Int32 nFound = 0;
+
+ EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() );
+
+ // FIND_ALL is not possible without multiple selection.
+ if ( ( rSearchItem.GetCommand() == SvxSearchCmd::FIND ) ||
+ ( rSearchItem.GetCommand() == SvxSearchCmd::FIND_ALL ) )
+ {
+ if ( Search( rSearchItem, pEditView ) )
+ nFound++;
+ }
+ else if ( rSearchItem.GetCommand() == SvxSearchCmd::REPLACE )
+ {
+ // The word is selected if the user not altered the selection
+ // in between:
+ if ( aCurSel.HasRange() )
+ {
+ pEditView->InsertText( rSearchItem.GetReplaceString() );
+ nFound = 1;
+ }
+ else
+ if( Search( rSearchItem, pEditView ) )
+ nFound = 1;
+ }
+ else if ( rSearchItem.GetCommand() == SvxSearchCmd::REPLACE_ALL )
+ {
+ // The Writer replaces all front beginning to end ...
+ SvxSearchItem aTmpItem( rSearchItem );
+ aTmpItem.SetBackward( false );
+
+ pEditView->pImpEditView->DrawSelectionXOR();
+
+ aCurSel.Adjust( maEditDoc );
+ EditPaM aStartPaM = aTmpItem.GetSelection() ? aCurSel.Min() : maEditDoc.GetStartPaM();
+ EditSelection aFoundSel( aCurSel.Max() );
+ bool bFound = ImpSearch( aTmpItem, aCurSel, aStartPaM, aFoundSel );
+ if ( bFound )
+ UndoActionStart( EDITUNDO_REPLACEALL );
+ while ( bFound )
+ {
+ nFound++;
+ aStartPaM = ImpInsertText( aFoundSel, rSearchItem.GetReplaceString() );
+ bFound = ImpSearch( aTmpItem, aCurSel, aStartPaM, aFoundSel );
+ }
+ if ( nFound )
+ {
+ EditPaM aNewPaM( aFoundSel.Max() );
+ if ( aNewPaM.GetIndex() > aNewPaM.GetNode()->Len() )
+ aNewPaM.SetIndex( aNewPaM.GetNode()->Len() );
+ pEditView->pImpEditView->SetEditSelection( aNewPaM );
+ FormatAndLayout( pEditView );
+ UndoActionEnd();
+ }
+ else
+ {
+ pEditView->pImpEditView->DrawSelectionXOR();
+ pEditView->ShowCursor( true, false );
+ }
+ }
+ return nFound;
+}
+
+bool ImpEditEngine::Search( const SvxSearchItem& rSearchItem, EditView* pEditView )
+{
+ EditSelection aSel( pEditView->pImpEditView->GetEditSelection() );
+ aSel.Adjust( maEditDoc );
+ EditPaM aStartPaM( aSel.Max() );
+ if ( rSearchItem.GetSelection() && !rSearchItem.GetBackward() )
+ aStartPaM = aSel.Min();
+
+ EditSelection aFoundSel;
+ bool bFound = ImpSearch( rSearchItem, aSel, aStartPaM, aFoundSel );
+ if ( bFound && ( aFoundSel == aSel ) ) // For backwards-search
+ {
+ aStartPaM = aSel.Min();
+ bFound = ImpSearch( rSearchItem, aSel, aStartPaM, aFoundSel );
+ }
+
+ pEditView->pImpEditView->DrawSelectionXOR();
+ if ( bFound )
+ {
+ // First, set the minimum, so the whole word is in the visible range.
+ pEditView->pImpEditView->SetEditSelection( aFoundSel.Min() );
+ pEditView->ShowCursor( true, false );
+ pEditView->pImpEditView->SetEditSelection( aFoundSel );
+ }
+ else
+ pEditView->pImpEditView->SetEditSelection( aSel.Max() );
+
+ pEditView->pImpEditView->DrawSelectionXOR();
+ pEditView->ShowCursor( true, false );
+ return bFound;
+}
+
+bool ImpEditEngine::ImpSearch( const SvxSearchItem& rSearchItem,
+ const EditSelection& rSearchSelection, const EditPaM& rStartPos, EditSelection& rFoundSel )
+{
+ i18nutil::SearchOptions2 aSearchOptions( rSearchItem.GetSearchOptions() );
+ aSearchOptions.Locale = GetLocale( rStartPos );
+
+ bool bBack = rSearchItem.GetBackward();
+ bool bSearchInSelection = rSearchItem.GetSelection();
+ sal_Int32 nStartNode = maEditDoc.GetPos( rStartPos.GetNode() );
+ sal_Int32 nEndNode;
+ if ( bSearchInSelection )
+ {
+ nEndNode = maEditDoc.GetPos( bBack ? rSearchSelection.Min().GetNode() : rSearchSelection.Max().GetNode() );
+ }
+ else
+ {
+ nEndNode = bBack ? 0 : maEditDoc.Count()-1;
+ }
+
+ utl::TextSearch aSearcher( aSearchOptions );
+
+ // iterate over the paragraphs ...
+ for ( sal_Int32 nNode = nStartNode;
+ bBack ? ( nNode >= nEndNode ) : ( nNode <= nEndNode) ;
+ bBack ? nNode-- : nNode++ )
+ {
+ // For backwards-search if nEndNode = 0:
+ if ( nNode < 0 )
+ return false;
+
+ ContentNode* pNode = maEditDoc.GetObject( nNode );
+
+ sal_Int32 nStartPos = 0;
+ sal_Int32 nEndPos = pNode->GetExpandedLen();
+ if ( nNode == nStartNode )
+ {
+ if ( bBack )
+ nEndPos = rStartPos.GetIndex();
+ else
+ nStartPos = rStartPos.GetIndex();
+ }
+ if ( ( nNode == nEndNode ) && bSearchInSelection )
+ {
+ if ( bBack )
+ nStartPos = rSearchSelection.Min().GetIndex();
+ else
+ nEndPos = rSearchSelection.Max().GetIndex();
+ }
+
+ // Searching ...
+ OUString aParaStr( pNode->GetExpandedText() );
+ bool bFound = false;
+ if ( bBack )
+ {
+ sal_Int32 nTemp;
+ nTemp = nStartPos;
+ nStartPos = nEndPos;
+ nEndPos = nTemp;
+
+ bFound = aSearcher.SearchBackward( aParaStr, &nStartPos, &nEndPos);
+ }
+ else
+ {
+ bFound = aSearcher.SearchForward( aParaStr, &nStartPos, &nEndPos);
+ }
+ if ( bFound )
+ {
+ pNode->UnExpandPositions( nStartPos, nEndPos );
+
+ rFoundSel.Min().SetNode( pNode );
+ rFoundSel.Min().SetIndex( nStartPos );
+ rFoundSel.Max().SetNode( pNode );
+ rFoundSel.Max().SetIndex( nEndPos );
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ImpEditEngine::HasText( const SvxSearchItem& rSearchItem )
+{
+ SvxSearchItem aTmpItem( rSearchItem );
+ aTmpItem.SetBackward( false );
+ aTmpItem.SetSelection( false );
+
+ EditPaM aStartPaM( maEditDoc.GetStartPaM() );
+ EditSelection aDummySel( aStartPaM );
+ EditSelection aFoundSel;
+ return ImpSearch( aTmpItem, aDummySel, aStartPaM, aFoundSel );
+}
+
+void ImpEditEngine::SetAutoCompleteText(const OUString& rStr, bool bClearTipWindow)
+{
+ maAutoCompleteText = rStr;
+ if ( bClearTipWindow && pActiveView )
+ Help::ShowQuickHelp( pActiveView->GetWindow(), tools::Rectangle(), OUString() );
+}
+
+namespace
+{
+ struct eeTransliterationChgData
+ {
+ sal_Int32 nStart;
+ sal_Int32 nLen;
+ EditSelection aSelection;
+ OUString aNewText;
+ uno::Sequence< sal_Int32 > aOffsets;
+ };
+}
+
+EditSelection ImpEditEngine::TransliterateText( const EditSelection& rSelection, TransliterationFlags nTransliterationMode )
+{
+ uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
+ if (!_xBI.is())
+ return rSelection;
+
+ EditSelection aSel( rSelection );
+ aSel.Adjust( maEditDoc );
+
+ if ( !aSel.HasRange() )
+ {
+ /* Cursor is inside of a word */
+ if (nTransliterationMode == TransliterationFlags::SENTENCE_CASE)
+ aSel = SelectSentence( aSel );
+ else
+ aSel = SelectWord( aSel );
+ }
+
+ // tdf#107176: if there's still no range, just return aSel
+ if ( !aSel.HasRange() )
+ return aSel;
+
+ EditSelection aNewSel( aSel );
+
+ const sal_Int32 nStartNode = maEditDoc.GetPos( aSel.Min().GetNode() );
+ const sal_Int32 nEndNode = maEditDoc.GetPos( aSel.Max().GetNode() );
+
+ bool bChanges = false;
+ bool bLenChanged = false;
+ std::unique_ptr<EditUndoTransliteration> pUndo;
+
+ utl::TransliterationWrapper aTransliterationWrapper( ::comphelper::getProcessComponentContext(), nTransliterationMode );
+ bool bConsiderLanguage = aTransliterationWrapper.needLanguageForTheMode();
+
+ for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ )
+ {
+ ContentNode* pNode = maEditDoc.GetObject( nNode );
+ const OUString& aNodeStr = pNode->GetString();
+ const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0;
+ const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : aNodeStr.getLength(); // can also be == nStart!
+
+ sal_Int32 nCurrentStart = nStartPos;
+ sal_Int32 nCurrentEnd = nEndPos;
+ LanguageType nLanguage = LANGUAGE_SYSTEM;
+
+ // since we don't use Hiragana/Katakana or half-width/full-width transliterations here
+ // it is fine to use ANYWORD_IGNOREWHITESPACES. (ANY_WORD btw is broken and will
+ // occasionally miss words in consecutive sentences). Also with ANYWORD_IGNOREWHITESPACES
+ // text like 'just-in-time' will be converted to 'Just-In-Time' which seems to be the
+ // proper thing to do.
+ const sal_Int16 nWordType = i18n::WordType::ANYWORD_IGNOREWHITESPACES;
+
+ //! In order to have less trouble with changing text size, e.g. because
+ //! of ligatures or German small sz being resolved, we need to process
+ //! the text replacements from end to start.
+ //! This way the offsets for the yet to be changed words will be
+ //! left unchanged by the already replaced text.
+ //! For this we temporarily save the changes to be done in this vector
+ std::vector< eeTransliterationChgData > aChanges;
+ eeTransliterationChgData aChgData;
+
+ if (nTransliterationMode == TransliterationFlags::TITLE_CASE)
+ {
+ // for 'capitalize every word' we need to iterate over each word
+
+ i18n::Boundary aSttBndry;
+ i18n::Boundary aEndBndry;
+ aSttBndry = _xBI->getWordBoundary(
+ aNodeStr, nStartPos,
+ GetLocale( EditPaM( pNode, nStartPos + 1 ) ),
+ nWordType, true /*prefer forward direction*/);
+ aEndBndry = _xBI->getWordBoundary(
+ aNodeStr, nEndPos,
+ GetLocale( EditPaM( pNode, nEndPos + 1 ) ),
+ nWordType, false /*prefer backward direction*/);
+
+ // prevent backtracking to the previous word if selection is at word boundary
+ if (aSttBndry.endPos <= nStartPos)
+ {
+ aSttBndry = _xBI->nextWord(
+ aNodeStr, aSttBndry.endPos,
+ GetLocale( EditPaM( pNode, aSttBndry.endPos + 1 ) ),
+ nWordType);
+ }
+ // prevent advancing to the next word if selection is at word boundary
+ if (aEndBndry.startPos >= nEndPos)
+ {
+ aEndBndry = _xBI->previousWord(
+ aNodeStr, aEndBndry.startPos,
+ GetLocale( EditPaM( pNode, aEndBndry.startPos + 1 ) ),
+ nWordType);
+ }
+
+ /* Nothing to do if user selection lies entirely outside of word start and end boundary computed above.
+ * Skip this node, because otherwise the below logic for constraining to the selection will fail */
+ if (aSttBndry.startPos >= aSel.Max().GetIndex() || aEndBndry.endPos <= aSel.Min().GetIndex()) {
+ continue;
+ }
+
+ // prevent going outside of the user's selection, which may
+ // start or end in the middle of a word
+ if (nNode == nStartNode) {
+ aSttBndry.startPos = std::max(aSttBndry.startPos, aSel.Min().GetIndex());
+ aSttBndry.endPos = std::min(aSttBndry.endPos, aSel.Max().GetIndex());
+ aEndBndry.startPos = std::max(aEndBndry.startPos, aSttBndry.startPos);
+ aEndBndry.endPos = std::min(aEndBndry.endPos, aSel.Max().GetIndex());
+ }
+
+ i18n::Boundary aCurWordBndry( aSttBndry );
+ while (aCurWordBndry.endPos && aCurWordBndry.startPos <= aEndBndry.startPos)
+ {
+ nCurrentStart = aCurWordBndry.startPos;
+ nCurrentEnd = aCurWordBndry.endPos;
+ sal_Int32 nLen = nCurrentEnd - nCurrentStart;
+ DBG_ASSERT( nLen > 0, "invalid word length of 0" );
+
+ Sequence< sal_Int32 > aOffsets;
+ OUString aNewText( aTransliterationWrapper.transliterate(aNodeStr,
+ GetLanguage( EditPaM( pNode, nCurrentStart + 1 ) ).nLang,
+ nCurrentStart, nLen, &aOffsets ));
+
+ if (aNodeStr != aNewText)
+ {
+ aChgData.nStart = nCurrentStart;
+ aChgData.nLen = nLen;
+ aChgData.aSelection = EditSelection( EditPaM( pNode, nCurrentStart ), EditPaM( pNode, nCurrentEnd ) );
+ aChgData.aNewText = aNewText;
+ aChgData.aOffsets = aOffsets;
+ aChanges.push_back( aChgData );
+ }
+#if OSL_DEBUG_LEVEL > 1
+ OUString aSelTxt ( GetSelected( aChgData.aSelection ) );
+ (void) aSelTxt;
+#endif
+
+ aCurWordBndry = _xBI->nextWord(aNodeStr, nCurrentStart,
+ GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ),
+ nWordType);
+ }
+ DBG_ASSERT( nCurrentEnd >= aEndBndry.endPos, "failed to reach end of transliteration" );
+ }
+ else if (nTransliterationMode == TransliterationFlags::SENTENCE_CASE)
+ {
+ // for 'sentence case' we need to iterate sentence by sentence
+
+ sal_Int32 nLastStart = _xBI->beginOfSentence(
+ aNodeStr, nEndPos,
+ GetLocale( EditPaM( pNode, nEndPos + 1 ) ) );
+ sal_Int32 nLastEnd = _xBI->endOfSentence(
+ aNodeStr, nLastStart,
+ GetLocale( EditPaM( pNode, nLastStart + 1 ) ) );
+
+ // extend nCurrentStart, nCurrentEnd to the current sentence boundaries
+ nCurrentStart = _xBI->beginOfSentence(
+ aNodeStr, nStartPos,
+ GetLocale( EditPaM( pNode, nStartPos + 1 ) ) );
+ nCurrentEnd = _xBI->endOfSentence(
+ aNodeStr, nCurrentStart,
+ GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ) );
+
+ // prevent backtracking to the previous sentence if selection starts at end of a sentence
+ if (nCurrentEnd <= nStartPos)
+ {
+ // now nCurrentStart is probably located on a non-letter word. (unless we
+ // are in Asian text with no spaces...)
+ // Thus to get the real sentence start we should locate the next real word,
+ // that is one found by DICTIONARY_WORD
+ i18n::Boundary aBndry = _xBI->nextWord( aNodeStr, nCurrentEnd,
+ GetLocale( EditPaM( pNode, nCurrentEnd + 1 ) ),
+ i18n::WordType::DICTIONARY_WORD);
+
+ // now get new current sentence boundaries
+ nCurrentStart = _xBI->beginOfSentence(
+ aNodeStr, aBndry.startPos,
+ GetLocale( EditPaM( pNode, aBndry.startPos + 1 ) ) );
+ nCurrentEnd = _xBI->endOfSentence(
+ aNodeStr, nCurrentStart,
+ GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ) );
+ }
+ // prevent advancing to the next sentence if selection ends at start of a sentence
+ if (nLastStart >= nEndPos)
+ {
+ // now nCurrentStart is probably located on a non-letter word. (unless we
+ // are in Asian text with no spaces...)
+ // Thus to get the real sentence start we should locate the previous real word,
+ // that is one found by DICTIONARY_WORD
+ i18n::Boundary aBndry = _xBI->previousWord( aNodeStr, nLastStart,
+ GetLocale( EditPaM( pNode, nLastStart + 1 ) ),
+ i18n::WordType::DICTIONARY_WORD);
+ nLastEnd = _xBI->endOfSentence(
+ aNodeStr, aBndry.startPos,
+ GetLocale( EditPaM( pNode, aBndry.startPos + 1 ) ) );
+ if (nCurrentEnd > nLastEnd)
+ nCurrentEnd = nLastEnd;
+ }
+
+ // prevent making any change outside of the user's selection
+ nCurrentStart = std::max(aSel.Min().GetIndex(), nCurrentStart);
+ nCurrentEnd = std::min(aSel.Max().GetIndex(), nCurrentEnd);
+ nLastStart = std::max(aSel.Min().GetIndex(), nLastStart);
+ nLastEnd = std::min(aSel.Max().GetIndex(), nLastEnd);
+
+ while (nCurrentStart < nLastEnd)
+ {
+ const sal_Int32 nLen = nCurrentEnd - nCurrentStart;
+ DBG_ASSERT( nLen > 0, "invalid word length of 0" );
+
+ Sequence< sal_Int32 > aOffsets;
+ OUString aNewText( aTransliterationWrapper.transliterate( aNodeStr,
+ GetLanguage( EditPaM( pNode, nCurrentStart + 1 ) ).nLang,
+ nCurrentStart, nLen, &aOffsets ));
+
+ if (aNodeStr != aNewText)
+ {
+ aChgData.nStart = nCurrentStart;
+ aChgData.nLen = nLen;
+ aChgData.aSelection = EditSelection( EditPaM( pNode, nCurrentStart ), EditPaM( pNode, nCurrentEnd ) );
+ aChgData.aNewText = aNewText;
+ aChgData.aOffsets = aOffsets;
+ aChanges.push_back( aChgData );
+ }
+
+ i18n::Boundary aFirstWordBndry = _xBI->nextWord(
+ aNodeStr, nCurrentEnd,
+ GetLocale( EditPaM( pNode, nCurrentEnd + 1 ) ),
+ nWordType);
+ nCurrentStart = aFirstWordBndry.startPos;
+ nCurrentEnd = _xBI->endOfSentence(
+ aNodeStr, nCurrentStart,
+ GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ) );
+ }
+ DBG_ASSERT( nCurrentEnd >= nLastEnd, "failed to reach end of transliteration" );
+ }
+ else
+ {
+ do
+ {
+ if ( bConsiderLanguage )
+ {
+ nLanguage = GetLanguage( EditPaM( pNode, nCurrentStart+1 ), &nCurrentEnd ).nLang;
+ if ( nCurrentEnd > nEndPos )
+ nCurrentEnd = nEndPos;
+ }
+
+ const sal_Int32 nLen = nCurrentEnd - nCurrentStart;
+
+ Sequence< sal_Int32 > aOffsets;
+ OUString aNewText( aTransliterationWrapper.transliterate( aNodeStr, nLanguage, nCurrentStart, nLen, &aOffsets ) );
+
+ if (aNodeStr != aNewText)
+ {
+ aChgData.nStart = nCurrentStart;
+ aChgData.nLen = nLen;
+ aChgData.aSelection = EditSelection( EditPaM( pNode, nCurrentStart ), EditPaM( pNode, nCurrentEnd ) );
+ aChgData.aNewText = aNewText;
+ aChgData.aOffsets = aOffsets;
+ aChanges.push_back( aChgData );
+ }
+
+ nCurrentStart = nCurrentEnd;
+ } while( nCurrentEnd < nEndPos );
+ }
+
+ if (!aChanges.empty())
+ {
+ // Create a single UndoAction on Demand for all the changes ...
+ if ( !pUndo && IsUndoEnabled() && !IsInUndo() )
+ {
+ // adjust selection to include all changes
+ for (const eeTransliterationChgData & aChange : aChanges)
+ {
+ const EditSelection &rSel = aChange.aSelection;
+ if (aSel.Min().GetNode() == rSel.Min().GetNode() &&
+ aSel.Min().GetIndex() > rSel.Min().GetIndex())
+ aSel.Min().SetIndex( rSel.Min().GetIndex() );
+ if (aSel.Max().GetNode() == rSel.Max().GetNode() &&
+ aSel.Max().GetIndex() < rSel.Max().GetIndex())
+ aSel.Max().SetIndex( rSel.Max().GetIndex() );
+ }
+ aNewSel = aSel;
+
+ ESelection aESel( CreateESel( aSel ) );
+ pUndo.reset(new EditUndoTransliteration(pEditEngine, aESel, nTransliterationMode));
+
+ const bool bSingleNode = aSel.Min().GetNode()== aSel.Max().GetNode();
+ const bool bHasAttribs = aSel.Min().GetNode()->GetCharAttribs().HasAttrib( aSel.Min().GetIndex(), aSel.Max().GetIndex() );
+ if (bSingleNode && !bHasAttribs)
+ pUndo->SetText( aSel.Min().GetNode()->Copy( aSel.Min().GetIndex(), aSel.Max().GetIndex()-aSel.Min().GetIndex() ) );
+ else
+ pUndo->SetText( CreateTextObject( aSel, nullptr ) );
+ }
+
+ // now apply the changes from end to start to leave the offsets of the
+ // yet unchanged text parts remain the same.
+ for (size_t i = 0; i < aChanges.size(); ++i)
+ {
+ eeTransliterationChgData& rData = aChanges[ aChanges.size() - 1 - i ];
+
+ bChanges = true;
+ if (rData.nLen != rData.aNewText.getLength())
+ bLenChanged = true;
+
+ // Change text without losing the attributes
+ const sal_Int32 nDiffs =
+ ReplaceTextOnly( rData.aSelection.Min().GetNode(),
+ rData.nStart, rData.aNewText, rData.aOffsets );
+
+ // adjust selection in end node to possibly changed size
+ if (aSel.Max().GetNode() == rData.aSelection.Max().GetNode())
+ aNewSel.Max().SetIndex( aNewSel.Max().GetIndex() + nDiffs );
+
+ sal_Int32 nSelNode = maEditDoc.GetPos( rData.aSelection.Min().GetNode() );
+ ParaPortion* pParaPortion = GetParaPortions()[nSelNode];
+ pParaPortion->MarkSelectionInvalid( rData.nStart );
+ }
+ }
+ }
+
+ if ( pUndo )
+ {
+ ESelection aESel( CreateESel( aNewSel ) );
+ pUndo->SetNewSelection( aESel );
+ InsertUndo( std::move(pUndo) );
+ }
+
+ if ( bChanges )
+ {
+ TextModified();
+ SetModifyFlag( true );
+ if ( bLenChanged )
+ UpdateSelections();
+ if (IsUpdateLayout())
+ FormatAndLayout();
+ }
+
+ return aNewSel;
+}
+
+
+short ImpEditEngine::ReplaceTextOnly(
+ ContentNode* pNode,
+ sal_Int32 nCurrentStart,
+ std::u16string_view rNewText,
+ const uno::Sequence< sal_Int32 >& rOffsets )
+{
+ // Change text without losing the attributes
+ sal_Int32 nCharsAfterTransliteration = rOffsets.getLength();
+ const sal_Int32* pOffsets = rOffsets.getConstArray();
+ short nDiffs = 0;
+ for ( sal_Int32 n = 0; n < nCharsAfterTransliteration; n++ )
+ {
+ sal_Int32 nCurrentPos = nCurrentStart+n;
+ sal_Int32 nDiff = (nCurrentPos-nDiffs) - pOffsets[n];
+
+ if ( !nDiff )
+ {
+ DBG_ASSERT( nCurrentPos < pNode->Len(), "TransliterateText - String smaller than expected!" );
+ pNode->SetChar( nCurrentPos, rNewText[n] );
+ }
+ else if ( nDiff < 0 )
+ {
+ // Replace first char, delete the rest...
+ DBG_ASSERT( nCurrentPos < pNode->Len(), "TransliterateText - String smaller than expected!" );
+ pNode->SetChar( nCurrentPos, rNewText[n] );
+
+ DBG_ASSERT( (nCurrentPos+1) < pNode->Len(), "TransliterateText - String smaller than expected!" );
+ GetEditDoc().RemoveChars( EditPaM( pNode, nCurrentPos+1 ), -nDiff);
+ }
+ else
+ {
+ DBG_ASSERT( nDiff == 1, "TransliterateText - Diff other than expected! But should work..." );
+ GetEditDoc().InsertText( EditPaM( pNode, nCurrentPos ), OUStringChar(rNewText[n]) );
+
+ }
+ nDiffs = sal::static_int_cast< short >(nDiffs + nDiff);
+ }
+
+ return nDiffs;
+}
+
+
+void ImpEditEngine::SetAsianCompressionMode( CharCompressType n )
+{
+ if (n != mnAsianCompressionMode)
+ {
+ mnAsianCompressionMode = n;
+ if ( ImplHasText() )
+ {
+ FormatFullDoc();
+ UpdateViews();
+ }
+ }
+}
+
+void ImpEditEngine::SetKernAsianPunctuation( bool b )
+{
+ if ( b != mbKernAsianPunctuation )
+ {
+ mbKernAsianPunctuation = b;
+ if ( ImplHasText() )
+ {
+ FormatFullDoc();
+ UpdateViews();
+ }
+ }
+}
+
+void ImpEditEngine::SetAddExtLeading( bool bExtLeading )
+{
+ if ( IsAddExtLeading() != bExtLeading )
+ {
+ mbAddExtLeading = bExtLeading;
+ if ( ImplHasText() )
+ {
+ FormatFullDoc();
+ UpdateViews();
+ }
+ }
+};
+
+
+bool ImpEditEngine::ImplHasText() const
+{
+ return ( ( GetEditDoc().Count() > 1 ) || GetEditDoc().GetObject(0)->Len() );
+}
+
+sal_Int32 ImpEditEngine::LogicToTwips(sal_Int32 n)
+{
+ Size aSz(n, 0);
+ MapMode aTwipsMode( MapUnit::MapTwip );
+ aSz = pRefDev->LogicToLogic( aSz, nullptr, &aTwipsMode );
+ return aSz.Width();
+}
+
+double ImpEditEngine::roundToNearestPt(double fInput) const
+{
+ if (mbRoundToNearestPt)
+ {
+ double fInputPt = o3tl::convert(fInput, o3tl::Length::mm100, o3tl::Length::pt);
+ auto nInputRounded = basegfx::fround(fInputPt);
+ return o3tl::convert(double(nInputRounded), o3tl::Length::pt, o3tl::Length::mm100);
+ }
+ else
+ {
+ return fInput;
+ }
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/impedit5.cxx b/editeng/source/editeng/impedit5.cxx
new file mode 100644
index 0000000000..0f5af2f75d
--- /dev/null
+++ b/editeng/source/editeng/impedit5.cxx
@@ -0,0 +1,845 @@
+/* -*- 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 <memory>
+#include "impedit.hxx"
+#include <editeng/editeng.hxx>
+#include <svl/hint.hxx>
+#include <sfx2/app.hxx>
+#include <utility>
+
+void ImpEditEngine::SetStyleSheetPool( SfxStyleSheetPool* pSPool )
+{
+ if ( pStylePool != pSPool )
+ {
+ pStylePool = pSPool;
+ }
+}
+
+const SfxStyleSheet* ImpEditEngine::GetStyleSheet( sal_Int32 nPara ) const
+{
+ const ContentNode* pNode = maEditDoc.GetObject( nPara );
+ return pNode ? pNode->GetContentAttribs().GetStyleSheet() : nullptr;
+}
+
+SfxStyleSheet* ImpEditEngine::GetStyleSheet( sal_Int32 nPara )
+{
+ ContentNode* pNode = maEditDoc.GetObject( nPara );
+ return pNode ? pNode->GetContentAttribs().GetStyleSheet() : nullptr;
+}
+
+void ImpEditEngine::SetStyleSheet( EditSelection aSel, SfxStyleSheet* pStyle )
+{
+ aSel.Adjust( maEditDoc );
+
+ sal_Int32 nStartPara = maEditDoc.GetPos( aSel.Min().GetNode() );
+ sal_Int32 nEndPara = maEditDoc.GetPos( aSel.Max().GetNode() );
+
+ bool _bUpdate = SetUpdateLayout( false );
+
+ for ( sal_Int32 n = nStartPara; n <= nEndPara; n++ )
+ SetStyleSheet( n, pStyle );
+
+ SetUpdateLayout( _bUpdate );
+}
+
+void ImpEditEngine::SetStyleSheet( sal_Int32 nPara, SfxStyleSheet* pStyle )
+{
+ DBG_ASSERT( GetStyleSheetPool() || !pStyle, "SetStyleSheet: No StyleSheetPool registered!" );
+ ContentNode* pNode = maEditDoc.GetObject( nPara );
+ SfxStyleSheet* pCurStyle = pNode->GetStyleSheet();
+ if ( pStyle != pCurStyle )
+ {
+ if ( IsUndoEnabled() && !IsInUndo() && maStatus.DoUndoAttribs() )
+ {
+ OUString aPrevStyleName;
+ if ( pCurStyle )
+ aPrevStyleName = pCurStyle->GetName();
+
+ OUString aNewStyleName;
+ if ( pStyle )
+ aNewStyleName = pStyle->GetName();
+
+ InsertUndo(
+ std::make_unique<EditUndoSetStyleSheet>(pEditEngine, maEditDoc.GetPos( pNode ),
+ aPrevStyleName, pCurStyle ? pCurStyle->GetFamily() : SfxStyleFamily::Para,
+ aNewStyleName, pStyle ? pStyle->GetFamily() : SfxStyleFamily::Para,
+ pNode->GetContentAttribs().GetItems() ) );
+ }
+ if ( pCurStyle )
+ EndListening( *pCurStyle );
+ pNode->SetStyleSheet( pStyle, maStatus.UseCharAttribs() );
+ if ( pStyle )
+ StartListening(*pStyle, DuplicateHandling::Allow);
+
+ if (pNode->GetWrongList())
+ pNode->GetWrongList()->ResetInvalidRange(0, pNode->Len());
+ ParaAttribsChanged( pNode );
+ }
+ if (IsUpdateLayout())
+ FormatAndLayout();
+}
+
+void ImpEditEngine::UpdateParagraphsWithStyleSheet( SfxStyleSheet* pStyle )
+{
+ SvxFont aFontFromStyle;
+ CreateFont( aFontFromStyle, pStyle->GetItemSet() );
+
+ bool bUsed = false;
+ for ( sal_Int32 nNode = 0; nNode < maEditDoc.Count(); nNode++ )
+ {
+ ContentNode* pNode = maEditDoc.GetObject( nNode );
+ if ( pNode->GetStyleSheet() == pStyle )
+ {
+ bUsed = true;
+ if (maStatus.UseCharAttribs())
+ pNode->SetStyleSheet( pStyle, aFontFromStyle );
+ else
+ pNode->SetStyleSheet( pStyle, false );
+
+ if (pNode->GetWrongList())
+ pNode->GetWrongList()->ResetInvalidRange(0, pNode->Len());
+ ParaAttribsChanged( pNode );
+ }
+ }
+ if ( bUsed )
+ {
+ GetEditEnginePtr()->StyleSheetChanged( pStyle );
+ if (IsUpdateLayout())
+ FormatAndLayout();
+ }
+}
+
+void ImpEditEngine::RemoveStyleFromParagraphs( SfxStyleSheet const * pStyle )
+{
+ for ( sal_Int32 nNode = 0; nNode < maEditDoc.Count(); nNode++ )
+ {
+ ContentNode* pNode = maEditDoc.GetObject(nNode);
+ if ( pNode->GetStyleSheet() == pStyle )
+ {
+ pNode->SetStyleSheet( nullptr );
+ ParaAttribsChanged( pNode );
+ }
+ }
+ if (IsUpdateLayout())
+ FormatAndLayout();
+}
+
+void ImpEditEngine::Notify( SfxBroadcaster& rBC, const SfxHint& rHint )
+{
+ // So that not a lot of unnecessary formatting is done when destructing:
+ if (!mbDowning)
+ {
+ SfxHintId nId = rHint.GetId();
+ if ( ( nId == SfxHintId::StyleSheetInDestruction ) ||
+ ( nId == SfxHintId::StyleSheetErased ) )
+ {
+ const SfxStyleSheetHint* pStyleSheetHint = static_cast<const SfxStyleSheetHint*>(&rHint);
+ SfxStyleSheet* pStyle = static_cast<SfxStyleSheet*>( pStyleSheetHint->GetStyleSheet() );
+ RemoveStyleFromParagraphs( pStyle );
+ }
+ else if ( nId == SfxHintId::StyleSheetModified )
+ {
+ const SfxStyleSheetHint* pStyleSheetHint = static_cast<const SfxStyleSheetHint*>(&rHint);
+ SfxStyleSheet* pStyle = static_cast<SfxStyleSheet*>( pStyleSheetHint->GetStyleSheet() );
+ UpdateParagraphsWithStyleSheet( pStyle );
+ }
+ else if ( nId == SfxHintId::Dying )
+ {
+ if ( auto pStyle = dynamic_cast< SfxStyleSheet* >(&rBC) )
+ RemoveStyleFromParagraphs( pStyle );
+ }
+ else if ( nId == SfxHintId::DataChanged )
+ {
+ if ( auto pStyle = dynamic_cast< SfxStyleSheet* >(&rBC) )
+ UpdateParagraphsWithStyleSheet( pStyle );
+ }
+ }
+ if (rHint.GetId() == SfxHintId::Dying && dynamic_cast<const SfxApplication*>(&rBC))
+ Dispose();
+}
+
+std::unique_ptr<EditUndoSetAttribs> ImpEditEngine::CreateAttribUndo( EditSelection aSel, const SfxItemSet& rSet )
+{
+ DBG_ASSERT( !aSel.DbgIsBuggy( maEditDoc ), "CreateAttribUndo: Incorrect selection ");
+ aSel.Adjust( maEditDoc );
+
+ ESelection aESel( CreateESel( aSel ) );
+
+ sal_Int32 nStartNode = maEditDoc.GetPos( aSel.Min().GetNode() );
+ sal_Int32 nEndNode = maEditDoc.GetPos( aSel.Max().GetNode() );
+
+ DBG_ASSERT( nStartNode <= nEndNode, "CreateAttribUndo: Start > End ?!" );
+
+ std::unique_ptr<EditUndoSetAttribs> pUndo;
+ if ( rSet.GetPool() != &maEditDoc.GetItemPool() )
+ {
+ SfxItemSet aTmpSet( GetEmptyItemSet() );
+ aTmpSet.Put( rSet );
+ pUndo.reset( new EditUndoSetAttribs(pEditEngine, aESel, std::move(aTmpSet)) );
+ }
+ else
+ {
+ pUndo.reset( new EditUndoSetAttribs(pEditEngine, aESel, rSet) );
+ }
+
+ SfxItemPool* pPool = pUndo->GetNewAttribs().GetPool();
+
+ for ( sal_Int32 nPara = nStartNode; nPara <= nEndNode; nPara++ )
+ {
+ ContentNode* pNode = maEditDoc.GetObject( nPara );
+ DBG_ASSERT( maEditDoc.GetObject( nPara ), "Node not found: CreateAttribUndo" );
+ ContentAttribsInfo* pInf = new ContentAttribsInfo( pNode->GetContentAttribs().GetItems() );
+ pUndo->AppendContentInfo(pInf);
+
+ for ( sal_Int32 nAttr = 0; nAttr < pNode->GetCharAttribs().Count(); nAttr++ )
+ {
+ const EditCharAttrib& rAttr = *pNode->GetCharAttribs().GetAttribs()[nAttr];
+ if (rAttr.GetLen())
+ {
+ EditCharAttrib* pNew = MakeCharAttrib(*pPool, *rAttr.GetItem(), rAttr.GetStart(), rAttr.GetEnd());
+ pInf->AppendCharAttrib(pNew);
+ }
+ }
+ }
+ return pUndo;
+}
+
+ViewShellId ImpEditEngine::CreateViewShellId()
+{
+ ViewShellId nRet(-1);
+
+ const EditView* pEditView = pEditEngine ? pEditEngine->GetActiveView() : nullptr;
+ const OutlinerViewShell* pViewShell = pEditView ? pEditView->GetImpEditView()->GetViewShell() : nullptr;
+ if (pViewShell)
+ nRet = pViewShell->GetViewShellId();
+
+ return nRet;
+}
+
+void ImpEditEngine::UndoActionStart( sal_uInt16 nId, const ESelection& aSel )
+{
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ GetUndoManager().EnterListAction( GetEditEnginePtr()->GetUndoComment( nId ), OUString(), nId, CreateViewShellId() );
+ DBG_ASSERT( !moUndoMarkSelection, "UndoAction SelectionMarker?" );
+ moUndoMarkSelection = aSel;
+ }
+}
+
+void ImpEditEngine::UndoActionStart( sal_uInt16 nId )
+{
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ GetUndoManager().EnterListAction( GetEditEnginePtr()->GetUndoComment( nId ), OUString(), nId, CreateViewShellId() );
+ DBG_ASSERT( !moUndoMarkSelection, "UndoAction SelectionMarker?" );
+ }
+}
+
+void ImpEditEngine::UndoActionEnd()
+{
+ if ( IsUndoEnabled() && !IsInUndo() )
+ {
+ GetUndoManager().LeaveListAction();
+ moUndoMarkSelection.reset();
+ }
+}
+
+void ImpEditEngine::InsertUndo( std::unique_ptr<EditUndo> pUndo, bool bTryMerge )
+{
+ DBG_ASSERT( !IsInUndo(), "InsertUndo in Undo mode!" );
+ if ( moUndoMarkSelection )
+ {
+ GetUndoManager().AddUndoAction( std::make_unique<EditUndoMarkSelection>(pEditEngine, *moUndoMarkSelection) );
+ moUndoMarkSelection.reset();
+ }
+ GetUndoManager().AddUndoAction( std::move(pUndo), bTryMerge );
+
+ mbLastTryMerge = bTryMerge;
+}
+
+void ImpEditEngine::ResetUndoManager()
+{
+ if ( HasUndoManager() )
+ GetUndoManager().Clear();
+}
+
+void ImpEditEngine::EnableUndo( bool bEnable )
+{
+ // When switching the mode Delete list:
+ if ( bEnable != IsUndoEnabled() )
+ ResetUndoManager();
+
+ mbUndoEnabled = bEnable;
+}
+
+void ImpEditEngine::Undo( EditView* pView )
+{
+ if ( HasUndoManager() && GetUndoManager().GetUndoActionCount() )
+ {
+ SetActiveView( pView );
+ GetUndoManager().Undo();
+ }
+}
+
+void ImpEditEngine::Redo( EditView* pView )
+{
+ if ( HasUndoManager() && GetUndoManager().GetRedoActionCount() )
+ {
+ SetActiveView( pView );
+ GetUndoManager().Redo();
+ }
+}
+
+SfxItemSet ImpEditEngine::GetAttribs( EditSelection aSel, EditEngineAttribs nOnlyHardAttrib )
+{
+
+ aSel.Adjust( maEditDoc );
+
+ SfxItemSet aCurSet( GetEmptyItemSet() );
+
+ sal_Int32 nStartNode = maEditDoc.GetPos( aSel.Min().GetNode() );
+ sal_Int32 nEndNode = maEditDoc.GetPos( aSel.Max().GetNode() );
+
+ // iterate over the paragraphs ...
+ for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ )
+ {
+ ContentNode* pNode = maEditDoc.GetObject( nNode );
+ assert( pNode && "Node not found: GetAttrib" );
+
+ const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0;
+ const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // Can also be == nStart!
+
+ // Problem: Templates...
+ // => Other way:
+ // 1) Hard character attributes, as usual...
+ // 2) Examine Style and paragraph attributes only when OFF...
+
+ // First the very hard formatting...
+ if (pNode)
+ EditDoc::FindAttribs( pNode, nStartPos, nEndPos, aCurSet );
+
+ if( nOnlyHardAttrib != EditEngineAttribs::OnlyHard )
+ {
+ // and then paragraph formatting and template...
+ for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++)
+ {
+ if ( aCurSet.GetItemState( nWhich ) == SfxItemState::DEFAULT )
+ {
+ if ( nOnlyHardAttrib == EditEngineAttribs::All )
+ {
+ const SfxPoolItem& rItem = pNode->GetContentAttribs().GetItem( nWhich );
+ aCurSet.Put( rItem );
+ }
+ else if ( pNode->GetContentAttribs().GetItems().GetItemState( nWhich ) == SfxItemState::SET )
+ {
+ const SfxPoolItem& rItem = pNode->GetContentAttribs().GetItems().Get( nWhich );
+ aCurSet.Put( rItem );
+ }
+ }
+ else if ( aCurSet.GetItemState( nWhich ) == SfxItemState::SET )
+ {
+ const SfxPoolItem* pItem = nullptr;
+ if ( nOnlyHardAttrib == EditEngineAttribs::All )
+ {
+ pItem = &pNode->GetContentAttribs().GetItem( nWhich );
+ }
+ else if ( pNode->GetContentAttribs().GetItems().GetItemState( nWhich ) == SfxItemState::SET )
+ {
+ pItem = &pNode->GetContentAttribs().GetItems().Get( nWhich );
+ }
+ // pItem can only be NULL when nOnlyHardAttrib...
+ if ( !pItem || ( *pItem != aCurSet.Get( nWhich ) ) )
+ {
+ // Problem: When Paragraph style with for example font,
+ // but the Font is hard and completely different,
+ // wrong in selection if invalidated....
+ // => better not invalidate, instead CHANGE!
+ // It would be better to fill each paragraph with
+ // an itemset and compare this in large.
+ if ( nWhich <= EE_PARA_END )
+ aCurSet.InvalidateItem( nWhich );
+ }
+ }
+ }
+ }
+ }
+
+ // fill empty slots with defaults ...
+ if ( nOnlyHardAttrib == EditEngineAttribs::All )
+ {
+ for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++ )
+ {
+ if ( aCurSet.GetItemState( nWhich ) == SfxItemState::DEFAULT )
+ {
+ aCurSet.Put( maEditDoc.GetItemPool().GetDefaultItem( nWhich ) );
+ }
+ }
+ }
+ return aCurSet;
+}
+
+
+SfxItemSet ImpEditEngine::GetAttribs( sal_Int32 nPara, sal_Int32 nStart, sal_Int32 nEnd, GetAttribsFlags nFlags ) const
+{
+ // Optimized function with fewer Puts(), which cause unnecessary cloning from default items.
+ // If this works, change GetAttribs( EditSelection ) to use this for each paragraph and merge the results!
+
+
+ ContentNode* pNode = const_cast<ContentNode*>(maEditDoc.GetObject(nPara));
+ DBG_ASSERT( pNode, "GetAttribs - unknown paragraph!" );
+ DBG_ASSERT( nStart <= nEnd, "getAttribs: Start > End not supported!" );
+
+ SfxItemSet aAttribs(GetEmptyItemSet());
+
+ if ( pNode )
+ {
+ if ( nEnd > pNode->Len() )
+ nEnd = pNode->Len();
+
+ if ( nStart > nEnd )
+ nStart = nEnd;
+
+ // StyleSheet / Parattribs...
+
+ if ( pNode->GetStyleSheet() && ( nFlags & GetAttribsFlags::STYLESHEET ) )
+ aAttribs.Set(pNode->GetStyleSheet()->GetItemSet());
+
+ if ( nFlags & GetAttribsFlags::PARAATTRIBS )
+ aAttribs.Put( pNode->GetContentAttribs().GetItems() );
+
+ // CharAttribs...
+
+ if ( nFlags & GetAttribsFlags::CHARATTRIBS )
+ {
+ // Make testing easier...
+ pNode->GetCharAttribs().OptimizeRanges();
+
+ const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs();
+ for (const auto & nAttr : rAttrs)
+ {
+ const EditCharAttrib& rAttr = *nAttr;
+
+ if ( nStart == nEnd )
+ {
+ sal_Int32 nCursorPos = nStart;
+ if ( ( rAttr.GetStart() <= nCursorPos ) && ( rAttr.GetEnd() >= nCursorPos ) )
+ {
+ // To be used the attribute has to start BEFORE the position, or it must be a
+ // new empty attr AT the position, or we are on position 0.
+ if ( ( rAttr.GetStart() < nCursorPos ) || rAttr.IsEmpty() || !nCursorPos )
+ {
+ // maybe this attrib ends here and a new attrib with 0 Len may follow and be valid here,
+ // but that s no problem, the empty item will come later and win.
+ aAttribs.Put( *rAttr.GetItem() );
+ }
+ }
+ }
+ else
+ {
+ // Check every attribute covering the area, partial or full.
+ if ( ( rAttr.GetStart() < nEnd ) && ( rAttr.GetEnd() > nStart ) )
+ {
+ if ( ( rAttr.GetStart() <= nStart ) && ( rAttr.GetEnd() >= nEnd ) )
+ {
+ // full coverage
+ aAttribs.Put( *rAttr.GetItem() );
+ }
+ else
+ {
+ // OptimizeRanges() assures that not the same attr can follow for full coverage
+ // only partial, check with current, when using para/style, otherwise invalid.
+ if ( !( nFlags & (GetAttribsFlags::PARAATTRIBS|GetAttribsFlags::STYLESHEET) ) ||
+ ( *rAttr.GetItem() != aAttribs.Get( rAttr.Which() ) ) )
+ {
+ aAttribs.InvalidateItem( rAttr.Which() );
+ }
+ }
+ }
+ }
+
+ if ( rAttr.GetStart() > nEnd )
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ return aAttribs;
+}
+
+
+void ImpEditEngine::SetAttribs( EditSelection aSel, const SfxItemSet& rSet, SetAttribsMode nSpecial, bool bSetSelection )
+{
+ aSel.Adjust( maEditDoc );
+
+ // When no selection => use the Attribute on the word.
+ // ( the RTF-parser should actually never call the Method without a Range )
+ if ( nSpecial == SetAttribsMode::WholeWord && !aSel.HasRange() )
+ aSel = SelectWord( aSel, css::i18n::WordType::ANYWORD_IGNOREWHITESPACES, false );
+
+ sal_Int32 nStartNode = maEditDoc.GetPos( aSel.Min().GetNode() );
+ sal_Int32 nEndNode = maEditDoc.GetPos( aSel.Max().GetNode() );
+
+ if (IsUndoEnabled() && !IsInUndo() && maStatus.DoUndoAttribs())
+ {
+ std::unique_ptr<EditUndoSetAttribs> pUndo = CreateAttribUndo( aSel, rSet );
+ pUndo->SetSpecial( nSpecial );
+ pUndo->SetUpdateSelection(bSetSelection);
+ InsertUndo( std::move(pUndo) );
+ }
+
+ bool bCheckLanguage = false;
+ if ( GetStatus().DoOnlineSpelling() )
+ {
+ bCheckLanguage = ( rSet.GetItemState( EE_CHAR_LANGUAGE ) == SfxItemState::SET ) ||
+ ( rSet.GetItemState( EE_CHAR_LANGUAGE_CJK ) == SfxItemState::SET ) ||
+ ( rSet.GetItemState( EE_CHAR_LANGUAGE_CTL ) == SfxItemState::SET );
+ }
+
+ // iterate over the paragraphs ...
+ for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ )
+ {
+ bool bParaAttribFound = false;
+ bool bCharAttribFound = false;
+
+ DBG_ASSERT( maEditDoc.GetObject( nNode ), "Node not found: SetAttribs" );
+ DBG_ASSERT( GetParaPortions().SafeGetObject( nNode ), "Portion not found: SetAttribs" );
+
+ ContentNode* pNode = maEditDoc.GetObject( nNode );
+ ParaPortion* pPortion = GetParaPortions()[nNode];
+
+ const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0;
+ const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // can also be == nStart!
+
+ // Iterate over the Items...
+ for ( sal_uInt16 nWhich = EE_ITEMS_START; nWhich <= EE_CHAR_END; nWhich++)
+ {
+ if ( rSet.GetItemState( nWhich ) == SfxItemState::SET )
+ {
+ const SfxPoolItem& rItem = rSet.Get( nWhich );
+ if ( nWhich <= EE_PARA_END )
+ {
+ pNode->GetContentAttribs().GetItems().Put( rItem );
+ bParaAttribFound = true;
+ }
+ else
+ {
+ maEditDoc.InsertAttrib( pNode, nStartPos, nEndPos, rItem );
+ bCharAttribFound = true;
+ if ( nSpecial == SetAttribsMode::Edge )
+ {
+ CharAttribList::AttribsType& rAttribs = pNode->GetCharAttribs().GetAttribs();
+ for (std::unique_ptr<EditCharAttrib> & rAttrib : rAttribs)
+ {
+ EditCharAttrib& rAttr = *rAttrib;
+ if (rAttr.GetStart() > nEndPos)
+ break;
+
+ if (rAttr.GetEnd() == nEndPos && rAttr.Which() == nWhich)
+ {
+ rAttr.SetEdge(true);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if ( bParaAttribFound )
+ {
+ ParaAttribsChanged( pPortion->GetNode() );
+ }
+ else if ( bCharAttribFound )
+ {
+ mbFormatted = false;
+ if ( !pNode->Len() || ( nStartPos != nEndPos ) )
+ {
+ pPortion->MarkSelectionInvalid( nStartPos );
+ if ( bCheckLanguage )
+ pNode->GetWrongList()->SetInvalidRange(nStartPos, nEndPos);
+ }
+ }
+ }
+}
+
+void ImpEditEngine::RemoveCharAttribs( EditSelection aSel, EERemoveParaAttribsMode eMode, sal_uInt16 nWhich )
+{
+ aSel.Adjust( maEditDoc );
+
+ sal_Int32 nStartNode = maEditDoc.GetPos( aSel.Min().GetNode() );
+ sal_Int32 nEndNode = maEditDoc.GetPos( aSel.Max().GetNode() );
+ bool bRemoveParaAttribs = eMode == EERemoveParaAttribsMode::RemoveAll;
+ const SfxItemSet* _pEmptyItemSet = bRemoveParaAttribs ? &GetEmptyItemSet() : nullptr;
+
+ if (IsUndoEnabled() && !IsInUndo() && maStatus.DoUndoAttribs())
+ {
+ // Possibly a special Undo, or itemset*
+ std::unique_ptr<EditUndoSetAttribs> pUndo = CreateAttribUndo( aSel, GetEmptyItemSet() );
+ pUndo->SetRemoveAttribs( true );
+ pUndo->SetRemoveParaAttribs( bRemoveParaAttribs );
+ pUndo->SetRemoveWhich( nWhich );
+ InsertUndo( std::move(pUndo) );
+ }
+
+ // iterate over the paragraphs ...
+ for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ )
+ {
+ ContentNode* pNode = maEditDoc.GetObject( nNode );
+ ParaPortion* pPortion = GetParaPortions()[nNode];
+
+ DBG_ASSERT( maEditDoc.GetObject( nNode ), "Node not found: SetAttribs" );
+ DBG_ASSERT( GetParaPortions().SafeGetObject( nNode ), "Portion not found: SetAttribs" );
+
+ const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0;
+ const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : pNode->Len(); // can also be == nStart!
+
+ // Optimize: If whole paragraph, then RemoveCharAttribs (nPara)?
+ bool bChanged = maEditDoc.RemoveAttribs( pNode, nStartPos, nEndPos, nWhich );
+ if ( bRemoveParaAttribs )
+ {
+ SetParaAttribs( nNode, *_pEmptyItemSet ); // Invalidated
+ }
+ else if (eMode == EERemoveParaAttribsMode::RemoveCharItems)
+ {
+ // For 'Format-Standard' also the character attributes should
+ // disappear, which were set as paragraph attributes by the
+ // DrawingEngine. These could not have been set by the user anyway.
+
+ // #106871# Not when nWhich
+ // Would have been better to offer a separate method for format/standard...
+ if ( !nWhich )
+ {
+ SfxItemSet aAttribs( GetParaAttribs( nNode ) );
+ for ( sal_uInt16 nW = EE_CHAR_START; nW <= EE_CHAR_END; nW++ )
+ aAttribs.ClearItem( nW );
+ SetParaAttribs( nNode, aAttribs );
+ }
+ }
+
+ if ( bChanged && !bRemoveParaAttribs )
+ {
+ mbFormatted = false;
+ pPortion->MarkSelectionInvalid( nStartPos );
+ }
+ }
+}
+
+void ImpEditEngine::RemoveCharAttribs( sal_Int32 nPara, sal_uInt16 nWhich, bool bRemoveFeatures )
+{
+ ContentNode* pNode = maEditDoc.GetObject( nPara );
+ ParaPortion* pPortion = GetParaPortions().SafeGetObject( nPara );
+
+ DBG_ASSERT( pNode, "Node not found: RemoveCharAttribs" );
+ DBG_ASSERT( pPortion, "Portion not found: RemoveCharAttribs" );
+
+ if ( !pNode || !pPortion )
+ return;
+
+ size_t nAttr = 0;
+ CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs();
+ EditCharAttrib* pAttr = GetAttrib(rAttrs, nAttr);
+ while ( pAttr )
+ {
+ if ( ( !pAttr->IsFeature() || bRemoveFeatures ) &&
+ ( !nWhich || ( pAttr->GetItem()->Which() == nWhich ) ) )
+ {
+ pNode->GetCharAttribs().Remove(nAttr);
+ nAttr--;
+ }
+ nAttr++;
+ pAttr = GetAttrib(rAttrs, nAttr);
+ }
+
+#if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
+ CharAttribList::DbgCheckAttribs(pNode->GetCharAttribs());
+#endif
+
+ pPortion->MarkSelectionInvalid( 0 );
+}
+
+void ImpEditEngine::SetParaAttribs( sal_Int32 nPara, const SfxItemSet& rSet )
+{
+ ContentNode* pNode = maEditDoc.GetObject( nPara );
+
+ if ( !pNode )
+ return;
+
+ if ( pNode->GetContentAttribs().GetItems() == rSet )
+ return;
+
+ if (IsUndoEnabled() && !IsInUndo() && maStatus.DoUndoAttribs())
+ {
+ if ( rSet.GetPool() != &maEditDoc.GetItemPool() )
+ {
+ SfxItemSet aTmpSet( GetEmptyItemSet() );
+ aTmpSet.Put( rSet );
+ InsertUndo(std::make_unique<EditUndoSetParaAttribs>(pEditEngine, nPara, pNode->GetContentAttribs().GetItems(), aTmpSet));
+ }
+ else
+ {
+ InsertUndo(std::make_unique<EditUndoSetParaAttribs>(pEditEngine, nPara, pNode->GetContentAttribs().GetItems(), rSet));
+ }
+ }
+
+ bool bCheckLanguage = ( rSet.GetItemState( EE_CHAR_LANGUAGE ) == SfxItemState::SET ) ||
+ ( rSet.GetItemState( EE_CHAR_LANGUAGE_CJK ) == SfxItemState::SET ) ||
+ ( rSet.GetItemState( EE_CHAR_LANGUAGE_CTL ) == SfxItemState::SET );
+
+ pNode->GetContentAttribs().GetItems().Set( rSet );
+
+ if ( bCheckLanguage && pNode->GetWrongList() )
+ pNode->GetWrongList()->ResetInvalidRange(0, pNode->Len());
+
+ if (maStatus.UseCharAttribs())
+ pNode->CreateDefFont();
+
+ ParaAttribsChanged( pNode );
+}
+
+const SfxItemSet& ImpEditEngine::GetParaAttribs( sal_Int32 nPara ) const
+{
+ const ContentNode* pNode = maEditDoc.GetObject( nPara );
+ assert(pNode && "Node not found: GetParaAttribs");
+ return pNode->GetContentAttribs().GetItems();
+}
+
+bool ImpEditEngine::HasParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const
+{
+ const ContentNode* pNode = maEditDoc.GetObject( nPara );
+ assert(pNode && "Node not found: HasParaAttrib");
+ return pNode->GetContentAttribs().HasItem( nWhich );
+}
+
+const SfxPoolItem& ImpEditEngine::GetParaAttrib( sal_Int32 nPara, sal_uInt16 nWhich ) const
+{
+ const ContentNode* pNode = maEditDoc.GetObject(nPara);
+ assert(pNode && "Node not found: GetParaAttrib");
+ return pNode->GetContentAttribs().GetItem(nWhich);
+}
+
+void ImpEditEngine::GetCharAttribs( sal_Int32 nPara, std::vector<EECharAttrib>& rLst ) const
+{
+ rLst.clear();
+ const ContentNode* pNode = maEditDoc.GetObject( nPara );
+ if ( !pNode )
+ return;
+
+ rLst.reserve(pNode->GetCharAttribs().Count());
+ const CharAttribList::AttribsType& rAttrs = pNode->GetCharAttribs().GetAttribs();
+ for (const auto & i : rAttrs)
+ {
+ const EditCharAttrib& rAttr = *i;
+ EECharAttrib aEEAttr(rAttr.GetStart(), rAttr.GetEnd(), rAttr.GetItem());
+ rLst.push_back(aEEAttr);
+ }
+}
+
+void ImpEditEngine::ParaAttribsToCharAttribs( ContentNode* pNode )
+{
+ pNode->GetCharAttribs().DeleteEmptyAttribs();
+ sal_Int32 nEndPos = pNode->Len();
+ for ( sal_uInt16 nWhich = EE_CHAR_START; nWhich <= EE_CHAR_END; nWhich++ )
+ {
+ if ( pNode->GetContentAttribs().HasItem( nWhich ) )
+ {
+ const SfxPoolItem& rItem = pNode->GetContentAttribs().GetItem( nWhich );
+ // Fill the gap:
+ sal_Int32 nLastEnd = 0;
+ const EditCharAttrib* pAttr = pNode->GetCharAttribs().FindNextAttrib( nWhich, nLastEnd );
+ while ( pAttr )
+ {
+ nLastEnd = pAttr->GetEnd();
+ if ( pAttr->GetStart() > nLastEnd )
+ maEditDoc.InsertAttrib( pNode, nLastEnd, pAttr->GetStart(), rItem );
+ // #112831# Last Attr might go from 0xffff to 0x0000
+ pAttr = nLastEnd ? pNode->GetCharAttribs().FindNextAttrib( nWhich, nLastEnd ) : nullptr;
+ }
+
+ // And the Rest:
+ if ( nLastEnd < nEndPos )
+ maEditDoc.InsertAttrib( pNode, nLastEnd, nEndPos, rItem );
+ }
+ }
+ mbFormatted = false;
+ // Portion does not need to be invalidated here, happens elsewhere.
+}
+
+IdleFormattter::IdleFormattter()
+ : Idle("editeng::ImpEditEngine aIdleFormatter")
+{
+ pView = nullptr;
+ nRestarts = 0;
+}
+
+IdleFormattter::~IdleFormattter()
+{
+ pView = nullptr;
+}
+
+void IdleFormattter::DoIdleFormat( EditView* pV )
+{
+ pView = pV;
+
+ if ( IsActive() )
+ nRestarts++;
+
+ if ( nRestarts > 4 )
+ ForceTimeout();
+ else
+ Start();
+}
+
+void IdleFormattter::ForceTimeout()
+{
+ if ( IsActive() )
+ {
+ Stop();
+ Invoke();
+ }
+}
+
+ImplIMEInfos::ImplIMEInfos( const EditPaM& rPos, OUString _aOldTextAfterStartPos )
+ : aOldTextAfterStartPos(std::move( _aOldTextAfterStartPos )),
+ aPos(rPos),
+ nLen(0),
+ bWasCursorOverwrite(false)
+ {
+ }
+
+ImplIMEInfos::~ImplIMEInfos()
+{
+}
+
+void ImplIMEInfos::CopyAttribs( const ExtTextInputAttr* pA, sal_uInt16 nL )
+{
+ nLen = nL;
+ pAttribs.reset( new ExtTextInputAttr[ nL ] );
+ memcpy( pAttribs.get(), pA, nL*sizeof(ExtTextInputAttr) );
+}
+
+void ImplIMEInfos::DestroyAttribs()
+{
+ pAttribs.reset();
+ nLen = 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/misspellrange.cxx b/editeng/source/editeng/misspellrange.cxx
new file mode 100644
index 0000000000..562a9905c2
--- /dev/null
+++ b/editeng/source/editeng/misspellrange.cxx
@@ -0,0 +1,21 @@
+/* -*- 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/.
+ */
+
+#include <editeng/misspellrange.hxx>
+
+namespace editeng {
+
+MisspellRange::MisspellRange(size_t nStart, size_t nEnd) : mnStart(nStart), mnEnd(nEnd) {}
+
+MisspellRanges::MisspellRanges(sal_Int32 nParagraph, std::vector<MisspellRange>&& rRanges) :
+ mnParagraph(nParagraph), maRanges(std::move(rRanges)) {}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/section.cxx b/editeng/source/editeng/section.cxx
new file mode 100644
index 0000000000..f65b0158a9
--- /dev/null
+++ b/editeng/source/editeng/section.cxx
@@ -0,0 +1,19 @@
+/* -*- 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/.
+ */
+
+#include <editeng/section.hxx>
+
+namespace editeng {
+
+Section::Section(sal_Int32 nPara, sal_Int32 nStart, sal_Int32 nEnd) :
+ mnParagraph(nPara), mnStart(nStart), mnEnd(nEnd){}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/textconv.cxx b/editeng/source/editeng/textconv.cxx
new file mode 100644
index 0000000000..e97be254d6
--- /dev/null
+++ b/editeng/source/editeng/textconv.cxx
@@ -0,0 +1,543 @@
+/* -*- 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 "impedit.hxx"
+#include <editeng/editview.hxx>
+#include <editeng/editeng.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/fontitem.hxx>
+#include "textconv.hxx"
+#include <osl/diagnose.h>
+#include <vcl/weld.hxx>
+
+using namespace com::sun::star;
+using namespace com::sun::star::uno;
+using namespace com::sun::star::beans;
+using namespace com::sun::star::linguistic2;
+
+TextConvWrapper::TextConvWrapper( weld::Widget* pWindow,
+ const Reference< XComponentContext >& rxContext,
+ const lang::Locale& rSourceLocale,
+ const lang::Locale& rTargetLocale,
+ const vcl::Font* pTargetFont,
+ sal_Int32 nOptions,
+ bool bIsInteractive,
+ bool bIsStart,
+ EditView* pView ) :
+ HangulHanjaConversion( pWindow, rxContext, rSourceLocale, rTargetLocale, pTargetFont, nOptions, bIsInteractive )
+ , m_nConvTextLang(LANGUAGE_NONE)
+ , m_nUnitOffset(0)
+ , m_nLastPos(0)
+ , m_aConvSel(pView->GetSelection())
+ , m_pEditView(pView)
+ , m_pWin(pWindow)
+ , m_bStartChk(false)
+ , m_bStartDone(bIsStart)
+ , m_bEndDone(false)
+ , m_bAllowChange(false)
+{
+ DBG_ASSERT( pWindow, "TextConvWrapper: window missing" );
+
+ m_aConvSel.Adjust(); // make Start <= End
+}
+
+
+TextConvWrapper::~TextConvWrapper()
+{
+}
+
+
+bool TextConvWrapper::ConvNext_impl()
+{
+ // modified version of SvxSpellWrapper::SpellNext
+
+ if( m_bStartChk )
+ m_bStartDone = true;
+ else
+ m_bEndDone = true;
+
+ if ( m_bStartDone && m_bEndDone )
+ {
+ if ( ConvMore_impl() ) // examine another document?
+ {
+ m_bStartDone = true;
+ m_bEndDone = false;
+ ConvStart_impl( SvxSpellArea::Body );
+ return true;
+ }
+ return false;
+
+ }
+
+ if ( m_bStartDone && m_bEndDone )
+ {
+ if ( ConvMore_impl() ) // examine another document?
+ {
+ m_bStartDone = true;
+ m_bEndDone = false;
+ ConvStart_impl( SvxSpellArea::Body );
+ return true;
+ }
+ }
+ else if (!m_aConvSel.HasRange())
+ {
+ m_bStartChk = !m_bStartDone;
+ ConvStart_impl( m_bStartChk ? SvxSpellArea::BodyStart : SvxSpellArea::BodyEnd );
+ return true;
+ }
+
+ return false;
+}
+
+void TextConvWrapper::FindConvText_impl()
+{
+ // modified version of SvxSpellWrapper::FindSpellError
+ weld::WaitObject aWait(m_pWin);
+ while ( true )
+ {
+ if (ConvContinue_impl() || !ConvNext_impl())
+ break;
+ }
+}
+
+bool TextConvWrapper::ConvMore_impl()
+{
+ // modified version of SvxSpellWrapper::SpellMore
+
+ bool bMore = false;
+ EditEngine* pEE = m_pEditView->GetEditEngine();
+ ImpEditEngine* pImpEE = m_pEditView->GetImpEditEngine();
+ ConvInfo* pConvInfo = pImpEE->GetConvInfo();
+ if ( pConvInfo->bMultipleDoc )
+ {
+ bMore = pEE->ConvertNextDocument();
+ if ( bMore )
+ {
+ // The text has been entered in this engine ...
+ m_pEditView->GetImpEditView()->SetEditSelection(
+ pEE->GetEditDoc().GetStartPaM() );
+ }
+ }
+ return bMore;
+}
+
+
+void TextConvWrapper::ConvStart_impl( SvxSpellArea eArea )
+{
+ // modified version of EditSpellWrapper::SpellStart
+
+ EditEngine* pEE = m_pEditView->GetEditEngine();
+ ImpEditEngine* pImpEE = m_pEditView->GetImpEditEngine();
+ ConvInfo* pConvInfo = pImpEE->GetConvInfo();
+
+ if ( eArea == SvxSpellArea::BodyStart )
+ {
+ // Is called when Spell-forward has reached the end, and to start over
+ if ( m_bEndDone )
+ {
+ pConvInfo->bConvToEnd = false;
+ pConvInfo->aConvTo = pConvInfo->aConvStart;
+ pConvInfo->aConvContinue = EPaM( 0, 0 );
+ m_pEditView->GetImpEditView()->SetEditSelection(
+ pEE->GetEditDoc().GetStartPaM() );
+ }
+ else
+ {
+ pConvInfo->bConvToEnd = true;
+ pConvInfo->aConvTo = pImpEE->CreateEPaM(
+ pEE->GetEditDoc().GetStartPaM() );
+ }
+ }
+ else if ( eArea == SvxSpellArea::BodyEnd )
+ {
+ // Is called when Spell-forward starts
+ pConvInfo->bConvToEnd = true;
+ if (m_aConvSel.HasRange())
+ {
+ // user selection: convert to end of selection
+ pConvInfo->aConvTo.nPara = m_aConvSel.nEndPara;
+ pConvInfo->aConvTo.nIndex = m_aConvSel.nEndPos;
+ pConvInfo->bConvToEnd = false;
+ }
+ else
+ {
+ // nothing selected: convert to end of document
+ pConvInfo->aConvTo = pImpEE->CreateEPaM(
+ pEE->GetEditDoc().GetEndPaM() );
+ }
+ }
+ else if ( eArea == SvxSpellArea::Body )
+ {
+ // called by ConvNext_impl...
+ pConvInfo->aConvContinue = pConvInfo->aConvStart;
+ pConvInfo->aConvTo = pImpEE->CreateEPaM(
+ pEE->GetEditDoc().GetEndPaM() );
+ }
+ else
+ {
+ OSL_FAIL( "ConvStart_impl: Unknown Area!" );
+ }
+}
+
+
+bool TextConvWrapper::ConvContinue_impl()
+{
+ // modified version of EditSpellWrapper::SpellContinue
+
+ // get next convertible text portion and its language
+ m_aConvText.clear();
+ m_nConvTextLang = LANGUAGE_NONE;
+ m_pEditView->GetImpEditEngine()->ImpConvert( m_aConvText, m_nConvTextLang,
+ m_pEditView, GetSourceLanguage(), m_aConvSel,
+ m_bAllowChange, GetTargetLanguage(), GetTargetFont() );
+ return !m_aConvText.isEmpty();
+}
+
+
+void TextConvWrapper::SetLanguageAndFont( const ESelection &rESel,
+ LanguageType nLang, sal_uInt16 nLangWhichId,
+ const vcl::Font *pFont, sal_uInt16 nFontWhichId )
+{
+ ESelection aOldSel = m_pEditView->GetSelection();
+ m_pEditView->SetSelection( rESel );
+
+ // set new language attribute
+ SfxItemSet aNewSet( m_pEditView->GetEmptyItemSet() );
+ aNewSet.Put( SvxLanguageItem( nLang, nLangWhichId ) );
+
+ // new font to be set?
+ DBG_ASSERT( pFont, "target font missing?" );
+ if (pFont)
+ {
+ // set new font attribute
+ SvxFontItem aFontItem = static_cast<const SvxFontItem&>( aNewSet.Get( nFontWhichId ) );
+ aFontItem.SetFamilyName( pFont->GetFamilyName());
+ aFontItem.SetFamily( pFont->GetFamilyType());
+ aFontItem.SetStyleName( pFont->GetStyleName());
+ aFontItem.SetPitch( pFont->GetPitch());
+ aFontItem.SetCharSet(pFont->GetCharSet());
+ aNewSet.Put( aFontItem );
+ }
+
+ // apply new attributes
+ m_pEditView->SetAttribs( aNewSet );
+
+ m_pEditView->SetSelection( aOldSel );
+}
+
+
+void TextConvWrapper::SelectNewUnit_impl(
+ const sal_Int32 nUnitStart,
+ const sal_Int32 nUnitEnd )
+{
+ const bool bOK = 0 <= nUnitStart && 0 <= nUnitEnd && nUnitStart <= nUnitEnd;
+ DBG_ASSERT( bOK, "invalid arguments" );
+ if (!bOK)
+ return;
+
+ ESelection aSelection = m_pEditView->GetSelection();
+ DBG_ASSERT( aSelection.nStartPara == aSelection.nEndPara,
+ "paragraph mismatch in selection" );
+ aSelection.nStartPos = (m_nLastPos + m_nUnitOffset + nUnitStart);
+ aSelection.nEndPos = (m_nLastPos + m_nUnitOffset + nUnitEnd);
+ m_pEditView->SetSelection( aSelection );
+}
+
+
+void TextConvWrapper::GetNextPortion(
+ OUString& /* [out] */ rNextPortion,
+ LanguageType& /* [out] */ rLangOfPortion,
+ bool /* [in] */ _bAllowImplicitChangesForNotConvertibleText )
+{
+ m_bAllowChange = _bAllowImplicitChangesForNotConvertibleText;
+
+ FindConvText_impl();
+ rNextPortion = m_aConvText;
+ rLangOfPortion = m_nConvTextLang;
+ m_nUnitOffset = 0;
+
+ ESelection aSelection = m_pEditView->GetSelection();
+ DBG_ASSERT( aSelection.nStartPara == aSelection.nEndPara,
+ "paragraph mismatch in selection" );
+ DBG_ASSERT( aSelection.nStartPos <= aSelection.nEndPos,
+ "start pos > end pos" );
+ m_nLastPos = aSelection.nStartPos;
+}
+
+
+void TextConvWrapper::HandleNewUnit(
+ const sal_Int32 nUnitStart,
+ const sal_Int32 nUnitEnd )
+{
+ SelectNewUnit_impl( nUnitStart, nUnitEnd );
+}
+
+#ifdef DBG_UTIL
+namespace
+{
+ bool IsSimilarChinese( LanguageType nLang1, LanguageType nLang2 )
+ {
+ using namespace editeng;
+ return (HangulHanjaConversion::IsTraditional(nLang1) && HangulHanjaConversion::IsTraditional(nLang2)) ||
+ (HangulHanjaConversion::IsSimplified(nLang1) && HangulHanjaConversion::IsSimplified(nLang2));
+ }
+}
+#endif
+
+void TextConvWrapper::ReplaceUnit(
+ const sal_Int32 nUnitStart, const sal_Int32 nUnitEnd,
+ const OUString& rOrigText,
+ const OUString& rReplaceWith,
+ const css::uno::Sequence< sal_Int32 > &rOffsets,
+ ReplacementAction eAction,
+ LanguageType *pNewUnitLanguage )
+{
+ const bool bOK = 0 <= nUnitStart && 0 <= nUnitEnd && nUnitStart <= nUnitEnd;
+ DBG_ASSERT( bOK, "invalid arguments" );
+ if (!bOK)
+ return;
+
+ // select current unit
+ SelectNewUnit_impl( nUnitStart, nUnitEnd );
+
+ OUString aOrigTxt( m_pEditView->GetSelected() );
+ OUString aNewTxt( rReplaceWith );
+ switch (eAction)
+ {
+ case eExchange :
+ break;
+ case eReplacementBracketed :
+ aNewTxt = aOrigTxt + "(" + rReplaceWith + ")";
+ break;
+ case eOriginalBracketed :
+ aNewTxt = rReplaceWith + "(" + aOrigTxt + ")";
+ break;
+ case eReplacementAbove :
+ case eOriginalAbove :
+ case eReplacementBelow :
+ case eOriginalBelow :
+ OSL_FAIL( "Rubies not supported" );
+ break;
+ default:
+ OSL_FAIL( "unexpected case" );
+ }
+ m_nUnitOffset = m_nUnitOffset + nUnitStart + aNewTxt.getLength();
+
+ // remember current original language for later use
+ ImpEditEngine *pImpEditEng = m_pEditView->GetImpEditEngine();
+ ESelection aOldSel = m_pEditView->GetSelection();
+ //EditSelection aOldEditSel = pEditView->GetImpEditView()->GetEditSelection();
+
+#ifdef DBG_UTIL
+ LanguageType nOldLang = pImpEditEng->GetLanguage( pImpEditEng->CreateSel( aOldSel ).Min() ).nLang;
+#endif
+
+ pImpEditEng->UndoActionStart( EDITUNDO_INSERT );
+
+ // according to FT we should currently not bother about keeping
+ // attributes in Hangul/Hanja conversion and leave that untouched.
+ // Thus we do this only for Chinese translation...
+ bool bIsChineseConversion = IsChinese( GetSourceLanguage() );
+ if (bIsChineseConversion)
+ ChangeText( aNewTxt, rOrigText, &rOffsets, &aOldSel );
+ else
+ ChangeText( aNewTxt, rOrigText, nullptr, nullptr );
+
+ // change language and font if necessary
+ if (bIsChineseConversion)
+ {
+ DBG_ASSERT( GetTargetLanguage() == LANGUAGE_CHINESE_SIMPLIFIED || GetTargetLanguage() == LANGUAGE_CHINESE_TRADITIONAL,
+ "TextConvWrapper::ReplaceUnit : unexpected target language" );
+
+ ESelection aNewSel( aOldSel );
+ aNewSel.nStartPos = aNewSel.nStartPos - aNewTxt.getLength();
+
+ if (pNewUnitLanguage)
+ {
+#ifdef DBG_UTIL
+ DBG_ASSERT(!IsSimilarChinese( *pNewUnitLanguage, nOldLang ),
+ "similar language should not be changed!");
+#endif
+ SetLanguageAndFont( aNewSel, *pNewUnitLanguage, EE_CHAR_LANGUAGE_CJK,
+ GetTargetFont(), EE_CHAR_FONTINFO_CJK );
+ }
+ }
+
+ pImpEditEng->UndoActionEnd();
+
+ // adjust ConvContinue / ConvTo if necessary
+ ImpEditEngine* pImpEE = m_pEditView->GetImpEditEngine();
+ ConvInfo* pConvInfo = pImpEE->GetConvInfo();
+ sal_Int32 nDelta = aNewTxt.getLength() - aOrigTxt.getLength();
+ if (nDelta != 0)
+ {
+ // Note: replacement is always done in the current paragraph
+ // which is the one ConvContinue points to
+ pConvInfo->aConvContinue.nIndex = pConvInfo->aConvContinue.nIndex + nDelta;
+
+ // if that is the same as the one where the conversions ends
+ // the end needs to be updated also
+ if (pConvInfo->aConvTo.nPara == pConvInfo->aConvContinue.nPara)
+ pConvInfo->aConvTo.nIndex = pConvInfo->aConvTo.nIndex + nDelta;
+ }
+}
+
+
+void TextConvWrapper::ChangeText( const OUString &rNewText,
+ std::u16string_view rOrigText,
+ const uno::Sequence< sal_Int32 > *pOffsets,
+ ESelection *pESelection )
+{
+ //!! code is a modified copy of SwHHCWrapper::ChangeText from sw !!
+
+ DBG_ASSERT( !rNewText.isEmpty(), "unexpected empty string" );
+ if (rNewText.isEmpty())
+ return;
+
+ if (pOffsets && pESelection) // try to keep as much attributation as possible ?
+ {
+ pESelection->Adjust();
+
+ // remember cursor start position for later setting of the cursor
+ const sal_Int32 nStartIndex = pESelection->nStartPos;
+
+ const sal_Int32 nIndices = pOffsets->getLength();
+ const sal_Int32 *pIndices = pOffsets->getConstArray();
+ const sal_Int32 nConvTextLen = rNewText.getLength();
+ sal_Int32 nPos = 0;
+ sal_Int32 nChgPos = -1;
+ sal_Int32 nConvChgPos = -1;
+
+ // offset to calculate the position in the text taking into
+ // account that text may have been replaced with new text of
+ // different length. Negative values allowed!
+ sal_Int32 nCorrectionOffset = 0;
+
+ DBG_ASSERT(nIndices == 0 || nIndices == nConvTextLen,
+ "mismatch between string length and sequence length!" );
+
+ // find all substrings that need to be replaced (and only those)
+ while (true)
+ {
+ // get index in original text that matches nPos in new text
+ sal_Int32 nIndex;
+ if (nPos < nConvTextLen)
+ nIndex = nPos < nIndices ? pIndices[nPos] : nPos;
+ else
+ {
+ nPos = nConvTextLen;
+ nIndex = rOrigText.size();
+ }
+
+ // end of string also terminates non-matching char sequence
+ if (nPos == nConvTextLen || rOrigText[nIndex] == rNewText[nPos])
+ {
+ // substring that needs to be replaced found?
+ if (nChgPos>=0 && nConvChgPos>=0)
+ {
+ const sal_Int32 nChgLen = nIndex - nChgPos;
+ const sal_Int32 nConvChgLen = nPos - nConvChgPos;
+ OUString aInNew( rNewText.copy( nConvChgPos, nConvChgLen ) );
+
+ // set selection to sub string to be replaced in original text
+ ESelection aSel( *pESelection );
+ sal_Int32 nChgInNodeStartIndex = nStartIndex + nCorrectionOffset + nChgPos;
+ aSel.nStartPos = nChgInNodeStartIndex;
+ aSel.nEndPos = nChgInNodeStartIndex + nChgLen;
+ m_pEditView->SetSelection( aSel );
+
+ // replace selected sub string with the corresponding
+ // sub string from the new text while keeping as
+ // much from the attributes as possible
+ ChangeText_impl( aInNew, true );
+
+ nCorrectionOffset += nConvChgLen - nChgLen;
+
+ nChgPos = -1;
+ nConvChgPos = -1;
+ }
+ }
+ else
+ {
+ // begin of non-matching char sequence found ?
+ if (nChgPos<0 && nConvChgPos<0)
+ {
+ nChgPos = nIndex;
+ nConvChgPos = nPos;
+ }
+ }
+ if (nPos >= nConvTextLen)
+ break;
+ ++nPos;
+ }
+
+ // set cursor to the end of the inserted text
+ // (as it would happen after ChangeText_impl (Delete and Insert)
+ // of the whole text in the 'else' branch below)
+ pESelection->nStartPos = pESelection->nEndPos = nStartIndex + nConvTextLen;
+ }
+ else
+ {
+ ChangeText_impl( rNewText, false );
+ }
+}
+
+
+void TextConvWrapper::ChangeText_impl( const OUString &rNewText, bool bKeepAttributes )
+{
+ if (bKeepAttributes)
+ {
+ // save attributes to be restored
+ SfxItemSet aSet( m_pEditView->GetAttribs() );
+
+ // replace old text and select new text
+ m_pEditView->InsertText( rNewText, true );
+
+ // since 'SetAttribs' below function like merging with the attributes
+ // from the itemset with any existing ones we have to get rid of all
+ // all attributes now. (Those attributes that may take effect left
+ // to the position where the new text gets inserted after the old text
+ // was deleted)
+ m_pEditView->RemoveAttribs(EERemoveParaAttribsMode::RemoveNone, 0);
+ // apply saved attributes to new inserted text
+ m_pEditView->SetAttribs( aSet );
+ }
+ else
+ {
+ m_pEditView->InsertText( rNewText );
+ }
+}
+
+
+void TextConvWrapper::Convert()
+{
+ m_bStartChk = false;
+ ConvStart_impl( SvxSpellArea::BodyEnd );
+ ConvertDocument();
+}
+
+
+bool TextConvWrapper::HasRubySupport() const
+{
+ return false;
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/editeng/source/editeng/textconv.hxx b/editeng/source/editeng/textconv.hxx
new file mode 100644
index 0000000000..96525a98f5
--- /dev/null
+++ b/editeng/source/editeng/textconv.hxx
@@ -0,0 +1,107 @@
+/* -*- 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 .
+ */
+#pragma once
+
+#include <editeng/editdata.hxx>
+#include <editeng/svxenum.hxx>
+#include <com/sun/star/uno/Reference.h>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <editeng/hangulhanja.hxx>
+
+class EditView;
+
+class TextConvWrapper final : public editeng::HangulHanjaConversion
+{
+ OUString m_aConvText; // convertible text part found last time
+ LanguageType m_nConvTextLang; // language of aConvText
+ sal_uInt16 m_nUnitOffset; // offset of current unit in the current text portion (word)
+ sal_uInt16 m_nLastPos; // starting position of the last found text portion (word)
+
+ ESelection m_aConvSel; // selection to be converted if
+ // 'HasRange' is true, other conversion
+ // starts from the cursor position
+
+ EditView * m_pEditView;
+ weld::Widget* m_pWin;
+
+ bool m_bStartChk;
+ bool m_bStartDone;
+ bool m_bEndDone;
+ bool m_bAllowChange; // storage for _bAllowImplicitChangesForNotConvertibleText
+ // parameters value of function GetNextPortion.
+ // used to transport the value to where it is needed.
+
+
+ // from SvxSpellWrapper copied and modified
+ bool ConvNext_impl(); // former SpellNext
+ void FindConvText_impl(); // former FindSpellError
+ bool ConvMore_impl(); // former SpellMore
+
+ // from EditSpellWrapper copied and modified
+ void ConvStart_impl( SvxSpellArea eSpell ); // former SpellStart
+ bool ConvContinue_impl(); // former SpellContinue
+
+ void SelectNewUnit_impl( const sal_Int32 nUnitStart,
+ const sal_Int32 nUnitEnd );
+
+ void ChangeText( const OUString &rNewText,
+ std::u16string_view rOrigText,
+ const css::uno::Sequence< sal_Int32 > *pOffsets,
+ ESelection *pESelection );
+ void ChangeText_impl( const OUString &rNewText, bool bKeepAttributes );
+
+ TextConvWrapper (const TextConvWrapper &) = delete;
+ TextConvWrapper & operator= (const TextConvWrapper &) = delete;
+
+ virtual void GetNextPortion( OUString& /* [out] */ rNextPortion,
+ LanguageType& /* [out] */ rLangOfPortion,
+ bool /* [in] */ _bAllowImplicitChangesForNotConvertibleText ) override;
+ virtual void HandleNewUnit( const sal_Int32 nUnitStart,
+ const sal_Int32 nUnitEnd ) override;
+ virtual void ReplaceUnit(
+ const sal_Int32 nUnitStart, const sal_Int32 nUnitEnd,
+ const OUString& rOrigText,
+ const OUString& rReplaceWith,
+ const css::uno::Sequence< sal_Int32 > &rOffsets,
+ ReplacementAction eAction,
+ LanguageType *pNewUnitLanguage ) override;
+
+ virtual bool HasRubySupport() const override;
+
+ void SetLanguageAndFont( const ESelection &rESel,
+ LanguageType nLang, sal_uInt16 nLangWhichId,
+ const vcl::Font *pFont, sal_uInt16 nFontWhichId );
+
+
+public:
+ TextConvWrapper(weld::Widget* pWindow,
+ const css::uno::Reference< css::uno::XComponentContext >& rxContext,
+ const css::lang::Locale& rSourceLocale,
+ const css::lang::Locale& rTargetLocale,
+ const vcl::Font* pTargetFont,
+ sal_Int32 nOptions,
+ bool bIsInteractive,
+ bool bIsStart, EditView* pView );
+
+ virtual ~TextConvWrapper() override;
+
+ void Convert();
+};
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */