xref: /core/editeng/source/editeng/impedit4.cxx (revision 492ea7e0)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 
21 #include <vcl/svapp.hxx>
22 
23 #include <svl/srchitem.hxx>
24 #include <editeng/lspcitem.hxx>
25 #include <editeng/adjustitem.hxx>
26 #include <editeng/tstpitem.hxx>
27 
28 #include "eertfpar.hxx"
29 #include <editeng/editeng.hxx>
30 #include "impedit.hxx"
31 #include <editeng/editview.hxx>
32 #include "eehtml.hxx"
33 #include "editobj2.hxx"
34 #include <i18nlangtag/lang.h>
35 #include <sal/log.hxx>
36 #include <osl/diagnose.h>
37 
38 #include <editxml.hxx>
39 
40 #include <editeng/autokernitem.hxx>
41 #include <editeng/contouritem.hxx>
42 #include <editeng/colritem.hxx>
43 #include <editeng/crossedoutitem.hxx>
44 #include <editeng/escapementitem.hxx>
45 #include <editeng/fhgtitem.hxx>
46 #include <editeng/fontitem.hxx>
47 #include <editeng/kernitem.hxx>
48 #include <editeng/lrspitem.hxx>
49 #include <editeng/postitem.hxx>
50 #include <editeng/shdditem.hxx>
51 #include <editeng/udlnitem.hxx>
52 #include <editeng/ulspitem.hxx>
53 #include <editeng/wghtitem.hxx>
54 #include <editeng/langitem.hxx>
55 #include <editeng/charreliefitem.hxx>
56 #include <editeng/frmdiritem.hxx>
57 #include <editeng/emphasismarkitem.hxx>
58 #include "textconv.hxx"
59 #include <rtl/tencinfo.h>
60 #include <svtools/rtfout.hxx>
61 #include <edtspell.hxx>
62 #include <editeng/scripttypeitem.hxx>
63 #include <editeng/unolingu.hxx>
64 #include <linguistic/lngprops.hxx>
65 #include <com/sun/star/linguistic2/XThesaurus.hpp>
66 #include <com/sun/star/i18n/ScriptType.hpp>
67 #include <com/sun/star/i18n/WordType.hpp>
68 #include <unotools/transliterationwrapper.hxx>
69 #include <unotools/textsearch.hxx>
70 #include <comphelper/processfactory.hxx>
71 #include <vcl/help.hxx>
72 #include <svtools/rtfkeywd.hxx>
73 #include <editeng/edtdlg.hxx>
74 
75 #include <memory>
76 #include <unordered_map>
77 #include <vector>
78 
79 using namespace ::com::sun::star;
80 using namespace ::com::sun::star::uno;
81 using namespace ::com::sun::star::beans;
82 using namespace ::com::sun::star::linguistic2;
83 
84 
85 EditPaM ImpEditEngine::Read(SvStream& rInput, const OUString& rBaseURL, EETextFormat eFormat, const EditSelection& rSel, SvKeyValueIterator* pHTTPHeaderAttrs)
86 {
87     bool _bUpdate = GetUpdateMode();
88     SetUpdateMode( false );
89     EditPaM aPaM;
90     if ( eFormat == EETextFormat::Text )
91         aPaM = ReadText( rInput, rSel );
92     else if ( eFormat == EETextFormat::Rtf )
93         aPaM = ReadRTF( rInput, rSel );
94     else if ( eFormat == EETextFormat::Xml )
95         aPaM = ReadXML( rInput, rSel );
96     else if ( eFormat == EETextFormat::Html )
97         aPaM = ReadHTML( rInput, rBaseURL, rSel, pHTTPHeaderAttrs );
98     else
99     {
100         OSL_FAIL( "Read: Unknown Format" );
101     }
102 
103     FormatFullDoc();        // perhaps a simple format is enough?
104     SetUpdateMode( _bUpdate );
105 
106     return aPaM;
107 }
108 
109 EditPaM ImpEditEngine::ReadText( SvStream& rInput, EditSelection aSel )
110 {
111     if ( aSel.HasRange() )
112         aSel = ImpDeleteSelection( aSel );
113     EditPaM aPaM = aSel.Max();
114 
115     OUString aTmpStr;
116     bool bDone = rInput.ReadByteStringLine( aTmpStr, rInput.GetStreamCharSet() );
117     while ( bDone )
118     {
119         if (aTmpStr.getLength() > MAXCHARSINPARA)
120         {
121             aTmpStr = aTmpStr.copy(0, MAXCHARSINPARA);
122         }
123         aPaM = ImpInsertText( EditSelection( aPaM, aPaM ), aTmpStr );
124         aPaM = ImpInsertParaBreak( aPaM );
125         bDone = rInput.ReadByteStringLine( aTmpStr, rInput.GetStreamCharSet() );
126     }
127     return aPaM;
128 }
129 
130 EditPaM ImpEditEngine::ReadXML( SvStream& rInput, EditSelection aSel )
131 {
132     if ( aSel.HasRange() )
133         aSel = ImpDeleteSelection( aSel );
134 
135     ESelection aESel = CreateESel( aSel );
136 
137     return ::SvxReadXML( *GetEditEnginePtr(), rInput, aESel );
138 }
139 
140 EditPaM ImpEditEngine::ReadRTF( SvStream& rInput, EditSelection aSel )
141 {
142     if ( aSel.HasRange() )
143         aSel = ImpDeleteSelection( aSel );
144 
145     // The SvRTF parser expects the Which-mapping passed on in the pool, not
146     // dependent on a secondary.
147     SfxItemPool* pPool = &aEditDoc.GetItemPool();
148     while (pPool->GetSecondaryPool() && pPool->GetName() != "EditEngineItemPool")
149    {
150         pPool = pPool->GetSecondaryPool();
151 
152     }
153 
154     DBG_ASSERT(pPool && pPool->GetName() == "EditEngineItemPool",
155         "ReadRTF: no EditEnginePool!");
156 
157     EditRTFParserRef xPrsr = new EditRTFParser(rInput, aSel, *pPool, pEditEngine);
158     SvParserState eState = xPrsr->CallParser();
159     if ( ( eState != SvParserState::Accepted ) && ( !rInput.GetError() ) )
160     {
161         rInput.SetError( EE_READWRITE_WRONGFORMAT );
162         return aSel.Min();
163     }
164     return xPrsr->GetCurPaM();
165 }
166 
167 EditPaM ImpEditEngine::ReadHTML( SvStream& rInput, const OUString& rBaseURL, EditSelection aSel, SvKeyValueIterator* pHTTPHeaderAttrs )
168 {
169     if ( aSel.HasRange() )
170         aSel = ImpDeleteSelection( aSel );
171 
172     EditHTMLParserRef xPrsr = new EditHTMLParser( rInput, rBaseURL, pHTTPHeaderAttrs );
173     SvParserState eState = xPrsr->CallParser(pEditEngine, aSel.Max());
174     if ( ( eState != SvParserState::Accepted ) && ( !rInput.GetError() ) )
175     {
176         rInput.SetError( EE_READWRITE_WRONGFORMAT );
177         return aSel.Min();
178     }
179     return xPrsr->GetCurSelection().Max();
180 }
181 
182 void ImpEditEngine::Write(SvStream& rOutput, EETextFormat eFormat, const EditSelection& rSel)
183 {
184     if ( !rOutput.IsWritable() )
185         rOutput.SetError( SVSTREAM_WRITE_ERROR );
186 
187     if ( !rOutput.GetError() )
188     {
189         if ( eFormat == EETextFormat::Text )
190             WriteText( rOutput, rSel );
191         else if ( eFormat == EETextFormat::Rtf )
192             WriteRTF( rOutput, rSel );
193         else if ( eFormat == EETextFormat::Xml )
194             WriteXML( rOutput, rSel );
195         else if ( eFormat == EETextFormat::Html )
196             ;
197         else
198         {
199             OSL_FAIL( "Write: Unknown Format" );
200         }
201     }
202 }
203 
204 ErrCode ImpEditEngine::WriteText( SvStream& rOutput, EditSelection aSel )
205 {
206     sal_Int32 nStartNode, nEndNode;
207     bool bRange = aSel.HasRange();
208     if ( bRange )
209     {
210         aSel.Adjust( aEditDoc );
211         nStartNode = aEditDoc.GetPos( aSel.Min().GetNode() );
212         nEndNode = aEditDoc.GetPos( aSel.Max().GetNode() );
213     }
214     else
215     {
216         nStartNode = 0;
217         nEndNode = aEditDoc.Count()-1;
218     }
219 
220     // iterate over the paragraphs ...
221     for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++  )
222     {
223         ContentNode* pNode = aEditDoc.GetObject( nNode );
224         DBG_ASSERT( pNode, "Node not found: Search&Replace" );
225 
226         sal_Int32 nStartPos = 0;
227         sal_Int32 nEndPos = pNode->Len();
228         if ( bRange )
229         {
230             if ( nNode == nStartNode )
231                 nStartPos = aSel.Min().GetIndex();
232             if ( nNode == nEndNode ) // can also be == nStart!
233                 nEndPos = aSel.Max().GetIndex();
234         }
235         OUString aTmpStr = EditDoc::GetParaAsString( pNode, nStartPos, nEndPos );
236         rOutput.WriteByteStringLine( aTmpStr, rOutput.GetStreamCharSet() );
237     }
238 
239     return rOutput.GetError();
240 }
241 
242 bool ImpEditEngine::WriteItemListAsRTF( ItemList& rLst, SvStream& rOutput, sal_Int32 nPara, sal_Int32 nPos,
243                         std::vector<SvxFontItem*>& rFontTable, SvxColorList& rColorList )
244 {
245     const SfxPoolItem* pAttrItem = rLst.First();
246     while ( pAttrItem )
247     {
248         WriteItemAsRTF( *pAttrItem, rOutput, nPara, nPos,rFontTable, rColorList );
249         pAttrItem = rLst.Next();
250     }
251     return rLst.Count() != 0;
252 }
253 
254 static void lcl_FindValidAttribs( ItemList& rLst, ContentNode* pNode, sal_Int32 nIndex, sal_uInt16 nScriptType )
255 {
256     sal_uInt16 nAttr = 0;
257     EditCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
258     while ( pAttr && ( pAttr->GetStart() <= nIndex ) )
259     {
260         // Start is checked in while ...
261         if ( pAttr->GetEnd() > nIndex )
262         {
263             if ( IsScriptItemValid( pAttr->GetItem()->Which(), nScriptType ) )
264                 rLst.Insert( pAttr->GetItem() );
265         }
266         nAttr++;
267         pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
268     }
269 }
270 
271 void ImpEditEngine::WriteXML(SvStream& rOutput, const EditSelection& rSel)
272 {
273     ESelection aESel = CreateESel(rSel);
274 
275     SvxWriteXML( *GetEditEnginePtr(), rOutput, aESel );
276 }
277 
278 ErrCode ImpEditEngine::WriteRTF( SvStream& rOutput, EditSelection aSel )
279 {
280     DBG_ASSERT( GetUpdateMode(), "WriteRTF for UpdateMode = sal_False!" );
281     CheckIdleFormatter();
282     if ( !IsFormatted() )
283         FormatDoc();
284 
285     sal_Int32 nStartNode, nEndNode;
286     aSel.Adjust( aEditDoc );
287 
288     nStartNode = aEditDoc.GetPos( aSel.Min().GetNode() );
289     nEndNode = aEditDoc.GetPos( aSel.Max().GetNode() );
290 
291     // RTF header ...
292     rOutput.WriteChar( '{' ) ;
293 
294     rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_RTF );
295 
296     rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ANSI );
297     rtl_TextEncoding eDestEnc = RTL_TEXTENCODING_MS_1252;
298 
299     // Generate and write out Font table  ...
300     std::vector<SvxFontItem*> aFontTable;
301     // default font must be up front, so DEF font in RTF
302     aFontTable.push_back( new SvxFontItem( aEditDoc.GetItemPool().GetDefaultItem( EE_CHAR_FONTINFO ) ) );
303     aFontTable.push_back( new SvxFontItem( aEditDoc.GetItemPool().GetDefaultItem( EE_CHAR_FONTINFO_CJK ) ) );
304     aFontTable.push_back( new SvxFontItem( aEditDoc.GetItemPool().GetDefaultItem( EE_CHAR_FONTINFO_CTL ) ) );
305     for ( sal_uInt16 nScriptType = 0; nScriptType < 3; nScriptType++ )
306     {
307         sal_uInt16 nWhich = EE_CHAR_FONTINFO;
308         if ( nScriptType == 1 )
309             nWhich = EE_CHAR_FONTINFO_CJK;
310         else if ( nScriptType == 2 )
311             nWhich = EE_CHAR_FONTINFO_CTL;
312 
313         auto const nFonts(aEditDoc.GetItemPool().GetItemCount2(nWhich));
314         for (sal_uInt32 i = 0; i < nFonts; ++i)
315         {
316             SvxFontItem const*const pFontItem = static_cast<const SvxFontItem*>(
317                     aEditDoc.GetItemPool().GetItem2(nWhich, i));
318             if (!pFontItem)
319             {
320                 continue;
321             }
322             bool bAlreadyExist = false;
323             sal_uLong nTestMax = nScriptType ? aFontTable.size() : 1;
324             for ( sal_uLong nTest = 0; !bAlreadyExist && ( nTest < nTestMax ); nTest++ )
325             {
326                 bAlreadyExist = *aFontTable[ nTest ] == *pFontItem;
327             }
328 
329             if ( !bAlreadyExist )
330                 aFontTable.push_back( new SvxFontItem( *pFontItem ) );
331         }
332     }
333 
334     rOutput << endl;
335     rOutput.WriteChar( '{' ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FONTTBL );
336     for ( std::vector<SvxFontItem*>::size_type j = 0; j < aFontTable.size(); j++ )
337     {
338         SvxFontItem* pFontItem = aFontTable[ j ];
339         rOutput.WriteChar( '{' );
340         rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_F );
341         rOutput.WriteUInt32AsString( j );
342         switch ( pFontItem->GetFamily()  )
343         {
344             case FAMILY_DONTKNOW:       rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FNIL );
345                                         break;
346             case FAMILY_DECORATIVE:     rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FDECOR );
347                                         break;
348             case FAMILY_MODERN:         rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FMODERN );
349                                         break;
350             case FAMILY_ROMAN:          rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FROMAN );
351                                         break;
352             case FAMILY_SCRIPT:         rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FSCRIPT );
353                                         break;
354             case FAMILY_SWISS:          rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FSWISS );
355                                         break;
356             default:
357                 break;
358         }
359         rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FPRQ );
360         sal_uInt16 nVal = 0;
361         switch( pFontItem->GetPitch() )
362         {
363             case PITCH_FIXED:       nVal = 1;       break;
364             case PITCH_VARIABLE:    nVal = 2;       break;
365             default:
366                 break;
367         }
368         rOutput.WriteUInt32AsString( nVal );
369 
370         rtl_TextEncoding eChrSet = pFontItem->GetCharSet();
371         DBG_ASSERT( eChrSet != 9, "SystemCharSet?!" );
372         if( RTL_TEXTENCODING_DONTKNOW == eChrSet )
373             eChrSet = osl_getThreadTextEncoding();
374         rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FCHARSET );
375         rOutput.WriteUInt32AsString( rtl_getBestWindowsCharsetFromTextEncoding( eChrSet ) );
376 
377         rOutput.WriteChar( ' ' );
378         RTFOutFuncs::Out_String( rOutput, pFontItem->GetFamilyName(), eDestEnc );
379         rOutput.WriteCharPtr( ";}" );
380     }
381     rOutput.WriteChar( '}' );
382     rOutput << endl;
383 
384     // Write out ColorList  ...
385     SvxColorList aColorList;
386     // COL_AUTO should be the default color, always put it first
387     aColorList.emplace_back(COL_AUTO);
388     SvxColorItem const& rDefault(aEditDoc.GetItemPool().GetDefaultItem(EE_CHAR_COLOR));
389     if (rDefault.GetValue() != COL_AUTO) // is the default always AUTO?
390     {
391         aColorList.push_back(rDefault.GetValue());
392     }
393     auto const nColors(aEditDoc.GetItemPool().GetItemCount2(EE_CHAR_COLOR));
394     for (sal_uInt32 i = 0; i < nColors; ++i)
395     {
396         SvxColorItem const*const pColorItem(aEditDoc.GetItemPool().GetItem2(EE_CHAR_COLOR, i));
397         if (pColorItem && pColorItem->GetValue() != COL_AUTO) // may be null!
398         {
399             aColorList.push_back(pColorItem->GetValue());
400         }
401     }
402 
403     rOutput.WriteChar( '{' ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_COLORTBL );
404     for ( SvxColorList::size_type j = 0; j < aColorList.size(); j++ )
405     {
406         Color const color = aColorList[j];
407         if (color != COL_AUTO) // auto is represented by "empty" element
408         {
409             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_RED );
410             rOutput.WriteUInt32AsString( color.GetRed() );
411             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_GREEN );
412             rOutput.WriteUInt32AsString( color.GetGreen() );
413             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_BLUE );
414             rOutput.WriteUInt32AsString( color.GetBlue() );
415         }
416         rOutput.WriteChar( ';' );
417     }
418     rOutput.WriteChar( '}' );
419     rOutput << endl;
420 
421     std::unordered_map<SfxStyleSheetBase*, sal_uInt32> aStyleSheetToIdMap;
422     // StyleSheets...
423     if ( GetStyleSheetPool() )
424     {
425         std::shared_ptr<SfxStyleSheetIterator> aSSSIterator = std::make_shared<SfxStyleSheetIterator>(GetStyleSheetPool(),
426                 SfxStyleFamily::All);
427         // fill aStyleSheetToIdMap
428         sal_uInt32 nId = 1;
429         for ( SfxStyleSheetBase* pStyle = aSSSIterator->First(); pStyle;
430                                  pStyle = aSSSIterator->Next() )
431         {
432             aStyleSheetToIdMap[pStyle] = nId;
433             nId++;
434         }
435 
436         if ( aSSSIterator->Count() )
437         {
438 
439             sal_uInt32 nStyle = 0;
440             rOutput.WriteChar( '{' ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_STYLESHEET );
441 
442             for ( SfxStyleSheetBase* pStyle = aSSSIterator->First(); pStyle;
443                                      pStyle = aSSSIterator->Next() )
444             {
445 
446                 rOutput << endl;
447                 rOutput.WriteChar( '{' ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_S );
448                 sal_uInt32 nNumber = nStyle + 1;
449                 rOutput.WriteUInt32AsString( nNumber );
450 
451                 // Attribute, also from Parent!
452                 for ( sal_uInt16 nParAttr = EE_PARA_START; nParAttr <= EE_CHAR_END; nParAttr++ )
453                 {
454                     if ( pStyle->GetItemSet().GetItemState( nParAttr ) == SfxItemState::SET )
455                     {
456                         const SfxPoolItem& rItem = pStyle->GetItemSet().Get( nParAttr );
457                         WriteItemAsRTF( rItem, rOutput, 0, 0, aFontTable, aColorList );
458                     }
459                 }
460 
461                 // Parent ... (only if necessary)
462                 if ( !pStyle->GetParent().isEmpty() && ( pStyle->GetParent() != pStyle->GetName() ) )
463                 {
464                     SfxStyleSheet* pParent = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( pStyle->GetParent(), pStyle->GetFamily() ));
465                     DBG_ASSERT( pParent, "Parent not found!" );
466                     rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SBASEDON );
467                     nNumber = aStyleSheetToIdMap.find(pParent)->second;
468                     rOutput.WriteUInt32AsString( nNumber );
469                 }
470 
471                 // Next Style... (more)
472                 // we assume that we have only SfxStyleSheet in the pool
473                 SfxStyleSheet* pNext = static_cast<SfxStyleSheet*>(pStyle);
474                 if ( !pStyle->GetFollow().isEmpty() && ( pStyle->GetFollow() != pStyle->GetName() ) )
475                     pNext = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( pStyle->GetFollow(), pStyle->GetFamily() ));
476 
477                 DBG_ASSERT( pNext, "Next not found!" );
478                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SNEXT );
479                 nNumber = aStyleSheetToIdMap.find(pNext)->second;
480                 rOutput.WriteUInt32AsString( nNumber );
481 
482                 // Name of the template...
483                 rOutput.WriteCharPtr( " " );
484                 RTFOutFuncs::Out_String( rOutput, pStyle->GetName(), eDestEnc );
485                 rOutput.WriteCharPtr( ";}" );
486                 nStyle++;
487             }
488             rOutput.WriteChar( '}' );
489             rOutput << endl;
490         }
491     }
492 
493     // Write the pool defaults in advance ...
494     rOutput.WriteChar( '{' ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_IGNORE ).WriteCharPtr( "\\EditEnginePoolDefaults" );
495     for ( sal_uInt16 nPoolDefItem = EE_PARA_START; nPoolDefItem <= EE_CHAR_END; nPoolDefItem++)
496     {
497         const SfxPoolItem& rItem = aEditDoc.GetItemPool().GetDefaultItem( nPoolDefItem );
498         WriteItemAsRTF( rItem, rOutput, 0, 0, aFontTable, aColorList );
499     }
500     rOutput.WriteChar( '}' ) << endl;
501 
502     // DefTab:
503     MapMode aTwpMode( MapUnit::MapTwip );
504     sal_uInt16 nDefTabTwps = static_cast<sal_uInt16>(GetRefDevice()->LogicToLogic(
505                                         Point( aEditDoc.GetDefTab(), 0 ),
506                                         &GetRefMapMode(), &aTwpMode ).X());
507     rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_DEFTAB );
508     rOutput.WriteUInt32AsString( nDefTabTwps );
509     rOutput << endl;
510 
511     // iterate over the paragraphs ...
512     rOutput.WriteChar( '{' ) << endl;
513     for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++  )
514     {
515         ContentNode* pNode = aEditDoc.GetObject( nNode );
516         DBG_ASSERT( pNode, "Node not found: Search&Replace" );
517 
518         // The paragraph attributes in advance ...
519         bool bAttr = false;
520 
521         // Template?
522         if ( pNode->GetStyleSheet() )
523         {
524             // Number of template
525             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_S );
526             sal_uInt32 nNumber = aStyleSheetToIdMap.find(pNode->GetStyleSheet())->second;
527             rOutput.WriteUInt32AsString( nNumber );
528 
529             // All Attribute
530             // Attribute, also from Parent!
531             for ( sal_uInt16 nParAttr = EE_PARA_START; nParAttr <= EE_CHAR_END; nParAttr++ )
532             {
533                 if ( pNode->GetStyleSheet()->GetItemSet().GetItemState( nParAttr ) == SfxItemState::SET )
534                 {
535                     const SfxPoolItem& rItem = pNode->GetStyleSheet()->GetItemSet().Get( nParAttr );
536                     WriteItemAsRTF( rItem, rOutput, nNode, 0, aFontTable, aColorList );
537                     bAttr = true;
538                 }
539             }
540         }
541 
542         for ( sal_uInt16 nParAttr = EE_PARA_START; nParAttr <= EE_CHAR_END; nParAttr++ )
543         {
544             // Now where stylesheet processing, only hard paragraph attributes!
545             if ( pNode->GetContentAttribs().GetItems().GetItemState( nParAttr ) == SfxItemState::SET )
546             {
547                 const SfxPoolItem& rItem = pNode->GetContentAttribs().GetItems().Get( nParAttr );
548                 WriteItemAsRTF( rItem, rOutput, nNode, 0, aFontTable, aColorList );
549                 bAttr = true;
550             }
551         }
552         if ( bAttr )
553             rOutput.WriteChar( ' ' ); // Separator
554 
555         ItemList aAttribItems;
556         ParaPortion* pParaPortion = FindParaPortion( pNode );
557         DBG_ASSERT( pParaPortion, "Portion not found: WriteRTF" );
558 
559         sal_Int32 nIndex = 0;
560         sal_Int32 nStartPos = 0;
561         sal_Int32 nEndPos = pNode->Len();
562         sal_Int32 nStartPortion = 0;
563         sal_Int32 nEndPortion = pParaPortion->GetTextPortions().Count() - 1;
564         bool bFinishPortion = false;
565         sal_Int32 nPortionStart;
566 
567         if ( nNode == nStartNode )
568         {
569             nStartPos = aSel.Min().GetIndex();
570             nStartPortion = pParaPortion->GetTextPortions().FindPortion( nStartPos, nPortionStart );
571             if ( nStartPos != 0 )
572             {
573                 aAttribItems.Clear();
574                 lcl_FindValidAttribs( aAttribItems, pNode, nStartPos, GetI18NScriptType( EditPaM( pNode, 0 ) ) );
575                 if ( aAttribItems.Count() )
576                 {
577                     // These attributes may not apply to the entire paragraph:
578                     rOutput.WriteChar( '{' );
579                     WriteItemListAsRTF( aAttribItems, rOutput, nNode, nStartPos, aFontTable, aColorList );
580                     bFinishPortion = true;
581                 }
582                 aAttribItems.Clear();
583             }
584         }
585         if ( nNode == nEndNode ) // can also be == nStart!
586         {
587             nEndPos = aSel.Max().GetIndex();
588             nEndPortion = pParaPortion->GetTextPortions().FindPortion( nEndPos, nPortionStart );
589         }
590 
591         const EditCharAttrib* pNextFeature = pNode->GetCharAttribs().FindFeature(nIndex);
592         // start at 0, so the index is right ...
593         for ( sal_Int32 n = 0; n <= nEndPortion; n++ )
594         {
595             const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[n];
596             if ( n < nStartPortion )
597             {
598                 nIndex = nIndex + rTextPortion.GetLen();
599                 continue;
600             }
601 
602             if ( pNextFeature && ( pNextFeature->GetStart() == nIndex ) && ( pNextFeature->GetItem()->Which() != EE_FEATURE_FIELD ) )
603             {
604                 WriteItemAsRTF( *pNextFeature->GetItem(), rOutput, nNode, nIndex, aFontTable, aColorList );
605                 pNextFeature = pNode->GetCharAttribs().FindFeature( pNextFeature->GetStart() + 1 );
606             }
607             else
608             {
609                 aAttribItems.Clear();
610                 sal_uInt16 nScriptTypeI18N = GetI18NScriptType( EditPaM( pNode, nIndex+1 ) );
611                 SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N);
612                 if ( !n || IsScriptChange( EditPaM( pNode, nIndex ) ) )
613                 {
614                     SfxItemSet aAttribs = GetAttribs( nNode, nIndex+1, nIndex+1 );
615                     aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_FONTINFO, nScriptType ) ) );
616                     aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_FONTHEIGHT, nScriptType ) ) );
617                     aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_WEIGHT, nScriptType ) ) );
618                     aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_ITALIC, nScriptType ) ) );
619                     aAttribItems.Insert( &aAttribs.Get( GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType ) ) );
620                 }
621                 // Insert hard attribs AFTER CJK attribs...
622                 lcl_FindValidAttribs( aAttribItems, pNode, nIndex, nScriptTypeI18N );
623 
624                 rOutput.WriteChar( '{' );
625                 if ( WriteItemListAsRTF( aAttribItems, rOutput, nNode, nIndex, aFontTable, aColorList ) )
626                     rOutput.WriteChar( ' ' );
627 
628                 sal_Int32 nS = nIndex;
629                 sal_Int32 nE = nIndex + rTextPortion.GetLen();
630                 if ( n == nStartPortion )
631                     nS = nStartPos;
632                 if ( n == nEndPortion )
633                     nE = nEndPos;
634 
635                 OUString aRTFStr = EditDoc::GetParaAsString( pNode, nS, nE);
636                 RTFOutFuncs::Out_String( rOutput, aRTFStr, eDestEnc );
637                 rOutput.WriteChar( '}' );
638             }
639             if ( bFinishPortion )
640             {
641                 rOutput.WriteChar( '}' );
642                 bFinishPortion = false;
643             }
644 
645             nIndex = nIndex + rTextPortion.GetLen();
646         }
647 
648         rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_PAR ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_PARD ).WriteCharPtr( OOO_STRING_SVTOOLS_RTF_PLAIN );
649         rOutput << endl;
650     }
651     // RTF-trailer ...
652     rOutput.WriteCharPtr( "}}" );    // 1xparentheses paragraphs, 1xparentheses RTF document
653     rOutput.Flush();
654 
655     std::vector<SvxFontItem*>::iterator it;
656     for (it = aFontTable.begin(); it != aFontTable.end(); ++it)
657         delete *it;
658 
659     return rOutput.GetError();
660 }
661 
662 
663 void ImpEditEngine::WriteItemAsRTF( const SfxPoolItem& rItem, SvStream& rOutput, sal_Int32 nPara, sal_Int32 nPos,
664                             std::vector<SvxFontItem*>& rFontTable, SvxColorList& rColorList )
665 {
666     sal_uInt16 nWhich = rItem.Which();
667     switch ( nWhich )
668     {
669         case EE_PARA_WRITINGDIR:
670         {
671             const SvxFrameDirectionItem& rWritingMode = static_cast<const SvxFrameDirectionItem&>(rItem);
672             if ( rWritingMode.GetValue() == SvxFrameDirection::Horizontal_RL_TB )
673                 rOutput.WriteCharPtr( "\\rtlpar" );
674             else
675                 rOutput.WriteCharPtr( "\\ltrpar" );
676         }
677         break;
678         case EE_PARA_OUTLLEVEL:
679         {
680             sal_Int32 nLevel = static_cast<const SfxInt16Item&>(rItem).GetValue();
681             if( nLevel >= 0 )
682             {
683                 rOutput.WriteCharPtr( "\\level" );
684                 rOutput.WriteInt32AsString( nLevel );
685             }
686         }
687         break;
688         case EE_PARA_OUTLLRSPACE:
689         case EE_PARA_LRSPACE:
690         {
691             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FI );
692             sal_Int32 nTxtFirst = static_cast<const SvxLRSpaceItem&>(rItem).GetTextFirstLineOfst();
693             nTxtFirst = LogicToTwips( nTxtFirst );
694             rOutput.WriteInt32AsString( nTxtFirst );
695             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_LI );
696             sal_uInt32 nTxtLeft = static_cast< sal_uInt32 >(static_cast<const SvxLRSpaceItem&>(rItem).GetTextLeft());
697             nTxtLeft = static_cast<sal_uInt32>(LogicToTwips( nTxtLeft ));
698             rOutput.WriteInt32AsString( nTxtLeft );
699             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_RI );
700             sal_uInt32 nTxtRight = static_cast<const SvxLRSpaceItem&>(rItem).GetRight();
701             nTxtRight = LogicToTwips( nTxtRight);
702             rOutput.WriteUInt32AsString( nTxtRight );
703         }
704         break;
705         case EE_PARA_ULSPACE:
706         {
707             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SB );
708             sal_uInt32 nUpper = static_cast<const SvxULSpaceItem&>(rItem).GetUpper();
709             nUpper = static_cast<sal_uInt32>(LogicToTwips( nUpper ));
710             rOutput.WriteUInt32AsString( nUpper );
711             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SA );
712             sal_uInt32 nLower = static_cast<const SvxULSpaceItem&>(rItem).GetLower();
713             nLower = LogicToTwips( nLower );
714             rOutput.WriteUInt32AsString( nLower );
715         }
716         break;
717         case EE_PARA_SBL:
718         {
719             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SL );
720             sal_Int32 nVal = static_cast<const SvxLineSpacingItem&>(rItem).GetLineHeight();
721             char cMult = '0';
722             if ( static_cast<const SvxLineSpacingItem&>(rItem).GetInterLineSpaceRule() == SvxInterLineSpaceRule::Prop )
723             {
724                 // From where do I get the value now?
725                 // The SwRTF parser is based on a 240 Font!
726                 nVal = static_cast<const SvxLineSpacingItem&>(rItem).GetPropLineSpace();
727                 nVal *= 240;
728                 nVal /= 100;
729                 cMult = '1';
730             }
731             rOutput.WriteInt32AsString( nVal );
732             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SLMULT ).WriteChar( cMult );
733         }
734         break;
735         case EE_PARA_JUST:
736         {
737             SvxAdjust eJustification = static_cast<const SvxAdjustItem&>(rItem).GetAdjust();
738             switch ( eJustification )
739             {
740                 case SvxAdjust::Center: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_QC );
741                                         break;
742                 case SvxAdjust::Right:  rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_QR );
743                                         break;
744                 default:                rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_QL );
745                                         break;
746             }
747         }
748         break;
749         case EE_PARA_TABS:
750         {
751             const SvxTabStopItem& rTabs = static_cast<const SvxTabStopItem&>(rItem);
752             for ( sal_uInt16 i = 0; i < rTabs.Count(); i++ )
753             {
754                 const SvxTabStop& rTab = rTabs[i];
755                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_TX );
756                 rOutput.WriteInt32AsString( LogicToTwips( rTab.GetTabPos() ) );
757             }
758         }
759         break;
760         case EE_CHAR_COLOR:
761         {
762             SvxColorList::const_iterator const iter = std::find(
763                     rColorList.begin(), rColorList.end(),
764                     static_cast<SvxColorItem const&>(rItem).GetValue());
765             assert(iter != rColorList.end());
766             sal_uInt32 const n = iter - rColorList.begin();
767             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_CF );
768             rOutput.WriteUInt32AsString( n );
769         }
770         break;
771         case EE_CHAR_FONTINFO:
772         case EE_CHAR_FONTINFO_CJK:
773         case EE_CHAR_FONTINFO_CTL:
774         {
775             sal_uInt32 n = 0;
776             for (size_t i = 0; i < rFontTable.size(); ++i)
777             {
778                 if (*rFontTable[i] == rItem)
779                 {
780                     n = i;
781                     break;
782                 }
783             }
784 
785             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_F );
786             rOutput.WriteUInt32AsString( n );
787         }
788         break;
789         case EE_CHAR_FONTHEIGHT:
790         case EE_CHAR_FONTHEIGHT_CJK:
791         case EE_CHAR_FONTHEIGHT_CTL:
792         {
793             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_FS );
794             sal_Int32 nHeight = static_cast<const SvxFontHeightItem&>(rItem).GetHeight();
795             nHeight = LogicToTwips( nHeight );
796             // Twips => HalfPoints
797             nHeight /= 10;
798             rOutput.WriteInt32AsString( nHeight );
799         }
800         break;
801         case EE_CHAR_WEIGHT:
802         case EE_CHAR_WEIGHT_CJK:
803         case EE_CHAR_WEIGHT_CTL:
804         {
805             FontWeight e = static_cast<const SvxWeightItem&>(rItem).GetWeight();
806             switch ( e )
807             {
808                 case WEIGHT_BOLD:   rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_B );                break;
809                 default:            rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_B ).WriteChar( '0' );     break;
810             }
811         }
812         break;
813         case EE_CHAR_UNDERLINE:
814         {
815             // Must underlined if in WordLineMode, but the information is
816             // missing here
817             FontLineStyle e = static_cast<const SvxUnderlineItem&>(rItem).GetLineStyle();
818             switch ( e )
819             {
820                 case LINESTYLE_NONE:    rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ULNONE );       break;
821                 case LINESTYLE_SINGLE:  rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_UL );       break;
822                 case LINESTYLE_DOUBLE:  rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ULDB );     break;
823                 case LINESTYLE_DOTTED:  rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ULD );      break;
824                 default:
825                     break;
826             }
827         }
828         break;
829         case EE_CHAR_OVERLINE:
830         {
831             FontLineStyle e = static_cast<const SvxOverlineItem&>(rItem).GetLineStyle();
832             switch ( e )
833             {
834                 case LINESTYLE_NONE:    rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_OLNONE );       break;
835                 case LINESTYLE_SINGLE:  rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_OL );       break;
836                 case LINESTYLE_DOUBLE:  rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_OLDB );     break;
837                 case LINESTYLE_DOTTED:  rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_OLD );      break;
838                 default:
839                     break;
840             }
841         }
842         break;
843         case EE_CHAR_STRIKEOUT:
844         {
845             FontStrikeout e = static_cast<const SvxCrossedOutItem&>(rItem).GetStrikeout();
846             switch ( e )
847             {
848                 case STRIKEOUT_SINGLE:
849                 case STRIKEOUT_DOUBLE:  rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_STRIKE );       break;
850                 case STRIKEOUT_NONE:    rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_STRIKE ).WriteChar( '0' );    break;
851                 default:
852                     break;
853             }
854         }
855         break;
856         case EE_CHAR_ITALIC:
857         case EE_CHAR_ITALIC_CJK:
858         case EE_CHAR_ITALIC_CTL:
859         {
860             FontItalic e = static_cast<const SvxPostureItem&>(rItem).GetPosture();
861             switch ( e )
862             {
863                 case ITALIC_OBLIQUE:
864                 case ITALIC_NORMAL: rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_I );        break;
865                 case ITALIC_NONE:   rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_I ).WriteChar( '0' ); break;
866                 default:
867                     break;
868             }
869         }
870         break;
871         case EE_CHAR_OUTLINE:
872         {
873             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_OUTL );
874             if ( !static_cast<const SvxContourItem&>(rItem).GetValue() )
875                 rOutput.WriteChar( '0' );
876         }
877         break;
878         case EE_CHAR_RELIEF:
879         {
880             FontRelief nRelief = static_cast<const SvxCharReliefItem&>(rItem).GetValue();
881             if ( nRelief == FontRelief::Embossed )
882                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_EMBO );
883             if ( nRelief == FontRelief::Engraved )
884                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_IMPR );
885         }
886         break;
887         case EE_CHAR_EMPHASISMARK:
888         {
889             FontEmphasisMark nMark = static_cast<const SvxEmphasisMarkItem&>(rItem).GetEmphasisMark();
890             if ( nMark == FontEmphasisMark::NONE )
891                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ACCNONE );
892             else if ( nMark == (FontEmphasisMark::Accent | FontEmphasisMark::PosAbove) )
893                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ACCCOMMA );
894             else
895                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_ACCDOT );
896         }
897         break;
898         case EE_CHAR_SHADOW:
899         {
900             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SHAD );
901             if ( !static_cast<const SvxShadowedItem&>(rItem).GetValue() )
902                 rOutput.WriteChar( '0' );
903         }
904         break;
905         case EE_FEATURE_TAB:
906         {
907             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_TAB );
908         }
909         break;
910         case EE_FEATURE_LINEBR:
911         {
912             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_SL );
913         }
914         break;
915         case EE_CHAR_KERNING:
916         {
917             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_EXPNDTW );
918             rOutput.WriteInt32AsString( LogicToTwips(
919                 static_cast<const SvxKerningItem&>(rItem).GetValue() ) );
920         }
921         break;
922         case EE_CHAR_PAIRKERNING:
923         {
924             rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_KERNING );
925             rOutput.WriteUInt32AsString( static_cast<const SvxAutoKernItem&>(rItem).GetValue() ? 1 : 0 );
926         }
927         break;
928         case EE_CHAR_ESCAPEMENT:
929         {
930             SvxFont aFont;
931             ContentNode* pNode = aEditDoc.GetObject( nPara );
932             SeekCursor( pNode, nPos, aFont );
933             MapMode aPntMode( MapUnit::MapPoint );
934             long nFontHeight = GetRefDevice()->LogicToLogic(
935                     aFont.GetFontSize(), &GetRefMapMode(), &aPntMode ).Height();
936             nFontHeight *=2;    // Half Points
937             sal_uInt16 const nProp = static_cast<const SvxEscapementItem&>(rItem).GetProportionalHeight();
938             sal_uInt16 nProp100 = nProp*100;    // For SWG-Token Prop in 100th percent.
939             short nEsc = static_cast<const SvxEscapementItem&>(rItem).GetEsc();
940             if ( nEsc == DFLT_ESC_AUTO_SUPER )
941             {
942                 nEsc = 100 - nProp;
943                 nProp100++; // A 1 afterwards means 'automatic'.
944             }
945             else if ( nEsc == DFLT_ESC_AUTO_SUB )
946             {
947                 nEsc = sal::static_int_cast< short >( -( 100 - nProp ) );
948                 nProp100++;
949             }
950             // SWG:
951             if ( nEsc )
952             {
953                 rOutput.WriteCharPtr( "{\\*\\updnprop" ).WriteCharPtr( OString::number(
954                     nProp100).getStr() ).WriteChar( '}' );
955             }
956             long nUpDown = nFontHeight * std::abs( nEsc ) / 100;
957             OString aUpDown = OString::number(
958                 nUpDown);
959             if ( nEsc < 0 )
960                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_DN ).WriteCharPtr( aUpDown.getStr() );
961             else if ( nEsc > 0 )
962                 rOutput.WriteCharPtr( OOO_STRING_SVTOOLS_RTF_UP ).WriteCharPtr( aUpDown.getStr() );
963         }
964         break;
965     }
966 }
967 
968 std::unique_ptr<EditTextObject> ImpEditEngine::GetEmptyTextObject()
969 {
970     EditSelection aEmptySel;
971     aEmptySel.Min() = aEditDoc.GetStartPaM();
972     aEmptySel.Max() = aEditDoc.GetStartPaM();
973 
974     return CreateTextObject( aEmptySel );
975 }
976 
977 std::unique_ptr<EditTextObject> ImpEditEngine::CreateTextObject()
978 {
979     EditSelection aCompleteSelection;
980     aCompleteSelection.Min() = aEditDoc.GetStartPaM();
981     aCompleteSelection.Max() = aEditDoc.GetEndPaM();
982 
983     return CreateTextObject( aCompleteSelection );
984 }
985 
986 std::unique_ptr<EditTextObject> ImpEditEngine::CreateTextObject(const EditSelection& rSel)
987 {
988     return CreateTextObject(rSel, GetEditTextObjectPool(), aStatus.AllowBigObjects(), nBigTextObjectStart);
989 }
990 
991 std::unique_ptr<EditTextObject> ImpEditEngine::CreateTextObject( EditSelection aSel, SfxItemPool* pPool, bool bAllowBigObjects, sal_Int32 nBigObjectStart )
992 {
993     std::unique_ptr<EditTextObject> pTxtObj(new EditTextObject(pPool));
994     pTxtObj->SetVertical( IsVertical(), IsTopToBottom());
995     MapUnit eMapUnit = aEditDoc.GetItemPool().GetMetric( DEF_METRIC );
996     pTxtObj->mpImpl->SetMetric( static_cast<sal_uInt16>(eMapUnit) );
997     if ( pTxtObj->mpImpl->IsOwnerOfPool() )
998         pTxtObj->mpImpl->GetPool()->SetDefaultMetric( eMapUnit );
999 
1000     sal_Int32 nStartNode, nEndNode;
1001     sal_Int32 nTextPortions = 0;
1002 
1003     aSel.Adjust( aEditDoc );
1004     nStartNode = aEditDoc.GetPos( aSel.Min().GetNode() );
1005     nEndNode = aEditDoc.GetPos( aSel.Max().GetNode() );
1006 
1007     bool bOnlyFullParagraphs = !( aSel.Min().GetIndex() ||
1008         ( aSel.Max().GetIndex() < aSel.Max().GetNode()->Len() ) );
1009 
1010     // Templates are not saved!
1011     // (Only the name and family, template itself must be in App!)
1012     pTxtObj->mpImpl->SetScriptType(GetItemScriptType(aSel));
1013 
1014     // iterate over the paragraphs ...
1015     sal_Int32 nNode;
1016     for ( nNode = nStartNode; nNode <= nEndNode; nNode++  )
1017     {
1018         ContentNode* pNode = aEditDoc.GetObject( nNode );
1019         DBG_ASSERT( pNode, "Node not found: Search&Replace" );
1020 
1021         if ( bOnlyFullParagraphs )
1022         {
1023             const ParaPortion* pParaPortion = GetParaPortions()[nNode];
1024             nTextPortions += pParaPortion->GetTextPortions().Count();
1025         }
1026 
1027         sal_Int32 nStartPos = 0;
1028         sal_Int32 nEndPos = pNode->Len();
1029 
1030         bool bEmptyPara = nEndPos == 0;
1031 
1032         if ( ( nNode == nStartNode ) && !bOnlyFullParagraphs )
1033             nStartPos = aSel.Min().GetIndex();
1034         if ( ( nNode == nEndNode ) && !bOnlyFullParagraphs )
1035             nEndPos = aSel.Max().GetIndex();
1036 
1037 
1038         ContentInfo *pC = pTxtObj->mpImpl->CreateAndInsertContent();
1039 
1040         // The paragraph attributes ...
1041         pC->GetParaAttribs().Set( pNode->GetContentAttribs().GetItems() );
1042 
1043         // The StyleSheet...
1044         if ( pNode->GetStyleSheet() )
1045         {
1046             pC->SetStyle(pNode->GetStyleSheet()->GetName());
1047             pC->SetFamily(pNode->GetStyleSheet()->GetFamily());
1048         }
1049 
1050         // The Text...
1051         pC->SetText(pNode->Copy(nStartPos, nEndPos-nStartPos));
1052 
1053         // and the Attribute...
1054         sal_uInt16 nAttr = 0;
1055         EditCharAttrib* pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
1056         while ( pAttr )
1057         {
1058             // In a blank paragraph keep the attributes!
1059             if ( bEmptyPara ||
1060                  ( ( pAttr->GetEnd() > nStartPos ) && ( pAttr->GetStart() < nEndPos ) ) )
1061             {
1062                 std::unique_ptr<XEditAttribute> pX = pTxtObj->mpImpl->CreateAttrib(*pAttr->GetItem(), pAttr->GetStart(), pAttr->GetEnd());
1063                 // Possibly Correct ...
1064                 if ( ( nNode == nStartNode ) && ( nStartPos != 0 ) )
1065                 {
1066                     pX->GetStart() = ( pX->GetStart() > nStartPos ) ? pX->GetStart()-nStartPos : 0;
1067                     pX->GetEnd() = pX->GetEnd() - nStartPos;
1068 
1069                 }
1070                 if ( nNode == nEndNode )
1071                 {
1072                     if ( pX->GetEnd() > (nEndPos-nStartPos) )
1073                         pX->GetEnd() = nEndPos-nStartPos;
1074                 }
1075                 DBG_ASSERT( pX->GetEnd() <= (nEndPos-nStartPos), "CreateBinTextObject: Attribute too long!" );
1076                 if ( !pX->GetLen() && !bEmptyPara )
1077                     pTxtObj->mpImpl->DestroyAttrib(std::move(pX));
1078                 else
1079                     pC->GetCharAttribs().push_back(std::move(pX));
1080             }
1081             nAttr++;
1082             pAttr = GetAttrib( pNode->GetCharAttribs().GetAttribs(), nAttr );
1083         }
1084 
1085         // If possible online spelling
1086         if ( bAllowBigObjects && bOnlyFullParagraphs && pNode->GetWrongList() )
1087             pC->SetWrongList( pNode->GetWrongList()->Clone() );
1088 
1089     }
1090 
1091     // Remember the portions info in case of large text objects:
1092     // sleeper set up when Olli paragraphs not hacked!
1093     if ( bAllowBigObjects && bOnlyFullParagraphs && IsFormatted() && GetUpdateMode() && ( nTextPortions >= nBigObjectStart ) )
1094     {
1095         XParaPortionList* pXList = new XParaPortionList( GetRefDevice(), aPaperSize.Width(), nStretchX, nStretchY );
1096         pTxtObj->mpImpl->SetPortionInfo(std::unique_ptr<XParaPortionList>(pXList));
1097         for ( nNode = nStartNode; nNode <= nEndNode; nNode++  )
1098         {
1099             const ParaPortion* pParaPortion = GetParaPortions()[nNode];
1100             XParaPortion* pX = new XParaPortion;
1101             pXList->push_back(pX);
1102 
1103             pX->nHeight = pParaPortion->GetHeight();
1104             pX->nFirstLineOffset = pParaPortion->GetFirstLineOffset();
1105 
1106             // The TextPortions
1107             sal_uInt16 nCount = pParaPortion->GetTextPortions().Count();
1108             sal_uInt16 n;
1109             for ( n = 0; n < nCount; n++ )
1110             {
1111                 const TextPortion& rTextPortion = pParaPortion->GetTextPortions()[n];
1112                 TextPortion* pNew = new TextPortion( rTextPortion );
1113                 pX->aTextPortions.Append(pNew);
1114             }
1115 
1116             // The lines
1117             nCount = pParaPortion->GetLines().Count();
1118             for ( n = 0; n < nCount; n++ )
1119             {
1120                 const EditLine& rLine = pParaPortion->GetLines()[n];
1121                 EditLine* pNew = rLine.Clone();
1122                 pX->aLines.Append(pNew);
1123             }
1124 #ifdef DBG_UTIL
1125             sal_uInt16 nTest;
1126             int nTPLen = 0, nTxtLen = 0;
1127             for ( nTest = pParaPortion->GetTextPortions().Count(); nTest; )
1128                 nTPLen += pParaPortion->GetTextPortions()[--nTest].GetLen();
1129             for ( nTest = pParaPortion->GetLines().Count(); nTest; )
1130                 nTxtLen += pParaPortion->GetLines()[--nTest].GetLen();
1131             DBG_ASSERT( ( nTPLen == pParaPortion->GetNode()->Len() ) && ( nTxtLen == pParaPortion->GetNode()->Len() ), "CreateBinTextObject: ParaPortion not completely formatted!" );
1132 #endif
1133         }
1134     }
1135     return pTxtObj;
1136 }
1137 
1138 void ImpEditEngine::SetText( const EditTextObject& rTextObject )
1139 {
1140     // Since setting a text object is not undo-able!
1141     ResetUndoManager();
1142     bool _bUpdate = GetUpdateMode();
1143     bool _bUndo = IsUndoEnabled();
1144 
1145     SetText( OUString() );
1146     EditPaM aPaM = aEditDoc.GetStartPaM();
1147 
1148     SetUpdateMode( false );
1149     EnableUndo( false );
1150 
1151     InsertText( rTextObject, EditSelection( aPaM, aPaM ) );
1152     SetVertical( rTextObject.IsVertical(), rTextObject.IsTopToBottom());
1153 
1154     DBG_ASSERT( !HasUndoManager() || !GetUndoManager().GetUndoActionCount(), "From where comes the Undo in SetText ?!" );
1155     SetUpdateMode( _bUpdate );
1156     EnableUndo( _bUndo );
1157 }
1158 
1159 EditSelection ImpEditEngine::InsertText( const EditTextObject& rTextObject, EditSelection aSel )
1160 {
1161     aSel.Adjust( aEditDoc );
1162     if ( aSel.HasRange() )
1163         aSel = ImpDeleteSelection( aSel );
1164     EditSelection aNewSel = InsertTextObject( rTextObject, aSel.Max() );
1165     return aNewSel;
1166 }
1167 
1168 EditSelection ImpEditEngine::InsertTextObject( const EditTextObject& rTextObject, EditPaM aPaM )
1169 {
1170     // Optimize: No getPos undFindParaportion, instead calculate index!
1171     EditSelection aSel( aPaM, aPaM );
1172     DBG_ASSERT( !aSel.DbgIsBuggy( aEditDoc ), "InsertBibTextObject: Selection broken!(1)" );
1173 
1174     bool bUsePortionInfo = false;
1175     XParaPortionList* pPortionInfo = rTextObject.mpImpl->GetPortionInfo();
1176 
1177     if ( pPortionInfo && ( static_cast<long>(pPortionInfo->GetPaperWidth()) == aPaperSize.Width() )
1178             && ( pPortionInfo->GetRefMapMode() == GetRefDevice()->GetMapMode() )
1179             && ( pPortionInfo->GetStretchX() == nStretchX )
1180             && ( pPortionInfo->GetStretchY() == nStretchY ) )
1181     {
1182         if ( (pPortionInfo->GetRefDevPtr() == GetRefDevice()) ||
1183              (pPortionInfo->RefDevIsVirtual() && GetRefDevice()->IsVirtual()) )
1184         bUsePortionInfo = true;
1185     }
1186 
1187     bool bConvertMetricOfItems = false;
1188     MapUnit eSourceUnit = MapUnit(), eDestUnit = MapUnit();
1189     if (rTextObject.mpImpl->HasMetric())
1190     {
1191         eSourceUnit = static_cast<MapUnit>(rTextObject.mpImpl->GetMetric());
1192         eDestUnit = aEditDoc.GetItemPool().GetMetric( DEF_METRIC );
1193         if ( eSourceUnit != eDestUnit )
1194             bConvertMetricOfItems = true;
1195     }
1196 
1197     // Before, paragraph count was of type sal_uInt16 so if nContents exceeded
1198     // 0xFFFF this wouldn't have worked anyway, given that nPara is used to
1199     // number paragraphs and is fearlessly incremented.
1200     sal_Int32 nContents = static_cast<sal_Int32>(rTextObject.mpImpl->GetContents().size());
1201     SAL_WARN_IF( nContents < 0, "editeng", "ImpEditEngine::InsertTextObject - contents overflow " << nContents);
1202     sal_Int32 nPara = aEditDoc.GetPos( aPaM.GetNode() );
1203 
1204     for (sal_Int32 n = 0; n < nContents; ++n, ++nPara)
1205     {
1206         const ContentInfo* pC = rTextObject.mpImpl->GetContents()[n].get();
1207         bool bNewContent = aPaM.GetNode()->Len() == 0;
1208         const sal_Int32 nStartPos = aPaM.GetIndex();
1209 
1210         aPaM = ImpFastInsertText( aPaM, pC->GetText() );
1211 
1212         ParaPortion* pPortion = FindParaPortion( aPaM.GetNode() );
1213         DBG_ASSERT( pPortion, "Blind Portion in FastInsertText" );
1214         pPortion->MarkInvalid( nStartPos, pC->GetText().getLength() );
1215 
1216         // Character attributes ...
1217         bool bAllreadyHasAttribs = aPaM.GetNode()->GetCharAttribs().Count() != 0;
1218         size_t nNewAttribs = pC->GetCharAttribs().size();
1219         if ( nNewAttribs )
1220         {
1221             bool bUpdateFields = false;
1222             for (size_t nAttr = 0; nAttr < nNewAttribs; ++nAttr)
1223             {
1224                 const XEditAttribute& rX = *pC->GetCharAttribs()[nAttr].get();
1225                 // Can happen when paragraphs > 16K, it is simply wrapped.
1226                     //TODO! Still true, still needed?
1227                 if ( rX.GetEnd() <= aPaM.GetNode()->Len() )
1228                 {
1229                     if ( !bAllreadyHasAttribs || rX.IsFeature() )
1230                     {
1231                         // Normal attributes then go faster ...
1232                         // Features shall not be inserted through
1233                         // EditDoc:: InsertAttrib, using FastInsertText they are
1234                         // already in the flow
1235                         DBG_ASSERT( rX.GetEnd() <= aPaM.GetNode()->Len(), "InsertBinTextObject: Attribute too large!" );
1236                         EditCharAttrib* pAttr;
1237                         if ( !bConvertMetricOfItems )
1238                             pAttr = MakeCharAttrib( aEditDoc.GetItemPool(), *(rX.GetItem()), rX.GetStart()+nStartPos, rX.GetEnd()+nStartPos );
1239                         else
1240                         {
1241                             std::unique_ptr<SfxPoolItem> pNew(rX.GetItem()->Clone());
1242                             ConvertItem( *pNew, eSourceUnit, eDestUnit );
1243                             pAttr = MakeCharAttrib( aEditDoc.GetItemPool(), *pNew, rX.GetStart()+nStartPos, rX.GetEnd()+nStartPos );
1244                         }
1245                         DBG_ASSERT( pAttr->GetEnd() <= aPaM.GetNode()->Len(), "InsertBinTextObject: Attribute does not fit! (1)" );
1246                         aPaM.GetNode()->GetCharAttribs().InsertAttrib( pAttr );
1247                         if ( pAttr->Which() == EE_FEATURE_FIELD )
1248                             bUpdateFields = true;
1249                     }
1250                     else
1251                     {
1252                         DBG_ASSERT( rX.GetEnd()+nStartPos <= aPaM.GetNode()->Len(), "InsertBinTextObject: Attribute does not fit! (2)" );
1253                         // Tabs and other Features can not be inserted through InsertAttrib:
1254                         aEditDoc.InsertAttrib( aPaM.GetNode(), rX.GetStart()+nStartPos, rX.GetEnd()+nStartPos, *rX.GetItem() );
1255                     }
1256                 }
1257             }
1258             if ( bUpdateFields )
1259                 UpdateFields();
1260 
1261             // Otherwise, quick format => no attributes!
1262             pPortion->MarkSelectionInvalid( nStartPos );
1263         }
1264 
1265 #if OSL_DEBUG_LEVEL > 0 && !defined NDEBUG
1266         CharAttribList::DbgCheckAttribs(aPaM.GetNode()->GetCharAttribs());
1267 #endif
1268 
1269         bool bParaAttribs = false;
1270         if ( bNewContent || ( ( n > 0 ) && ( n < (nContents-1) ) ) )
1271         {
1272             // only style and ParaAttribs when new paragraph, or
1273             // completely internal ...
1274             bParaAttribs = pC->GetParaAttribs().Count() != 0;
1275             if ( GetStyleSheetPool() && pC->GetStyle().getLength() )
1276             {
1277                 SfxStyleSheet* pStyle = static_cast<SfxStyleSheet*>(GetStyleSheetPool()->Find( pC->GetStyle(), pC->GetFamily() ));
1278                 DBG_ASSERT( pStyle, "InsertBinTextObject - Style not found!" );
1279                 SetStyleSheet( nPara, pStyle );
1280             }
1281             if ( !bConvertMetricOfItems )
1282                 SetParaAttribs( aEditDoc.GetPos( aPaM.GetNode() ), pC->GetParaAttribs() );
1283             else
1284             {
1285                 SfxItemSet aAttribs( GetEmptyItemSet() );
1286                 ConvertAndPutItems( aAttribs, pC->GetParaAttribs(), &eSourceUnit, &eDestUnit );
1287                 SetParaAttribs( aEditDoc.GetPos( aPaM.GetNode() ), aAttribs );
1288             }
1289             if ( bNewContent && bUsePortionInfo )
1290             {
1291                 const XParaPortion& rXP = (*pPortionInfo)[n];
1292                 ParaPortion* pParaPortion = GetParaPortions()[ nPara ];
1293                 DBG_ASSERT( pParaPortion, "InsertBinTextObject: ParaPortion?" );
1294                 pParaPortion->nHeight = rXP.nHeight;
1295                 pParaPortion->nFirstLineOffset = rXP.nFirstLineOffset;
1296                 pParaPortion->bForceRepaint = true;
1297                 pParaPortion->SetValid();   // Do not format
1298 
1299                 // The Text Portions
1300                 pParaPortion->GetTextPortions().Reset();
1301                 sal_uInt16 nCount = rXP.aTextPortions.Count();
1302                 for ( sal_uInt16 _n = 0; _n < nCount; _n++ )
1303                 {
1304                     const TextPortion& rTextPortion = rXP.aTextPortions[_n];
1305                     TextPortion* pNew = new TextPortion( rTextPortion );
1306                     pParaPortion->GetTextPortions().Insert(_n, pNew);
1307                 }
1308 
1309                 // The lines
1310                 pParaPortion->GetLines().Reset();
1311                 nCount = rXP.aLines.Count();
1312                 for ( sal_uInt16 m = 0; m < nCount; m++ )
1313                 {
1314                     const EditLine& rLine = rXP.aLines[m];
1315                     EditLine* pNew = rLine.Clone();
1316                     pNew->SetInvalid(); // Paint again!
1317                     pParaPortion->GetLines().Insert(m, pNew);
1318                 }
1319 #ifdef DBG_UTIL
1320                 sal_uInt16 nTest;
1321                 int nTPLen = 0, nTxtLen = 0;
1322                 for ( nTest = pParaPortion->GetTextPortions().Count(); nTest; )
1323                     nTPLen += pParaPortion->GetTextPortions()[--nTest].GetLen();
1324                 for ( nTest = pParaPortion->GetLines().Count(); nTest; )
1325                     nTxtLen += pParaPortion->GetLines()[--nTest].GetLen();
1326                 DBG_ASSERT( ( nTPLen == pParaPortion->GetNode()->Len() ) && ( nTxtLen == pParaPortion->GetNode()->Len() ), "InsertBinTextObject: ParaPortion not completely formatted!" );
1327 #endif
1328             }
1329         }
1330         if ( !bParaAttribs ) // DefFont is not calculated for FastInsertParagraph
1331         {
1332             aPaM.GetNode()->GetCharAttribs().GetDefFont() = aEditDoc.GetDefFont();
1333             if ( aStatus.UseCharAttribs() )
1334                 aPaM.GetNode()->CreateDefFont();
1335         }
1336 
1337         if ( bNewContent && GetStatus().DoOnlineSpelling() && pC->GetWrongList() )
1338         {
1339             aPaM.GetNode()->SetWrongList( pC->GetWrongList()->Clone() );
1340         }
1341 
1342         // Wrap when followed by other ...
1343         if ( n < ( nContents-1) )
1344         {
1345             if ( bNewContent )
1346                 aPaM = ImpFastInsertParagraph( nPara+1 );
1347             else
1348                 aPaM = ImpInsertParaBreak( aPaM, false );
1349         }
1350     }
1351 
1352     aSel.Max() = aPaM;
1353     DBG_ASSERT( !aSel.DbgIsBuggy( aEditDoc ), "InsertBibTextObject: Selection broken!(1)" );
1354     return aSel;
1355 }
1356 
1357 void ImpEditEngine::GetAllMisspellRanges( std::vector<editeng::MisspellRanges>& rRanges ) const
1358 {
1359     std::vector<editeng::MisspellRanges> aRanges;
1360     const EditDoc& rDoc = GetEditDoc();
1361     for (sal_Int32 i = 0, n = rDoc.Count(); i < n; ++i)
1362     {
1363         const ContentNode* pNode = rDoc.GetObject(i);
1364         const WrongList* pWrongList = pNode->GetWrongList();
1365         if (!pWrongList)
1366             continue;
1367 
1368         aRanges.emplace_back(i, pWrongList->GetRanges());
1369     }
1370 
1371     aRanges.swap(rRanges);
1372 }
1373 
1374 void ImpEditEngine::SetAllMisspellRanges( const std::vector<editeng::MisspellRanges>& rRanges )
1375 {
1376     EditDoc& rDoc = GetEditDoc();
1377     for (auto const& rParaRanges : rRanges)
1378     {
1379         ContentNode* pNode = rDoc.GetObject(rParaRanges.mnParagraph);
1380         if (!pNode)
1381             continue;
1382 
1383         pNode->CreateWrongList();
1384         WrongList* pWrongList = pNode->GetWrongList();
1385         pWrongList->SetRanges(rParaRanges.maRanges);
1386     }
1387 }
1388 
1389 LanguageType ImpEditEngine::GetLanguage( const EditPaM& rPaM, sal_Int32* pEndPos ) const
1390 {
1391     short nScriptTypeI18N = GetI18NScriptType( rPaM, pEndPos ); // pEndPos will be valid now, pointing to ScriptChange or NodeLen
1392     SvtScriptType nScriptType = SvtLanguageOptions::FromI18NToSvtScriptType(nScriptTypeI18N);
1393     sal_uInt16 nLangId = GetScriptItemId( EE_CHAR_LANGUAGE, nScriptType );
1394     const SvxLanguageItem* pLangItem = &static_cast<const SvxLanguageItem&>(rPaM.GetNode()->GetContentAttribs().GetItem( nLangId ));
1395     const EditCharAttrib* pAttr = rPaM.GetNode()->GetCharAttribs().FindAttrib( nLangId, rPaM.GetIndex() );
1396     if ( pAttr )
1397         pLangItem = static_cast<const SvxLanguageItem*>(pAttr->GetItem());
1398 
1399     if ( pEndPos && pAttr && ( pAttr->GetEnd() < *pEndPos ) )
1400         *pEndPos = pAttr->GetEnd();
1401 
1402     return pLangItem->GetLanguage();
1403 }
1404 
1405 css::lang::Locale ImpEditEngine::GetLocale( const EditPaM& rPaM ) const
1406 {
1407     return LanguageTag( GetLanguage( rPaM ) ).getLocale();
1408 }
1409 
1410 Reference< XSpellChecker1 > const & ImpEditEngine::GetSpeller()
1411 {
1412     if ( !xSpeller.is() )
1413         xSpeller = LinguMgr::GetSpellChecker();
1414     return xSpeller;
1415 }
1416 
1417 
1418 void ImpEditEngine::CreateSpellInfo( bool bMultipleDocs )
1419 {
1420     if (!pSpellInfo)
1421         pSpellInfo.reset( new SpellInfo );
1422     else
1423         *pSpellInfo = SpellInfo();  // reset to default values
1424 
1425     pSpellInfo->bMultipleDoc = bMultipleDocs;
1426     // always spell draw objects completely, starting at the top.
1427     // (spelling in only a selection or not starting with the top requires
1428     // further changes elsewhere to work properly)
1429     pSpellInfo->aSpellStart = EPaM();
1430     pSpellInfo->aSpellTo    = EPaM( EE_PARA_NOT_FOUND, EE_INDEX_NOT_FOUND );
1431 }
1432 
1433 
1434 EESpellState ImpEditEngine::Spell( EditView* pEditView, bool bMultipleDoc )
1435 {
1436     SAL_WARN_IF( !xSpeller.is(), "editeng", "No Spell checker set!" );
1437 
1438     if ( !xSpeller.is() )
1439         return EESpellState::NoSpeller;
1440 
1441     aOnlineSpellTimer.Stop();
1442 
1443     // In MultipleDoc always from the front / rear ...
1444     if ( bMultipleDoc )
1445     {
1446         pEditView->pImpEditView->SetEditSelection( aEditDoc.GetStartPaM() );
1447     }
1448 
1449     EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() );
1450     CreateSpellInfo( bMultipleDoc );
1451 
1452     bool bIsStart = false;
1453     if ( bMultipleDoc )
1454         bIsStart = true;    // Accessible from the front or from behind ...
1455     else if ( CreateEPaM( aEditDoc.GetStartPaM() ) == pSpellInfo->aSpellStart )
1456         bIsStart = true;
1457 
1458     std::unique_ptr<EditSpellWrapper> pWrp(new EditSpellWrapper( Application::GetDefDialogParent(),
1459             bIsStart, pEditView ));
1460     pWrp->SpellDocument();
1461     pWrp.reset();
1462 
1463     if ( !bMultipleDoc )
1464     {
1465         pEditView->pImpEditView->DrawSelectionXOR();
1466         if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() )
1467             aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() );
1468         aCurSel.Min() = aCurSel.Max();
1469         pEditView->pImpEditView->SetEditSelection( aCurSel );
1470         pEditView->pImpEditView->DrawSelectionXOR();
1471         pEditView->ShowCursor( true, false );
1472     }
1473     EESpellState eState = pSpellInfo->eState;
1474     pSpellInfo.reset();
1475     return eState;
1476 }
1477 
1478 
1479 bool ImpEditEngine::HasConvertibleTextPortion( LanguageType nSrcLang )
1480 {
1481     bool    bHasConvTxt = false;
1482 
1483     sal_Int32 nParas = pEditEngine->GetParagraphCount();
1484     for (sal_Int32 k = 0;  k < nParas;  ++k)
1485     {
1486         std::vector<sal_Int32> aPortions;
1487         pEditEngine->GetPortions( k, aPortions );
1488         for ( size_t nPos = 0; nPos < aPortions.size(); ++nPos )
1489         {
1490             sal_Int32 nEnd   = aPortions[ nPos ];
1491             sal_Int32 nStart = nPos > 0 ? aPortions[ nPos - 1 ] : 0;
1492 
1493             // if the paragraph is not empty we need to increase the index
1494             // by one since the attribute of the character left to the
1495             // specified position is evaluated.
1496             if (nEnd > nStart)  // empty para?
1497                 ++nStart;
1498             LanguageType nLangFound = pEditEngine->GetLanguage( k, nStart );
1499 #ifdef DEBUG
1500             lang::Locale aLocale( LanguageTag::convertToLocale( nLangFound ) );
1501 #endif
1502             bHasConvTxt =   (nSrcLang == nLangFound) ||
1503                             (editeng::HangulHanjaConversion::IsChinese( nLangFound ) &&
1504                              editeng::HangulHanjaConversion::IsChinese( nSrcLang ));
1505             if (bHasConvTxt)
1506                 return bHasConvTxt;
1507        }
1508     }
1509 
1510     return bHasConvTxt;
1511 }
1512 
1513 
1514 void ImpEditEngine::Convert( EditView* pEditView,
1515         LanguageType nSrcLang, LanguageType nDestLang, const vcl::Font *pDestFont,
1516         sal_Int32 nOptions, bool bIsInteractive, bool bMultipleDoc )
1517 {
1518     // modified version of ImpEditEngine::Spell
1519 
1520     // In MultipleDoc always from the front / rear ...
1521     if ( bMultipleDoc )
1522         pEditView->pImpEditView->SetEditSelection( aEditDoc.GetStartPaM() );
1523 
1524 
1525     // initialize pConvInfo
1526     EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() );
1527     aCurSel.Adjust( aEditDoc );
1528     pConvInfo.reset(new ConvInfo);
1529     pConvInfo->bMultipleDoc = bMultipleDoc;
1530     pConvInfo->aConvStart = CreateEPaM( aCurSel.Min() );
1531 
1532     // if it is not just a selection and we are about to begin
1533     // with the current conversion for the very first time
1534     // we need to find the start of the current (initial)
1535     // convertible unit in order for the text conversion to give
1536     // the correct result for that. Since it is easier to obtain
1537     // the start of the word we use that though.
1538     if (!aCurSel.HasRange() && ImplGetBreakIterator().is())
1539     {
1540         EditPaM aWordStartPaM(  SelectWord( aCurSel, i18n::WordType::DICTIONARY_WORD ).Min() );
1541 
1542         // since #118246 / #117803 still occurs if the cursor is placed
1543         // between the two chinese characters to be converted (because both
1544         // of them are words on their own!) using the word boundary here does
1545         // not work. Thus since chinese conversion is not interactive we start
1546         // at the begin of the paragraph to solve the problem, i.e. have the
1547         // TextConversion service get those characters together in the same call.
1548         pConvInfo->aConvStart.nIndex = editeng::HangulHanjaConversion::IsChinese( nSrcLang )
1549             ? 0 : aWordStartPaM.GetIndex();
1550     }
1551 
1552     pConvInfo->aConvContinue = pConvInfo->aConvStart;
1553 
1554     bool bIsStart = false;
1555     if ( bMultipleDoc )
1556         bIsStart = true;    // Accessible from the front or from behind ...
1557     else if ( CreateEPaM( aEditDoc.GetStartPaM() ) == pConvInfo->aConvStart )
1558         bIsStart = true;
1559 
1560     TextConvWrapper aWrp( Application::GetDefDialogParent(),
1561                           ::comphelper::getProcessComponentContext(),
1562                           LanguageTag::convertToLocale( nSrcLang ),
1563                           LanguageTag::convertToLocale( nDestLang ),
1564                           pDestFont,
1565                           nOptions, bIsInteractive,
1566                           bIsStart, pEditView );
1567 
1568 
1569     //!! optimization does not work since when update mode is false
1570     //!! the object is 'lying' about it portions, paragraphs,
1571     //!! EndPaM... later on.
1572     //!! Should not be a great problem since text boxes or cells in
1573     //!! Calc usually have only a rather short text.
1574     //
1575     // disallow formatting, updating the view, ... while
1576     // non-interactively converting the document. (saves time)
1577     //if (!bIsInteractive)
1578     //  SetUpdateMode( sal_False );
1579 
1580     aWrp.Convert();
1581 
1582     //if (!bIsInteractive)
1583     //SetUpdateMode( sal_True, 0, sal_True );
1584 
1585     if ( !bMultipleDoc )
1586     {
1587         pEditView->pImpEditView->DrawSelectionXOR();
1588         if ( aCurSel.Max().GetIndex() > aCurSel.Max().GetNode()->Len() )
1589             aCurSel.Max().SetIndex( aCurSel.Max().GetNode()->Len() );
1590         aCurSel.Min() = aCurSel.Max();
1591         pEditView->pImpEditView->SetEditSelection( aCurSel );
1592         pEditView->pImpEditView->DrawSelectionXOR();
1593         pEditView->ShowCursor( true, false );
1594     }
1595     pConvInfo.reset();
1596 }
1597 
1598 
1599 void ImpEditEngine::SetLanguageAndFont(
1600     const ESelection &rESel,
1601     LanguageType nLang, sal_uInt16 nLangWhichId,
1602     const vcl::Font *pFont,  sal_uInt16 nFontWhichId )
1603 {
1604     ESelection aOldSel = pActiveView->GetSelection();
1605     pActiveView->SetSelection( rESel );
1606 
1607     // set new language attribute
1608     SfxItemSet aNewSet( pActiveView->GetEmptyItemSet() );
1609     aNewSet.Put( SvxLanguageItem( nLang, nLangWhichId ) );
1610 
1611     // new font to be set?
1612     DBG_ASSERT( pFont, "target font missing?" );
1613     if (pFont)
1614     {
1615         // set new font attribute
1616         SvxFontItem aFontItem = static_cast<const SvxFontItem&>( aNewSet.Get( nFontWhichId ) );
1617         aFontItem.SetFamilyName( pFont->GetFamilyName());
1618         aFontItem.SetFamily( pFont->GetFamilyType());
1619         aFontItem.SetStyleName( pFont->GetStyleName());
1620         aFontItem.SetPitch( pFont->GetPitch());
1621         aFontItem.SetCharSet( pFont->GetCharSet() );
1622         aNewSet.Put( aFontItem );
1623     }
1624 
1625     // apply new attributes
1626     pActiveView->SetAttribs( aNewSet );
1627 
1628     pActiveView->SetSelection( aOldSel );
1629 }
1630 
1631 
1632 void ImpEditEngine::ImpConvert( OUString &rConvTxt, LanguageType &rConvTxtLang,
1633         EditView* pEditView, LanguageType nSrcLang, const ESelection &rConvRange,
1634         bool bAllowImplicitChangesForNotConvertibleText,
1635         LanguageType nTargetLang, const vcl::Font *pTargetFont  )
1636 {
1637     // modified version of ImpEditEngine::ImpSpell
1638 
1639     // looks for next convertible text portion to be passed on to the wrapper
1640 
1641     OUString aRes;
1642     LanguageType nResLang = LANGUAGE_NONE;
1643 
1644     EditPaM aPos( CreateEditPaM( pConvInfo->aConvContinue ) );
1645     EditSelection aCurSel = EditSelection( aPos, aPos );
1646 
1647     OUString aWord;
1648 
1649     while (aRes.isEmpty())
1650     {
1651         // empty paragraph found that needs to have language and font set?
1652         if (bAllowImplicitChangesForNotConvertibleText &&
1653             pEditEngine->GetText( pConvInfo->aConvContinue.nPara ).isEmpty())
1654         {
1655             sal_Int32 nPara = pConvInfo->aConvContinue.nPara;
1656             ESelection aESel( nPara, 0, nPara, 0 );
1657             // see comment for below same function call
1658             SetLanguageAndFont( aESel,
1659                     nTargetLang, EE_CHAR_LANGUAGE_CJK,
1660                     pTargetFont, EE_CHAR_FONTINFO_CJK );
1661         }
1662 
1663 
1664         if (pConvInfo->aConvContinue.nPara  == pConvInfo->aConvTo.nPara &&
1665             pConvInfo->aConvContinue.nIndex >= pConvInfo->aConvTo.nIndex)
1666             break;
1667 
1668         sal_Int32 nAttribStart = -1;
1669         sal_Int32 nAttribEnd   = -1;
1670         sal_Int32 nCurPos      = -1;
1671         EPaM aCurStart = CreateEPaM( aCurSel.Min() );
1672         std::vector<sal_Int32> aPortions;
1673         pEditEngine->GetPortions( aCurStart.nPara, aPortions );
1674         for ( size_t nPos = 0; nPos < aPortions.size(); ++nPos )
1675         {
1676             const sal_Int32 nEnd   = aPortions[ nPos ];
1677             const sal_Int32 nStart = nPos > 0 ? aPortions[ nPos - 1 ] : 0;
1678 
1679             // the language attribute is obtained from the left character
1680             // (like usually all other attributes)
1681             // thus we usually have to add 1 in order to get the language
1682             // of the text right to the cursor position
1683             const sal_Int32 nLangIdx = nEnd > nStart ? nStart + 1 : nStart;
1684             LanguageType nLangFound = pEditEngine->GetLanguage( aCurStart.nPara, nLangIdx );
1685 #ifdef DEBUG
1686             lang::Locale aLocale( LanguageTag::convertToLocale( nLangFound ) );
1687 #endif
1688             bool bLangOk =  (nLangFound == nSrcLang) ||
1689                                 (editeng::HangulHanjaConversion::IsChinese( nLangFound ) &&
1690                                  editeng::HangulHanjaConversion::IsChinese( nSrcLang ));
1691 
1692             if (nAttribEnd>=0) // start already found?
1693             {
1694                 DBG_ASSERT(nEnd >= aCurStart.nIndex, "error while scanning attributes (a)" );
1695                 DBG_ASSERT(nEnd >= nAttribEnd, "error while scanning attributes (b)" );
1696                 if (/*nEnd >= aCurStart.nIndex &&*/ nLangFound == nResLang)
1697                     nAttribEnd = nEnd;
1698                 else  // language attrib has changed
1699                     break;
1700             }
1701             if (nAttribStart<0 && // start not yet found?
1702                 nEnd > aCurStart.nIndex && bLangOk)
1703             {
1704                 nAttribStart = nStart;
1705                 nAttribEnd   = nEnd;
1706                 nResLang = nLangFound;
1707             }
1708             //! the list of portions may have changed compared to the previous
1709             //! call to this function (because of possibly changed language
1710             //! attribute!)
1711             //! But since we don't want to start in the already processed part
1712             //! we clip the start accordingly.
1713             if (nAttribStart >= 0 && nAttribStart < aCurStart.nIndex)
1714             {
1715                 nAttribStart = aCurStart.nIndex;
1716             }
1717 
1718             // check script type to the right of the start of the current portion
1719             EditPaM aPaM( CreateEditPaM( EPaM(aCurStart.nPara, nLangIdx) ) );
1720             bool bIsAsianScript = (i18n::ScriptType::ASIAN == GetI18NScriptType( aPaM ));
1721             // not yet processed text part with for conversion
1722             // not suitable language found that needs to be changed?
1723             if (bAllowImplicitChangesForNotConvertibleText &&
1724                 !bLangOk && !bIsAsianScript && nEnd > aCurStart.nIndex)
1725             {
1726                 ESelection aESel( aCurStart.nPara, nStart, aCurStart.nPara, nEnd );
1727                 // set language and font to target language and font of conversion
1728                 //! Now this especially includes all non convertible text e.g.
1729                 //! spaces, empty paragraphs and western text.
1730                 // This is in order for every *new* text entered at *any* position to
1731                 // have the correct language and font attributes set.
1732                 SetLanguageAndFont( aESel,
1733                         nTargetLang, EE_CHAR_LANGUAGE_CJK,
1734                         pTargetFont, EE_CHAR_FONTINFO_CJK );
1735             }
1736 
1737             nCurPos = nEnd;
1738         }
1739 
1740         if (nAttribStart>=0 && nAttribEnd>=0)
1741         {
1742             aCurSel.Min().SetIndex( nAttribStart );
1743             aCurSel.Max().SetIndex( nAttribEnd );
1744         }
1745         else if (nCurPos>=0)
1746         {
1747             // set selection to end of scanned text
1748             // (used to set the position where to continue from later on)
1749             aCurSel.Min().SetIndex( nCurPos );
1750             aCurSel.Max().SetIndex( nCurPos );
1751         }
1752 
1753         if ( !pConvInfo->bConvToEnd )
1754         {
1755             EPaM aEPaM( CreateEPaM( aCurSel.Min() ) );
1756             if ( !( aEPaM < pConvInfo->aConvTo ) )
1757                 break;
1758         }
1759 
1760         // clip selected word to the converted area
1761         // (main use when conversion starts/ends **within** a word)
1762         EditPaM aPaM( CreateEditPaM( pConvInfo->aConvStart ) );
1763         if (pConvInfo->bConvToEnd &&
1764             aCurSel.Min().GetNode() == aPaM.GetNode() &&
1765             aCurSel.Min().GetIndex() < aPaM.GetIndex())
1766                 aCurSel.Min().SetIndex( aPaM.GetIndex() );
1767         aPaM = CreateEditPaM( pConvInfo->aConvContinue );
1768         if (aCurSel.Min().GetNode() == aPaM.GetNode() &&
1769             aCurSel.Min().GetIndex() < aPaM.GetIndex())
1770                 aCurSel.Min().SetIndex( aPaM.GetIndex() );
1771         aPaM = CreateEditPaM( pConvInfo->aConvTo );
1772         if ((!pConvInfo->bConvToEnd || rConvRange.HasRange())&&
1773             aCurSel.Max().GetNode() == aPaM.GetNode() &&
1774             aCurSel.Max().GetIndex() > aPaM.GetIndex())
1775                 aCurSel.Max().SetIndex( aPaM.GetIndex() );
1776 
1777         aWord = GetSelected( aCurSel );
1778 
1779         if ( !aWord.isEmpty() /* && bLangOk */)
1780             aRes = aWord;
1781 
1782         // move to next word/paragraph if necessary
1783         if ( aRes.isEmpty() )
1784             aCurSel = WordRight( aCurSel.Min(), css::i18n::WordType::DICTIONARY_WORD );
1785 
1786         pConvInfo->aConvContinue = CreateEPaM( aCurSel.Max() );
1787     }
1788 
1789     pEditView->pImpEditView->DrawSelectionXOR();
1790     pEditView->pImpEditView->SetEditSelection( aCurSel );
1791     pEditView->pImpEditView->DrawSelectionXOR();
1792     pEditView->ShowCursor( true, false );
1793 
1794     rConvTxt = aRes;
1795     if ( !rConvTxt.isEmpty() )
1796         rConvTxtLang = nResLang;
1797 }
1798 
1799 
1800 Reference< XSpellAlternatives > ImpEditEngine::ImpSpell( EditView* pEditView )
1801 {
1802     DBG_ASSERT( xSpeller.is(), "No spell checker set!" );
1803 
1804     ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count()-1 );
1805     EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() );
1806     aCurSel.Min() = aCurSel.Max();
1807 
1808     Reference< XSpellAlternatives > xSpellAlt;
1809     Sequence< PropertyValue > aEmptySeq;
1810     while (!xSpellAlt.is())
1811     {
1812         // Known (most likely) bug: If SpellToCurrent, the current has to be
1813         // corrected at each replacement, otherwise it may not fit exactly in
1814         // the end ...
1815         if ( pSpellInfo->bSpellToEnd || pSpellInfo->bMultipleDoc )
1816         {
1817             if ( aCurSel.Max().GetNode() == pLastNode )
1818             {
1819                 if ( aCurSel.Max().GetIndex() >= pLastNode->Len() )
1820                     break;
1821             }
1822         }
1823         else if ( !pSpellInfo->bSpellToEnd )
1824         {
1825             EPaM aEPaM( CreateEPaM( aCurSel.Max() ) );
1826             if ( !( aEPaM < pSpellInfo->aSpellTo ) )
1827                 break;
1828         }
1829 
1830         aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD );
1831         OUString aWord = GetSelected( aCurSel );
1832 
1833         // If afterwards a dot, this must be handed over!
1834         // If an abbreviation ...
1835         if ( !aWord.isEmpty() && ( aCurSel.Max().GetIndex() < aCurSel.Max().GetNode()->Len() ) )
1836         {
1837             sal_Unicode cNext = aCurSel.Max().GetNode()->GetChar( aCurSel.Max().GetIndex() );
1838             if ( cNext == '.' )
1839             {
1840                 aCurSel.Max().SetIndex( aCurSel.Max().GetIndex()+1 );
1841                 aWord += OUStringLiteral1(cNext);
1842             }
1843         }
1844 
1845         if ( !aWord.isEmpty() )
1846         {
1847             LanguageType eLang = GetLanguage( aCurSel.Max() );
1848             SvxSpellWrapper::CheckSpellLang( xSpeller, eLang );
1849             xSpellAlt = xSpeller->spell( aWord, static_cast<sal_uInt16>(eLang), aEmptySeq );
1850         }
1851 
1852         if ( !xSpellAlt.is() )
1853             aCurSel = WordRight( aCurSel.Min(), css::i18n::WordType::DICTIONARY_WORD );
1854         else
1855             pSpellInfo->eState = EESpellState::ErrorFound;
1856     }
1857 
1858     pEditView->pImpEditView->DrawSelectionXOR();
1859     pEditView->pImpEditView->SetEditSelection( aCurSel );
1860     pEditView->pImpEditView->DrawSelectionXOR();
1861     pEditView->ShowCursor( true, false );
1862     return xSpellAlt;
1863 }
1864 
1865 Reference< XSpellAlternatives > ImpEditEngine::ImpFindNextError(EditSelection& rSelection)
1866 {
1867     EditSelection aCurSel( rSelection.Min() );
1868 
1869     Reference< XSpellAlternatives > xSpellAlt;
1870     Sequence< PropertyValue > aEmptySeq;
1871     while (!xSpellAlt.is())
1872     {
1873         //check if the end of the selection has been reached
1874         {
1875             EPaM aEPaM( CreateEPaM( aCurSel.Max() ) );
1876             if ( !( aEPaM < CreateEPaM( rSelection.Max()) ) )
1877                 break;
1878         }
1879 
1880         aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD );
1881         OUString aWord = GetSelected( aCurSel );
1882 
1883         // If afterwards a dot, this must be handed over!
1884         // If an abbreviation ...
1885         if ( !aWord.isEmpty() && ( aCurSel.Max().GetIndex() < aCurSel.Max().GetNode()->Len() ) )
1886         {
1887             sal_Unicode cNext = aCurSel.Max().GetNode()->GetChar( aCurSel.Max().GetIndex() );
1888             if ( cNext == '.' )
1889             {
1890                 aCurSel.Max().SetIndex( aCurSel.Max().GetIndex()+1 );
1891                 aWord += OUStringLiteral1(cNext);
1892             }
1893         }
1894 
1895         if ( !aWord.isEmpty() )
1896             xSpellAlt = xSpeller->spell( aWord, static_cast<sal_uInt16>(GetLanguage( aCurSel.Max() )), aEmptySeq );
1897 
1898         if ( !xSpellAlt.is() )
1899             aCurSel = WordRight( aCurSel.Min(), css::i18n::WordType::DICTIONARY_WORD );
1900         else
1901         {
1902             pSpellInfo->eState = EESpellState::ErrorFound;
1903             rSelection = aCurSel;
1904         }
1905     }
1906     return xSpellAlt;
1907 }
1908 
1909 bool ImpEditEngine::SpellSentence(EditView const & rEditView,
1910     svx::SpellPortions& rToFill )
1911 {
1912     bool bRet = false;
1913     EditSelection aCurSel( rEditView.pImpEditView->GetEditSelection() );
1914     if(!pSpellInfo)
1915         CreateSpellInfo( true );
1916     pSpellInfo->aCurSentenceStart = aCurSel.Min();
1917     DBG_ASSERT( xSpeller.is(), "No spell checker set!" );
1918     pSpellInfo->aLastSpellPortions.clear();
1919     pSpellInfo->aLastSpellContentSelections.clear();
1920     rToFill.clear();
1921     //if no selection previously exists the range is extended to the end of the object
1922     if (!aCurSel.HasRange())
1923     {
1924         ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count()-1);
1925         aCurSel.Max() = EditPaM(pLastNode, pLastNode->Len());
1926     }
1927     // check for next error in aCurSel and set aCurSel to that one if any was found
1928     Reference< XSpellAlternatives > xAlt = ImpFindNextError(aCurSel);
1929     if (xAlt.is())
1930     {
1931         bRet = true;
1932         //find the sentence boundaries
1933         EditSelection aSentencePaM = SelectSentence(aCurSel);
1934         //make sure that the sentence is never smaller than the error range!
1935         if(aSentencePaM.Max().GetIndex() < aCurSel.Max().GetIndex())
1936             aSentencePaM.Max() = aCurSel.Max();
1937         //add the portion preceding the error
1938         EditSelection aStartSelection(aSentencePaM.Min(), aCurSel.Min());
1939         if(aStartSelection.HasRange())
1940             AddPortionIterated(rEditView, aStartSelection, nullptr, rToFill);
1941         //add the error portion
1942         AddPortionIterated(rEditView, aCurSel, xAlt, rToFill);
1943         //find the end of the sentence
1944         //search for all errors in the rest of the sentence and add all the portions
1945         do
1946         {
1947             EditSelection aNextSel = EditSelection(aCurSel.Max(), aSentencePaM.Max());
1948             xAlt = ImpFindNextError(aNextSel);
1949             if(xAlt.is())
1950             {
1951                 //add the part between the previous and the current error
1952                 AddPortionIterated(rEditView, EditSelection(aCurSel.Max(), aNextSel.Min()), nullptr, rToFill);
1953                 //add the current error
1954                 AddPortionIterated(rEditView, aNextSel, xAlt, rToFill);
1955             }
1956             else
1957                 AddPortionIterated(rEditView, EditSelection(aCurSel.Max(), aSentencePaM.Max()), xAlt, rToFill);
1958             aCurSel = aNextSel;
1959         }
1960         while( xAlt.is() );
1961 
1962         //set the selection to the end of the current sentence
1963         rEditView.pImpEditView->SetEditSelection(aSentencePaM.Max());
1964     }
1965     return bRet;
1966 }
1967 
1968 // Adds one portion to the SpellPortions
1969 void ImpEditEngine::AddPortion(
1970                             const EditSelection& rSel,
1971                             const uno::Reference< XSpellAlternatives >& xAlt,
1972                             svx::SpellPortions& rToFill,
1973                             bool bIsField)
1974 {
1975     if(rSel.HasRange())
1976     {
1977         svx::SpellPortion aPortion;
1978         aPortion.sText = GetSelected( rSel );
1979         aPortion.eLanguage = GetLanguage( rSel.Min() );
1980         aPortion.xAlternatives = xAlt;
1981         aPortion.bIsField = bIsField;
1982         rToFill.push_back(aPortion);
1983 
1984         //save the spelled portions for later use
1985         pSpellInfo->aLastSpellPortions.push_back(aPortion);
1986         pSpellInfo->aLastSpellContentSelections.push_back(rSel);
1987 
1988     }
1989 }
1990 
1991 // Adds one or more portions of text to the SpellPortions depending on language changes
1992 void ImpEditEngine::AddPortionIterated(
1993                             EditView const & rEditView,
1994                             const EditSelection& rSel,
1995                             const Reference< XSpellAlternatives >& xAlt,
1996                             svx::SpellPortions& rToFill)
1997 {
1998     if (rSel.HasRange())
1999     {
2000         if(xAlt.is())
2001         {
2002             AddPortion(rSel, xAlt, rToFill, false);
2003         }
2004         else
2005         {
2006             //iterate and search for language attribute changes
2007             //save the start and end positions
2008             bool bTest = rSel.Min().GetIndex() <= rSel.Max().GetIndex();
2009             EditPaM aStart(bTest ? rSel.Min() : rSel.Max());
2010             EditPaM aEnd(bTest ? rSel.Max() : rSel.Min());
2011             //iterate over the text to find changes in language
2012             //set the mark equal to the point
2013             EditPaM aCursor(aStart);
2014             rEditView.pImpEditView->SetEditSelection( aCursor );
2015             LanguageType eStartLanguage = GetLanguage( aCursor );
2016             //search for a field attribute at the beginning - only the end position
2017             //of this field is kept to end a portion at that position
2018             const EditCharAttrib* pFieldAttr = aCursor.GetNode()->GetCharAttribs().
2019                                                     FindFeature( aCursor.GetIndex() );
2020             bool bIsField = pFieldAttr &&
2021                     pFieldAttr->GetStart() == aCursor.GetIndex() &&
2022                     pFieldAttr->GetStart() != pFieldAttr->GetEnd() &&
2023                     pFieldAttr->Which() == EE_FEATURE_FIELD;
2024             sal_Int32 nEndField = bIsField ? pFieldAttr->GetEnd() : -1;
2025             do
2026             {
2027                 aCursor = CursorRight( aCursor);
2028                 //determine whether a field and has been reached
2029                 bool bIsEndField = nEndField == aCursor.GetIndex();
2030                 //search for a new field attribute
2031                 const EditCharAttrib* _pFieldAttr = aCursor.GetNode()->GetCharAttribs().
2032                                                         FindFeature( aCursor.GetIndex() );
2033                 bIsField = _pFieldAttr &&
2034                         _pFieldAttr->GetStart() == aCursor.GetIndex() &&
2035                         _pFieldAttr->GetStart() != _pFieldAttr->GetEnd() &&
2036                         _pFieldAttr->Which() == EE_FEATURE_FIELD;
2037                 //on every new field move the end position
2038                 if (bIsField)
2039                     nEndField = _pFieldAttr->GetEnd();
2040 
2041                 LanguageType eCurLanguage = GetLanguage( aCursor );
2042                 if(eCurLanguage != eStartLanguage || bIsField || bIsEndField)
2043                 {
2044                     eStartLanguage = eCurLanguage;
2045                     //go one step back - the cursor currently selects the first character
2046                     //with a different language
2047                     //create a selection from start to the current Cursor
2048                     EditSelection aSelection(aStart, aCursor);
2049                     AddPortion(aSelection, xAlt, rToFill, bIsEndField);
2050                     aStart = aCursor;
2051                 }
2052             }
2053             while(aCursor.GetIndex() < aEnd.GetIndex());
2054             EditSelection aSelection(aStart, aCursor);
2055             AddPortion(aSelection, xAlt, rToFill, bIsField);
2056         }
2057     }
2058 }
2059 
2060 void ImpEditEngine::ApplyChangedSentence(EditView const & rEditView,
2061     const svx::SpellPortions& rNewPortions,
2062     bool bRecheck )
2063 {
2064     // Note: rNewPortions.size() == 0 is valid and happens when the whole
2065     // sentence got removed in the dialog
2066 
2067     DBG_ASSERT(pSpellInfo, "pSpellInfo not initialized");
2068     if (pSpellInfo &&
2069         !pSpellInfo->aLastSpellPortions.empty())  // no portions -> no text to be changed
2070     {
2071         // get current paragraph length to calculate later on how the sentence length changed,
2072         // in order to place the cursor at the end of the sentence again
2073         EditSelection aOldSel( rEditView.pImpEditView->GetEditSelection() );
2074         sal_Int32 nOldLen = aOldSel.Max().GetNode()->Len();
2075 
2076         UndoActionStart( EDITUNDO_INSERT );
2077         if(pSpellInfo->aLastSpellPortions.size() == rNewPortions.size())
2078         {
2079             DBG_ASSERT( !rNewPortions.empty(), "rNewPortions should not be empty here" );
2080             DBG_ASSERT( pSpellInfo->aLastSpellPortions.size() == pSpellInfo->aLastSpellContentSelections.size(),
2081                     "aLastSpellPortions and aLastSpellContentSelections size mismatch" );
2082 
2083             //the simple case: the same number of elements on both sides
2084             //each changed element has to be applied to the corresponding source element
2085             svx::SpellPortions::const_iterator aCurrentNewPortion = rNewPortions.end();
2086             svx::SpellPortions::const_iterator aCurrentOldPortion = pSpellInfo->aLastSpellPortions.end();
2087             SpellContentSelections::const_iterator aCurrentOldPosition = pSpellInfo->aLastSpellContentSelections.end();
2088             bool bSetToEnd = false;
2089             do
2090             {
2091                 --aCurrentNewPortion;
2092                 --aCurrentOldPortion;
2093                 --aCurrentOldPosition;
2094                 //set the cursor to the end of the sentence - necessary to
2095                 //resume there at the next step
2096                 if(!bSetToEnd)
2097                 {
2098                     bSetToEnd = true;
2099                     rEditView.pImpEditView->SetEditSelection( aCurrentOldPosition->Max() );
2100                 }
2101 
2102                 SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( aCurrentNewPortion->eLanguage );
2103                 sal_uInt16 nLangWhichId = EE_CHAR_LANGUAGE;
2104                 switch(nScriptType)
2105                 {
2106                     case SvtScriptType::ASIAN : nLangWhichId = EE_CHAR_LANGUAGE_CJK; break;
2107                     case SvtScriptType::COMPLEX : nLangWhichId = EE_CHAR_LANGUAGE_CTL; break;
2108                     default: break;
2109                 }
2110                 if(aCurrentNewPortion->sText != aCurrentOldPortion->sText)
2111                 {
2112                     //change text and apply language
2113                     SfxItemSet aSet( aEditDoc.GetItemPool(), {{nLangWhichId, nLangWhichId}});
2114                     aSet.Put(SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId));
2115                     SetAttribs( *aCurrentOldPosition, aSet );
2116                     ImpInsertText( *aCurrentOldPosition, aCurrentNewPortion->sText );
2117                 }
2118                 else if(aCurrentNewPortion->eLanguage != aCurrentOldPortion->eLanguage)
2119                 {
2120                     //apply language
2121                     SfxItemSet aSet( aEditDoc.GetItemPool(), {{nLangWhichId, nLangWhichId}});
2122                     aSet.Put(SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId));
2123                     SetAttribs( *aCurrentOldPosition, aSet );
2124                 }
2125                 if(aCurrentNewPortion == rNewPortions.begin())
2126                     break;
2127             }
2128             while(aCurrentNewPortion != rNewPortions.begin());
2129         }
2130         else
2131         {
2132             DBG_ASSERT( !pSpellInfo->aLastSpellContentSelections.empty(), "aLastSpellContentSelections should not be empty here" );
2133 
2134             //select the complete sentence
2135             SpellContentSelections::const_iterator aCurrentEndPosition = pSpellInfo->aLastSpellContentSelections.end();
2136             --aCurrentEndPosition;
2137             SpellContentSelections::const_iterator aCurrentStartPosition = pSpellInfo->aLastSpellContentSelections.begin();
2138             EditSelection aAllSentence(aCurrentStartPosition->Min(), aCurrentEndPosition->Max());
2139 
2140             //delete the sentence completely
2141             ImpDeleteSelection( aAllSentence );
2142             svx::SpellPortions::const_iterator aCurrentNewPortion = rNewPortions.begin();
2143             EditPaM aCurrentPaM = aAllSentence.Min();
2144             while(aCurrentNewPortion != rNewPortions.end())
2145             {
2146                 //set the language attribute
2147                 LanguageType eCurLanguage = GetLanguage( aCurrentPaM );
2148                 if(eCurLanguage != aCurrentNewPortion->eLanguage)
2149                 {
2150                     SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( aCurrentNewPortion->eLanguage );
2151                     sal_uInt16 nLangWhichId = EE_CHAR_LANGUAGE;
2152                     switch(nScriptType)
2153                     {
2154                         case SvtScriptType::ASIAN : nLangWhichId = EE_CHAR_LANGUAGE_CJK; break;
2155                         case SvtScriptType::COMPLEX : nLangWhichId = EE_CHAR_LANGUAGE_CTL; break;
2156                         default: break;
2157                     }
2158                     SfxItemSet aSet( aEditDoc.GetItemPool(), {{nLangWhichId, nLangWhichId}});
2159                     aSet.Put(SvxLanguageItem(aCurrentNewPortion->eLanguage, nLangWhichId));
2160                     SetAttribs( aCurrentPaM, aSet );
2161                 }
2162                 //insert the new string and set the cursor to the end of the inserted string
2163                 aCurrentPaM = ImpInsertText( aCurrentPaM , aCurrentNewPortion->sText );
2164                 ++aCurrentNewPortion;
2165             }
2166         }
2167         UndoActionEnd();
2168 
2169         EditPaM aNext;
2170         if (bRecheck)
2171             aNext = pSpellInfo->aCurSentenceStart;
2172         else
2173         {
2174             // restore cursor position to the end of the modified sentence.
2175             // (This will define the continuation position for spell/grammar checking)
2176             // First: check if the sentence/para length changed
2177             const sal_Int32 nDelta = rEditView.pImpEditView->GetEditSelection().Max().GetNode()->Len() - nOldLen;
2178             const sal_Int32 nEndOfSentence = aOldSel.Max().GetIndex() + nDelta;
2179             aNext = EditPaM( aOldSel.Max().GetNode(), nEndOfSentence );
2180         }
2181         rEditView.pImpEditView->SetEditSelection( aNext );
2182 
2183         FormatAndUpdate();
2184         aEditDoc.SetModified(true);
2185     }
2186 }
2187 
2188 void ImpEditEngine::PutSpellingToSentenceStart( EditView const & rEditView )
2189 {
2190     if( pSpellInfo && !pSpellInfo->aLastSpellContentSelections.empty() )
2191     {
2192         rEditView.pImpEditView->SetEditSelection( pSpellInfo->aLastSpellContentSelections.begin()->Min() );
2193     }
2194 }
2195 
2196 
2197 void ImpEditEngine::DoOnlineSpelling( ContentNode* pThisNodeOnly, bool bSpellAtCursorPos, bool bInterruptible )
2198 {
2199     /*
2200      It will iterate over all the paragraphs, paragraphs with only
2201      invalidated wrong list will be checked ...
2202 
2203      All the words are checked in the invalidated region. Is a word wrong,
2204      but not in the wrong list, or vice versa, the range of the word will be
2205      invalidated
2206      (no Invalidate, but if only transitions wrong from right =>, simple Paint,
2207       even out properly with VDev on transitions from wrong => right)
2208     */
2209 
2210     if ( !xSpeller.is() )
2211         return;
2212 
2213     EditPaM aCursorPos;
2214     if( pActiveView && !bSpellAtCursorPos )
2215     {
2216         aCursorPos = pActiveView->pImpEditView->GetEditSelection().Max();
2217     }
2218 
2219     bool bRestartTimer = false;
2220 
2221     ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count() - 1 );
2222     sal_Int32 nNodes = GetEditDoc().Count();
2223     sal_Int32 nInvalids = 0;
2224     Sequence< PropertyValue > aEmptySeq;
2225     for ( sal_Int32 n = 0; n < nNodes; n++ )
2226     {
2227         ContentNode* pNode = GetEditDoc().GetObject( n );
2228         if ( pThisNodeOnly )
2229             pNode = pThisNodeOnly;
2230 
2231         pNode->EnsureWrongList();
2232         if (!pNode->GetWrongList()->IsValid())
2233         {
2234             WrongList* pWrongList = pNode->GetWrongList();
2235             const size_t nInvStart = pWrongList->GetInvalidStart();
2236             const size_t nInvEnd = pWrongList->GetInvalidEnd();
2237 
2238             sal_Int32 nPaintFrom = -1;
2239             sal_Int32 nPaintTo = 0;
2240             bool bSimpleRepaint = true;
2241 
2242             pWrongList->SetValid();
2243 
2244             EditPaM aPaM( pNode, nInvStart );
2245             EditSelection aSel( aPaM, aPaM );
2246             while ( aSel.Max().GetNode() == pNode )
2247             {
2248                 if ( ( static_cast<size_t>(aSel.Min().GetIndex()) > nInvEnd )
2249                         || ( ( aSel.Max().GetNode() == pLastNode ) && ( aSel.Max().GetIndex() >= pLastNode->Len() ) ) )
2250                     break;  // Document end or end of invalid region
2251 
2252                 aSel = SelectWord( aSel, i18n::WordType::DICTIONARY_WORD );
2253                 // If afterwards a dot, this must be handed over!
2254                 // If an abbreviation ...
2255                 bool bDottAdded = false;
2256                 if ( aSel.Max().GetIndex() < aSel.Max().GetNode()->Len() )
2257                 {
2258                     sal_Unicode cNext = aSel.Max().GetNode()->GetChar( aSel.Max().GetIndex() );
2259                     if ( cNext == '.' )
2260                     {
2261                         aSel.Max().SetIndex( aSel.Max().GetIndex()+1 );
2262                         bDottAdded = true;
2263                     }
2264                 }
2265                 OUString aWord = GetSelected(aSel);
2266 
2267                 bool bChanged = false;
2268                 if (!aWord.isEmpty())
2269                 {
2270                     const sal_Int32 nWStart = aSel.Min().GetIndex();
2271                     const sal_Int32 nWEnd = aSel.Max().GetIndex();
2272                     if ( !xSpeller->isValid( aWord, static_cast<sal_uInt16>(GetLanguage( EditPaM( aSel.Min().GetNode(), nWStart+1 ) )), aEmptySeq ) )
2273                     {
2274                         // Check if already marked correctly...
2275                         const sal_Int32 nXEnd = bDottAdded ? nWEnd -1 : nWEnd;
2276                         if ( !pWrongList->HasWrong( nWStart, nXEnd ) )
2277                         {
2278                             // Mark Word as wrong...
2279                             // But only when not at Cursor-Position...
2280                             bool bCursorPos = false;
2281                             if ( aCursorPos.GetNode() == pNode )
2282                             {
2283                                 if ( ( nWStart <= aCursorPos.GetIndex() ) && nWEnd >= aCursorPos.GetIndex() )
2284                                     bCursorPos = true;
2285                             }
2286                             if ( bCursorPos )
2287                             {
2288                                 // Then continue to mark as invalid ...
2289                                 pWrongList->ResetInvalidRange(nWStart, nWEnd);
2290                                 bRestartTimer = true;
2291                             }
2292                             else
2293                             {
2294                                 // It may be that the Wrongs in the list ar not
2295                                 // spanning exactly over words because the
2296                                 // WordDelimiters during expansion are not
2297                                 // evaluated.
2298                                 pWrongList->InsertWrong(nWStart, nXEnd);
2299                                 bChanged = true;
2300                             }
2301                         }
2302                     }
2303                     else
2304                     {
2305                         // Check if not marked as wrong
2306                         if ( pWrongList->HasAnyWrong( nWStart, nWEnd ) )
2307                         {
2308                             pWrongList->ClearWrongs( nWStart, nWEnd, pNode );
2309                             bSimpleRepaint = false;
2310                             bChanged = true;
2311                         }
2312                     }
2313                     if ( bChanged  )
2314                     {
2315                         if ( nPaintFrom<0 )
2316                             nPaintFrom = nWStart;
2317                         nPaintTo = nWEnd;
2318                     }
2319                 }
2320 
2321                 EditPaM aLastEnd( aSel.Max() );
2322                 aSel = WordRight( aSel.Max(), i18n::WordType::DICTIONARY_WORD );
2323                 if ( bChanged && ( aSel.Min().GetNode() == pNode ) &&
2324                         ( aSel.Min().GetIndex()-aLastEnd.GetIndex() > 1 ) )
2325                 {
2326                     // If two words are separated by more than one blank, it
2327                     // can happen that when splitting a Wrongs the start of
2328                     // the second word is before the actually word
2329                     pWrongList->ClearWrongs( aLastEnd.GetIndex(), aSel.Min().GetIndex(), pNode );
2330                 }
2331             }
2332 
2333             // Invalidate?
2334             if ( nPaintFrom>=0 )
2335             {
2336                 aStatus.GetStatusWord() |= EditStatusFlags::WRONGWORDCHANGED;
2337                 CallStatusHdl();
2338 
2339                 if (!aEditViews.empty())
2340                 {
2341                     // For SimpleRepaint one was painted over a range without
2342                     // reaching VDEV, but then one would have to intersect, c
2343                     // clipping, ... over all views. Probably not worthwhile.
2344                     EditPaM aStartPaM( pNode, nPaintFrom );
2345                     EditPaM aEndPaM( pNode, nPaintTo );
2346                     tools::Rectangle aStartCursor( PaMtoEditCursor( aStartPaM ) );
2347                     tools::Rectangle aEndCursor( PaMtoEditCursor( aEndPaM ) );
2348                     DBG_ASSERT( aInvalidRect.IsEmpty(), "InvalidRect set!" );
2349                     aInvalidRect.SetLeft( 0 );
2350                     aInvalidRect.SetRight( GetPaperSize().Width() );
2351                     aInvalidRect.SetTop( aStartCursor.Top() );
2352                     aInvalidRect.SetBottom( aEndCursor.Bottom() );
2353                     if ( pActiveView && pActiveView->HasSelection() )
2354                     {
2355                         // Then no output through VDev.
2356                         UpdateViews();
2357                     }
2358                     else if ( bSimpleRepaint )
2359                     {
2360                         for (EditView* pView : aEditViews)
2361                         {
2362                             tools::Rectangle aClipRect( aInvalidRect );
2363                             aClipRect.Intersection( pView->GetVisArea() );
2364                             if ( !aClipRect.IsEmpty() )
2365                             {
2366                                 // convert to window coordinates ....
2367                                 aClipRect.SetPos( pView->pImpEditView->GetWindowPos( aClipRect.TopLeft() ) );
2368                                 pView->pImpEditView->GetWindow()->Invalidate(aClipRect);
2369                             }
2370                         }
2371                     }
2372                     else
2373                     {
2374                         UpdateViews( pActiveView );
2375                     }
2376                     aInvalidRect = tools::Rectangle();
2377                 }
2378             }
2379             // After two corrected nodes give up the control ...
2380             nInvalids++;
2381             if ( bInterruptible && ( nInvalids >= 2 ) )
2382             {
2383                 bRestartTimer = true;
2384                 break;
2385             }
2386         }
2387 
2388         if ( pThisNodeOnly )
2389             break;
2390     }
2391     if ( bRestartTimer )
2392         aOnlineSpellTimer.Start();
2393 }
2394 
2395 
2396 EESpellState ImpEditEngine::HasSpellErrors()
2397 {
2398     DBG_ASSERT( xSpeller.is(), "No spell checker set!" );
2399 
2400     ContentNode* pLastNode = aEditDoc.GetObject( aEditDoc.Count() - 1 );
2401     EditSelection aCurSel( aEditDoc.GetStartPaM() );
2402 
2403     OUString aWord;
2404     Reference< XSpellAlternatives > xSpellAlt;
2405     Sequence< PropertyValue > aEmptySeq;
2406     while ( !xSpellAlt.is() )
2407     {
2408         if ( ( aCurSel.Max().GetNode() == pLastNode ) &&
2409              ( aCurSel.Max().GetIndex() >= pLastNode->Len() ) )
2410         {
2411             return EESpellState::Ok;
2412         }
2413 
2414         aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD );
2415         aWord = GetSelected( aCurSel );
2416         if ( !aWord.isEmpty() )
2417         {
2418             LanguageType eLang = GetLanguage( aCurSel.Max() );
2419             SvxSpellWrapper::CheckSpellLang( xSpeller, eLang );
2420             xSpellAlt = xSpeller->spell( aWord, static_cast<sal_uInt16>(eLang), aEmptySeq );
2421         }
2422         aCurSel = WordRight( aCurSel.Max(), css::i18n::WordType::DICTIONARY_WORD );
2423     }
2424 
2425     return EESpellState::ErrorFound;
2426 }
2427 
2428 void ImpEditEngine::ClearSpellErrors()
2429 {
2430     aEditDoc.ClearSpellErrors();
2431 }
2432 
2433 EESpellState ImpEditEngine::StartThesaurus( EditView* pEditView )
2434 {
2435     EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() );
2436     if ( !aCurSel.HasRange() )
2437         aCurSel = SelectWord( aCurSel, css::i18n::WordType::DICTIONARY_WORD );
2438     OUString aWord( GetSelected( aCurSel ) );
2439 
2440     Reference< XThesaurus > xThes( LinguMgr::GetThesaurus() );
2441     if (!xThes.is())
2442         return EESpellState::ErrorFound;
2443 
2444     EditAbstractDialogFactory* pFact = EditAbstractDialogFactory::Create();
2445     ScopedVclPtr<AbstractThesaurusDialog> xDlg(pFact->CreateThesaurusDialog( pEditView->GetWindow(), xThes, aWord, GetLanguage( aCurSel.Max() ) ));
2446     if (xDlg->Execute() == RET_OK)
2447     {
2448         // Replace Word...
2449         pEditView->pImpEditView->DrawSelectionXOR();
2450         pEditView->pImpEditView->SetEditSelection( aCurSel );
2451         pEditView->pImpEditView->DrawSelectionXOR();
2452         pEditView->InsertText(xDlg->GetWord());
2453         pEditView->ShowCursor(true, false);
2454     }
2455 
2456     return EESpellState::Ok;
2457 }
2458 
2459 sal_Int32 ImpEditEngine::StartSearchAndReplace( EditView* pEditView, const SvxSearchItem& rSearchItem )
2460 {
2461     sal_Int32 nFound = 0;
2462 
2463     EditSelection aCurSel( pEditView->pImpEditView->GetEditSelection() );
2464 
2465     // FIND_ALL is not possible without multiple selection.
2466     if ( ( rSearchItem.GetCommand() == SvxSearchCmd::FIND ) ||
2467          ( rSearchItem.GetCommand() == SvxSearchCmd::FIND_ALL ) )
2468     {
2469         if ( Search( rSearchItem, pEditView ) )
2470             nFound++;
2471     }
2472     else if ( rSearchItem.GetCommand() == SvxSearchCmd::REPLACE )
2473     {
2474         // The word is selected if the user not altered the selection
2475         // in between:
2476         if ( aCurSel.HasRange() )
2477         {
2478             pEditView->InsertText( rSearchItem.GetReplaceString() );
2479             nFound = 1;
2480         }
2481         else
2482             if( Search( rSearchItem, pEditView ) )
2483                 nFound = 1;
2484     }
2485     else if ( rSearchItem.GetCommand() == SvxSearchCmd::REPLACE_ALL )
2486     {
2487         // The Writer replaces all front beginning to end ...
2488         SvxSearchItem aTmpItem( rSearchItem );
2489         aTmpItem.SetBackward( false );
2490 
2491         pEditView->pImpEditView->DrawSelectionXOR();
2492 
2493         aCurSel.Adjust( aEditDoc );
2494         EditPaM aStartPaM = aTmpItem.GetSelection() ? aCurSel.Min() : aEditDoc.GetStartPaM();
2495         EditSelection aFoundSel( aCurSel.Max() );
2496         bool bFound = ImpSearch( aTmpItem, aCurSel, aStartPaM, aFoundSel );
2497         if ( bFound )
2498             UndoActionStart( EDITUNDO_REPLACEALL );
2499         while ( bFound )
2500         {
2501             nFound++;
2502             aStartPaM = ImpInsertText( aFoundSel, rSearchItem.GetReplaceString() );
2503             bFound = ImpSearch( aTmpItem, aCurSel, aStartPaM, aFoundSel );
2504         }
2505         if ( nFound )
2506         {
2507             EditPaM aNewPaM( aFoundSel.Max() );
2508             if ( aNewPaM.GetIndex() > aNewPaM.GetNode()->Len() )
2509                 aNewPaM.SetIndex( aNewPaM.GetNode()->Len() );
2510             pEditView->pImpEditView->SetEditSelection( aNewPaM );
2511             FormatAndUpdate( pEditView );
2512             UndoActionEnd();
2513         }
2514         else
2515         {
2516             pEditView->pImpEditView->DrawSelectionXOR();
2517             pEditView->ShowCursor( true, false );
2518         }
2519     }
2520     return nFound;
2521 }
2522 
2523 bool ImpEditEngine::Search( const SvxSearchItem& rSearchItem, EditView* pEditView )
2524 {
2525     EditSelection aSel( pEditView->pImpEditView->GetEditSelection() );
2526     aSel.Adjust( aEditDoc );
2527     EditPaM aStartPaM( aSel.Max() );
2528     if ( rSearchItem.GetSelection() && !rSearchItem.GetBackward() )
2529         aStartPaM = aSel.Min();
2530 
2531     EditSelection aFoundSel;
2532     bool bFound = ImpSearch( rSearchItem, aSel, aStartPaM, aFoundSel );
2533     if ( bFound && ( aFoundSel == aSel ) )  // For backwards-search
2534     {
2535         aStartPaM = aSel.Min();
2536         bFound = ImpSearch( rSearchItem, aSel, aStartPaM, aFoundSel );
2537     }
2538 
2539     pEditView->pImpEditView->DrawSelectionXOR();
2540     if ( bFound )
2541     {
2542         // First, set the minimum, so the whole word is in the visible range.
2543         pEditView->pImpEditView->SetEditSelection( aFoundSel.Min() );
2544         pEditView->ShowCursor( true, false );
2545         pEditView->pImpEditView->SetEditSelection( aFoundSel );
2546     }
2547     else
2548         pEditView->pImpEditView->SetEditSelection( aSel.Max() );
2549 
2550     pEditView->pImpEditView->DrawSelectionXOR();
2551     pEditView->ShowCursor( true, false );
2552     return bFound;
2553 }
2554 
2555 bool ImpEditEngine::ImpSearch( const SvxSearchItem& rSearchItem,
2556     const EditSelection& rSearchSelection, const EditPaM& rStartPos, EditSelection& rFoundSel )
2557 {
2558     i18nutil::SearchOptions2 aSearchOptions( rSearchItem.GetSearchOptions() );
2559     aSearchOptions.Locale = GetLocale( rStartPos );
2560 
2561     bool bBack = rSearchItem.GetBackward();
2562     bool bSearchInSelection = rSearchItem.GetSelection();
2563     sal_Int32 nStartNode = aEditDoc.GetPos( rStartPos.GetNode() );
2564     sal_Int32 nEndNode;
2565     if ( bSearchInSelection )
2566     {
2567         nEndNode = aEditDoc.GetPos( bBack ? rSearchSelection.Min().GetNode() : rSearchSelection.Max().GetNode() );
2568     }
2569     else
2570     {
2571         nEndNode = bBack ? 0 : aEditDoc.Count()-1;
2572     }
2573 
2574     utl::TextSearch aSearcher( aSearchOptions );
2575 
2576     // iterate over the paragraphs ...
2577     for ( sal_Int32 nNode = nStartNode;
2578             bBack ? ( nNode >= nEndNode ) : ( nNode <= nEndNode) ;
2579             bBack ? nNode-- : nNode++ )
2580     {
2581         // For backwards-search if nEndNode = 0:
2582         if ( nNode < 0 )
2583             return false;
2584 
2585         ContentNode* pNode = aEditDoc.GetObject( nNode );
2586 
2587         sal_Int32 nStartPos = 0;
2588         sal_Int32 nEndPos = pNode->GetExpandedLen();
2589         if ( nNode == nStartNode )
2590         {
2591             if ( bBack )
2592                 nEndPos = rStartPos.GetIndex();
2593             else
2594                 nStartPos = rStartPos.GetIndex();
2595         }
2596         if ( ( nNode == nEndNode ) && bSearchInSelection )
2597         {
2598             if ( bBack )
2599                 nStartPos = rSearchSelection.Min().GetIndex();
2600             else
2601                 nEndPos = rSearchSelection.Max().GetIndex();
2602         }
2603 
2604         // Searching ...
2605         OUString aParaStr( pNode->GetExpandedText() );
2606         bool bFound = false;
2607         if ( bBack )
2608         {
2609             sal_Int32 nTemp;
2610             nTemp = nStartPos;
2611             nStartPos = nEndPos;
2612             nEndPos = nTemp;
2613 
2614             bFound = aSearcher.SearchBackward( aParaStr, &nStartPos, &nEndPos);
2615         }
2616         else
2617         {
2618             bFound = aSearcher.SearchForward( aParaStr, &nStartPos, &nEndPos);
2619         }
2620         if ( bFound )
2621         {
2622             pNode->UnExpandPositions( nStartPos, nEndPos );
2623 
2624             rFoundSel.Min().SetNode( pNode );
2625             rFoundSel.Min().SetIndex( nStartPos );
2626             rFoundSel.Max().SetNode( pNode );
2627             rFoundSel.Max().SetIndex( nEndPos );
2628             return true;
2629         }
2630     }
2631     return false;
2632 }
2633 
2634 bool ImpEditEngine::HasText( const SvxSearchItem& rSearchItem )
2635 {
2636     SvxSearchItem aTmpItem( rSearchItem );
2637     aTmpItem.SetBackward( false );
2638     aTmpItem.SetSelection( false );
2639 
2640     EditPaM aStartPaM( aEditDoc.GetStartPaM() );
2641     EditSelection aDummySel( aStartPaM );
2642     EditSelection aFoundSel;
2643     return ImpSearch( aTmpItem, aDummySel, aStartPaM, aFoundSel );
2644 }
2645 
2646 void ImpEditEngine::SetAutoCompleteText(const OUString& rStr, bool bClearTipWindow)
2647 {
2648     aAutoCompleteText = rStr;
2649     if ( bClearTipWindow && pActiveView )
2650         Help::ShowQuickHelp( pActiveView->GetWindow(), tools::Rectangle(), OUString() );
2651 }
2652 
2653 namespace
2654 {
2655     struct eeTransliterationChgData
2656     {
2657         sal_Int32                   nStart;
2658         sal_Int32                   nLen;
2659         EditSelection               aSelection;
2660         OUString                    aNewText;
2661         uno::Sequence< sal_Int32 >  aOffsets;
2662     };
2663 }
2664 
2665 EditSelection ImpEditEngine::TransliterateText( const EditSelection& rSelection, TransliterationFlags nTransliterationMode )
2666 {
2667     uno::Reference < i18n::XBreakIterator > _xBI( ImplGetBreakIterator() );
2668     if (!_xBI.is())
2669         return rSelection;
2670 
2671     EditSelection aSel( rSelection );
2672     aSel.Adjust( aEditDoc );
2673 
2674     if ( !aSel.HasRange() )
2675         aSel = SelectWord( aSel );
2676 
2677     // tdf#107176: if there's still no range, just return aSel
2678     if ( !aSel.HasRange() )
2679         return aSel;
2680 
2681     EditSelection aNewSel( aSel );
2682 
2683     const sal_Int32 nStartNode = aEditDoc.GetPos( aSel.Min().GetNode() );
2684     const sal_Int32 nEndNode = aEditDoc.GetPos( aSel.Max().GetNode() );
2685 
2686     bool bChanges = false;
2687     bool bLenChanged = false;
2688     std::unique_ptr<EditUndoTransliteration> pUndo;
2689 
2690     utl::TransliterationWrapper aTransliterationWrapper( ::comphelper::getProcessComponentContext(), nTransliterationMode );
2691     bool bConsiderLanguage = aTransliterationWrapper.needLanguageForTheMode();
2692 
2693     for ( sal_Int32 nNode = nStartNode; nNode <= nEndNode; nNode++ )
2694     {
2695         ContentNode* pNode = aEditDoc.GetObject( nNode );
2696         const OUString& aNodeStr = pNode->GetString();
2697         const sal_Int32 nStartPos = nNode==nStartNode ? aSel.Min().GetIndex() : 0;
2698         const sal_Int32 nEndPos = nNode==nEndNode ? aSel.Max().GetIndex() : aNodeStr.getLength(); // can also be == nStart!
2699 
2700         sal_Int32 nCurrentStart = nStartPos;
2701         sal_Int32 nCurrentEnd = nEndPos;
2702         LanguageType nLanguage = LANGUAGE_SYSTEM;
2703 
2704         // since we don't use Hiragana/Katakana or half-width/full-width transliterations here
2705         // it is fine to use ANYWORD_IGNOREWHITESPACES. (ANY_WORD btw is broken and will
2706         // occasionally miss words in consecutive sentences). Also with ANYWORD_IGNOREWHITESPACES
2707         // text like 'just-in-time' will be converted to 'Just-In-Time' which seems to be the
2708         // proper thing to do.
2709         const sal_Int16 nWordType = i18n::WordType::ANYWORD_IGNOREWHITESPACES;
2710 
2711         //! In order to have less trouble with changing text size, e.g. because
2712         //! of ligatures or German small sz being resolved, we need to process
2713         //! the text replacements from end to start.
2714         //! This way the offsets for the yet to be changed words will be
2715         //! left unchanged by the already replaced text.
2716         //! For this we temporarily save the changes to be done in this vector
2717         std::vector< eeTransliterationChgData >   aChanges;
2718         eeTransliterationChgData                  aChgData;
2719 
2720         if (nTransliterationMode == TransliterationFlags::TITLE_CASE)
2721         {
2722             // for 'capitalize every word' we need to iterate over each word
2723 
2724             i18n::Boundary aSttBndry;
2725             i18n::Boundary aEndBndry;
2726             aSttBndry = _xBI->getWordBoundary(
2727                         aNodeStr, nStartPos,
2728                         GetLocale( EditPaM( pNode, nStartPos + 1 ) ),
2729                         nWordType, true /*prefer forward direction*/);
2730             aEndBndry = _xBI->getWordBoundary(
2731                         aNodeStr, nEndPos,
2732                         GetLocale( EditPaM( pNode, nEndPos + 1 ) ),
2733                         nWordType, false /*prefer backward direction*/);
2734 
2735             // prevent backtracking to the previous word if selection is at word boundary
2736             if (aSttBndry.endPos <= nStartPos)
2737             {
2738                 aSttBndry = _xBI->nextWord(
2739                         aNodeStr, aSttBndry.endPos,
2740                         GetLocale( EditPaM( pNode, aSttBndry.endPos + 1 ) ),
2741                         nWordType);
2742             }
2743             // prevent advancing to the next word if selection is at word boundary
2744             if (aEndBndry.startPos >= nEndPos)
2745             {
2746                 aEndBndry = _xBI->previousWord(
2747                         aNodeStr, aEndBndry.startPos,
2748                         GetLocale( EditPaM( pNode, aEndBndry.startPos + 1 ) ),
2749                         nWordType);
2750             }
2751 
2752             i18n::Boundary aCurWordBndry( aSttBndry );
2753             while (aCurWordBndry.endPos && aCurWordBndry.startPos <= aEndBndry.startPos)
2754             {
2755                 nCurrentStart = aCurWordBndry.startPos;
2756                 nCurrentEnd   = aCurWordBndry.endPos;
2757                 sal_Int32 nLen = nCurrentEnd - nCurrentStart;
2758                 DBG_ASSERT( nLen > 0, "invalid word length of 0" );
2759 
2760                 Sequence< sal_Int32 > aOffsets;
2761                 OUString aNewText( aTransliterationWrapper.transliterate(aNodeStr,
2762                         GetLanguage( EditPaM( pNode, nCurrentStart + 1 ) ),
2763                         nCurrentStart, nLen, &aOffsets ));
2764 
2765                 if (aNodeStr != aNewText)
2766                 {
2767                     aChgData.nStart     = nCurrentStart;
2768                     aChgData.nLen       = nLen;
2769                     aChgData.aSelection = EditSelection( EditPaM( pNode, nCurrentStart ), EditPaM( pNode, nCurrentEnd ) );
2770                     aChgData.aNewText   = aNewText;
2771                     aChgData.aOffsets   = aOffsets;
2772                     aChanges.push_back( aChgData );
2773                 }
2774 #if OSL_DEBUG_LEVEL > 1
2775                 OUString aSelTxt ( GetSelected( aChgData.aSelection ) );
2776                 (void) aSelTxt;
2777 #endif
2778 
2779                 aCurWordBndry = _xBI->nextWord(aNodeStr, nCurrentStart,
2780                         GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ),
2781                         nWordType);
2782             }
2783             DBG_ASSERT( nCurrentEnd >= aEndBndry.endPos, "failed to reach end of transliteration" );
2784         }
2785         else if (nTransliterationMode == TransliterationFlags::SENTENCE_CASE)
2786         {
2787             // for 'sentence case' we need to iterate sentence by sentence
2788 
2789             sal_Int32 nLastStart = _xBI->beginOfSentence(
2790                     aNodeStr, nEndPos,
2791                     GetLocale( EditPaM( pNode, nEndPos + 1 ) ) );
2792             sal_Int32 nLastEnd = _xBI->endOfSentence(
2793                     aNodeStr, nLastStart,
2794                     GetLocale( EditPaM( pNode, nLastStart + 1 ) ) );
2795 
2796             // extend nCurrentStart, nCurrentEnd to the current sentence boundaries
2797             nCurrentStart = _xBI->beginOfSentence(
2798                     aNodeStr, nStartPos,
2799                     GetLocale( EditPaM( pNode, nStartPos + 1 ) ) );
2800             nCurrentEnd = _xBI->endOfSentence(
2801                     aNodeStr, nCurrentStart,
2802                     GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ) );
2803 
2804             // prevent backtracking to the previous sentence if selection starts at end of a sentence
2805             if (nCurrentEnd <= nStartPos)
2806             {
2807                 // now nCurrentStart is probably located on a non-letter word. (unless we
2808                 // are in Asian text with no spaces...)
2809                 // Thus to get the real sentence start we should locate the next real word,
2810                 // that is one found by DICTIONARY_WORD
2811                 i18n::Boundary aBndry = _xBI->nextWord( aNodeStr, nCurrentEnd,
2812                         GetLocale( EditPaM( pNode, nCurrentEnd + 1 ) ),
2813                         i18n::WordType::DICTIONARY_WORD);
2814 
2815                 // now get new current sentence boundaries
2816                 nCurrentStart = _xBI->beginOfSentence(
2817                         aNodeStr, aBndry.startPos,
2818                         GetLocale( EditPaM( pNode, aBndry.startPos + 1 ) ) );
2819                 nCurrentEnd = _xBI->endOfSentence(
2820                         aNodeStr, nCurrentStart,
2821                         GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ) );
2822             }
2823             // prevent advancing to the next sentence if selection ends at start of a sentence
2824             if (nLastStart >= nEndPos)
2825             {
2826                 // now nCurrentStart is probably located on a non-letter word. (unless we
2827                 // are in Asian text with no spaces...)
2828                 // Thus to get the real sentence start we should locate the previous real word,
2829                 // that is one found by DICTIONARY_WORD
2830                 i18n::Boundary aBndry = _xBI->previousWord( aNodeStr, nLastStart,
2831                         GetLocale( EditPaM( pNode, nLastStart + 1 ) ),
2832                         i18n::WordType::DICTIONARY_WORD);
2833                 nLastEnd = _xBI->endOfSentence(
2834                         aNodeStr, aBndry.startPos,
2835                         GetLocale( EditPaM( pNode, aBndry.startPos + 1 ) ) );
2836                 if (nCurrentEnd > nLastEnd)
2837                     nCurrentEnd = nLastEnd;
2838             }
2839 
2840             while (nCurrentStart < nLastEnd)
2841             {
2842                 const sal_Int32 nLen = nCurrentEnd - nCurrentStart;
2843                 DBG_ASSERT( nLen > 0, "invalid word length of 0" );
2844 
2845                 Sequence< sal_Int32 > aOffsets;
2846                 OUString aNewText( aTransliterationWrapper.transliterate( aNodeStr,
2847                         GetLanguage( EditPaM( pNode, nCurrentStart + 1 ) ),
2848                         nCurrentStart, nLen, &aOffsets ));
2849 
2850                 if (aNodeStr != aNewText)
2851                 {
2852                     aChgData.nStart     = nCurrentStart;
2853                     aChgData.nLen       = nLen;
2854                     aChgData.aSelection = EditSelection( EditPaM( pNode, nCurrentStart ), EditPaM( pNode, nCurrentEnd ) );
2855                     aChgData.aNewText   = aNewText;
2856                     aChgData.aOffsets   = aOffsets;
2857                     aChanges.push_back( aChgData );
2858                 }
2859 
2860                 i18n::Boundary aFirstWordBndry;
2861                 aFirstWordBndry = _xBI->nextWord(
2862                         aNodeStr, nCurrentEnd,
2863                         GetLocale( EditPaM( pNode, nCurrentEnd + 1 ) ),
2864                         nWordType);
2865                 nCurrentStart = aFirstWordBndry.startPos;
2866                 nCurrentEnd = _xBI->endOfSentence(
2867                         aNodeStr, nCurrentStart,
2868                         GetLocale( EditPaM( pNode, nCurrentStart + 1 ) ) );
2869             }
2870             DBG_ASSERT( nCurrentEnd >= nLastEnd, "failed to reach end of transliteration" );
2871         }
2872         else
2873         {
2874             do
2875             {
2876                 if ( bConsiderLanguage )
2877                 {
2878                     nLanguage = GetLanguage( EditPaM( pNode, nCurrentStart+1 ), &nCurrentEnd );
2879                     if ( nCurrentEnd > nEndPos )
2880                         nCurrentEnd = nEndPos;
2881                 }
2882 
2883                 const sal_Int32 nLen = nCurrentEnd - nCurrentStart;
2884 
2885                 Sequence< sal_Int32 > aOffsets;
2886                 OUString aNewText( aTransliterationWrapper.transliterate( aNodeStr, nLanguage, nCurrentStart, nLen, &aOffsets ) );
2887 
2888                 if (aNodeStr != aNewText)
2889                 {
2890                     aChgData.nStart     = nCurrentStart;
2891                     aChgData.nLen       = nLen;
2892                     aChgData.aSelection = EditSelection( EditPaM( pNode, nCurrentStart ), EditPaM( pNode, nCurrentEnd ) );
2893                     aChgData.aNewText   = aNewText;
2894                     aChgData.aOffsets   = aOffsets;
2895                     aChanges.push_back( aChgData );
2896                 }
2897 
2898                 nCurrentStart = nCurrentEnd;
2899             } while( nCurrentEnd < nEndPos );
2900         }
2901 
2902         if (!aChanges.empty())
2903         {
2904             // Create a single UndoAction on Demand for all the changes ...
2905             if ( !pUndo && IsUndoEnabled() && !IsInUndo() )
2906             {
2907                 // adjust selection to include all changes
2908                 for (eeTransliterationChgData & aChange : aChanges)
2909                 {
2910                     const EditSelection &rSel = aChange.aSelection;
2911                     if (aSel.Min().GetNode() == rSel.Min().GetNode() &&
2912                         aSel.Min().GetIndex() > rSel.Min().GetIndex())
2913                         aSel.Min().SetIndex( rSel.Min().GetIndex() );
2914                     if (aSel.Max().GetNode() == rSel.Max().GetNode() &&
2915                         aSel.Max().GetIndex() < rSel.Max().GetIndex())
2916                         aSel.Max().SetIndex( rSel.Max().GetIndex() );
2917                 }
2918                 aNewSel = aSel;
2919 
2920                 ESelection aESel( CreateESel( aSel ) );
2921                 pUndo.reset(new EditUndoTransliteration(pEditEngine, aESel, nTransliterationMode));
2922 
2923                 const bool bSingleNode = aSel.Min().GetNode()== aSel.Max().GetNode();
2924                 const bool bHasAttribs = aSel.Min().GetNode()->GetCharAttribs().HasAttrib( aSel.Min().GetIndex(), aSel.Max().GetIndex() );
2925                 if (bSingleNode && !bHasAttribs)
2926                     pUndo->SetText( aSel.Min().GetNode()->Copy( aSel.Min().GetIndex(), aSel.Max().GetIndex()-aSel.Min().GetIndex() ) );
2927                 else
2928                     pUndo->SetText( CreateTextObject( aSel, nullptr ) );
2929             }
2930 
2931             // now apply the changes from end to start to leave the offsets of the
2932             // yet unchanged text parts remain the same.
2933             for (size_t i = 0; i < aChanges.size(); ++i)
2934             {
2935                 eeTransliterationChgData& rData = aChanges[ aChanges.size() - 1 - i ];
2936 
2937                 bChanges = true;
2938                 if (rData.nLen != rData.aNewText.getLength())
2939                     bLenChanged = true;
2940 
2941                 // Change text without losing the attributes
2942                 const sal_Int32 nDiffs =
2943                     ReplaceTextOnly( rData.aSelection.Min().GetNode(),
2944                         rData.nStart, rData.aNewText, rData.aOffsets );
2945 
2946                 // adjust selection in end node to possibly changed size
2947                 if (aSel.Max().GetNode() == rData.aSelection.Max().GetNode())
2948                     aNewSel.Max().SetIndex( aNewSel.Max().GetIndex() + nDiffs );
2949 
2950                 sal_Int32 nSelNode = aEditDoc.GetPos( rData.aSelection.Min().GetNode() );
2951                 ParaPortion* pParaPortion = GetParaPortions()[nSelNode];
2952                 pParaPortion->MarkSelectionInvalid( rData.nStart );
2953             }
2954         }
2955     }
2956 
2957     if ( pUndo )
2958     {
2959         ESelection aESel( CreateESel( aNewSel ) );
2960         pUndo->SetNewSelection( aESel );
2961         InsertUndo( std::move(pUndo) );
2962     }
2963 
2964     if ( bChanges )
2965     {
2966         TextModified();
2967         SetModifyFlag( true );
2968         if ( bLenChanged )
2969             UpdateSelections();
2970         FormatAndUpdate();
2971     }
2972 
2973     return aNewSel;
2974 }
2975 
2976 
2977 short ImpEditEngine::ReplaceTextOnly(
2978     ContentNode* pNode,
2979     sal_Int32 nCurrentStart,
2980     const OUString& rNewText,
2981     const uno::Sequence< sal_Int32 >& rOffsets )
2982 {
2983     // Change text without losing the attributes
2984     sal_Int32 nCharsAfterTransliteration = rOffsets.getLength();
2985     const sal_Int32* pOffsets = rOffsets.getConstArray();
2986     short nDiffs = 0;
2987     for ( sal_Int32 n = 0; n < nCharsAfterTransliteration; n++ )
2988     {
2989         sal_Int32 nCurrentPos = nCurrentStart+n;
2990         sal_Int32 nDiff = (nCurrentPos-nDiffs) - pOffsets[n];
2991 
2992         if ( !nDiff )
2993         {
2994             DBG_ASSERT( nCurrentPos < pNode->Len(), "TransliterateText - String smaller than expected!" );
2995             pNode->SetChar( nCurrentPos, rNewText[n] );
2996         }
2997         else if ( nDiff < 0 )
2998         {
2999             // Replace first char, delete the rest...
3000             DBG_ASSERT( nCurrentPos < pNode->Len(), "TransliterateText - String smaller than expected!" );
3001             pNode->SetChar( nCurrentPos, rNewText[n] );
3002 
3003             DBG_ASSERT( (nCurrentPos+1) < pNode->Len(), "TransliterateText - String smaller than expected!" );
3004             GetEditDoc().RemoveChars( EditPaM( pNode, nCurrentPos+1 ), -nDiff);
3005         }
3006         else
3007         {
3008             DBG_ASSERT( nDiff == 1, "TransliterateText - Diff other than expected! But should work..." );
3009             GetEditDoc().InsertText( EditPaM( pNode, nCurrentPos ), OUString(rNewText[n]) );
3010 
3011         }
3012         nDiffs = sal::static_int_cast< short >(nDiffs + nDiff);
3013     }
3014 
3015     return nDiffs;
3016 }
3017 
3018 
3019 void ImpEditEngine::SetAsianCompressionMode( CharCompressType n )
3020 {
3021     if ( n != nAsianCompressionMode )
3022     {
3023         nAsianCompressionMode = n;
3024         if ( ImplHasText() )
3025         {
3026             FormatFullDoc();
3027             UpdateViews();
3028         }
3029     }
3030 }
3031 
3032 void ImpEditEngine::SetKernAsianPunctuation( bool b )
3033 {
3034     if ( b != bKernAsianPunctuation )
3035     {
3036         bKernAsianPunctuation = b;
3037         if ( ImplHasText() )
3038         {
3039             FormatFullDoc();
3040             UpdateViews();
3041         }
3042     }
3043 }
3044 
3045 void ImpEditEngine::SetAddExtLeading( bool bExtLeading )
3046 {
3047     if ( IsAddExtLeading() != bExtLeading )
3048     {
3049         bAddExtLeading = bExtLeading;
3050         if ( ImplHasText() )
3051         {
3052             FormatFullDoc();
3053             UpdateViews();
3054         }
3055     }
3056 };
3057 
3058 
3059 bool ImpEditEngine::ImplHasText() const
3060 {
3061     return ( ( GetEditDoc().Count() > 1 ) || GetEditDoc().GetObject(0)->Len() );
3062 }
3063 
3064 sal_Int32 ImpEditEngine::LogicToTwips(sal_Int32 n)
3065 {
3066     Size aSz(n, 0);
3067     MapMode aTwipsMode( MapUnit::MapTwip );
3068     aSz = pRefDev->LogicToLogic( aSz, nullptr, &aTwipsMode );
3069     return aSz.Width();
3070 }
3071 
3072 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
3073