xref: /core/sw/source/core/text/txtfld.cxx (revision 0203485a)
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 #include <hintids.hxx>
21 #include <fmtfld.hxx>
22 #include <txtfld.hxx>
23 #include <charfmt.hxx>
24 #include <fmtautofmt.hxx>
25 
26 #include <viewsh.hxx>
27 #include <doc.hxx>
28 #include <rootfrm.hxx>
29 #include <pagefrm.hxx>
30 #include <ndtxt.hxx>
31 #include <fldbas.hxx>
32 #include <viewopt.hxx>
33 #include <flyfrm.hxx>
34 #include <viewimp.hxx>
35 #include <swfont.hxx>
36 #include <swmodule.hxx>
37 #include "porfld.hxx"
38 #include "porftn.hxx"
39 #include "porref.hxx"
40 #include "portox.hxx"
41 #include "porfly.hxx"
42 #include "itrform2.hxx"
43 #include <chpfld.hxx>
44 #include <dbfld.hxx>
45 #include <expfld.hxx>
46 #include <docufld.hxx>
47 #include <pagedesc.hxx>
48 #include <fmtmeta.hxx>
49 #include <reffld.hxx>
50 #include <flddat.hxx>
51 #include <IDocumentSettingAccess.hxx>
52 #include <IDocumentRedlineAccess.hxx>
53 #include <redline.hxx>
54 #include <sfx2/docfile.hxx>
55 #include <svl/grabbagitem.hxx>
56 #include <svl/itemiter.hxx>
57 #include <svl/whiter.hxx>
58 #include <editeng/colritem.hxx>
59 #include <editeng/udlnitem.hxx>
60 #include <editeng/crossedoutitem.hxx>
61 
62 static bool lcl_IsInBody( SwFrame const *pFrame )
63 {
64     if ( pFrame->IsInDocBody() )
65         return true;
66     else
67     {
68         const SwFrame *pTmp = pFrame;
69         const SwFlyFrame *pFly;
70         while ( nullptr != (pFly = pTmp->FindFlyFrame()) )
71             pTmp = pFly->GetAnchorFrame();
72         return pTmp->IsInDocBody();
73     }
74 }
75 
76 SwExpandPortion *SwTextFormatter::NewFieldPortion( SwTextFormatInfo &rInf,
77                                                 const SwTextAttr *pHint ) const
78 {
79     SwExpandPortion *pRet = nullptr;
80     SwFrame *pFrame = m_pFrame;
81     SwField *pField = const_cast<SwField*>(pHint->GetFormatField().GetField());
82     const bool bName = rInf.GetOpt().IsFieldName();
83 
84     SwCharFormat* pChFormat = nullptr;
85     bool bNewFlyPor = false;
86     sal_uInt16 subType = 0;
87 
88     // set language
89     const_cast<SwTextFormatter*>(this)->SeekAndChg( rInf );
90     if (pField->GetLanguage() != GetFnt()->GetLanguage())
91     {
92         pField->SetLanguage( GetFnt()->GetLanguage() );
93         // let the visual note know about its new language
94         if (pField->GetTyp()->Which()==SwFieldIds::Postit)
95             const_cast<SwFormatField*> (&pHint->GetFormatField())->Broadcast( SwFormatFieldHint( &pHint->GetFormatField(), SwFormatFieldHintWhich::LANGUAGE ) );
96     }
97 
98     SwViewShell *pSh = rInf.GetVsh();
99     SwDoc *const pDoc( pSh ? pSh->GetDoc() : nullptr );
100     bool const bInClipboard( pDoc == nullptr || pDoc->IsClipBoard() );
101     bool bPlaceHolder = false;
102 
103     switch( pField->GetTyp()->Which() )
104     {
105         case SwFieldIds::Script:
106         case SwFieldIds::Postit:
107             pRet = new SwPostItsPortion( SwFieldIds::Script == pField->GetTyp()->Which() );
108             break;
109 
110         case SwFieldIds::CombinedChars:
111             {
112                 if( bName )
113                     pRet = new SwFieldPortion( pField->GetFieldName() );
114                 else
115                     pRet = new SwCombinedPortion( pField->ExpandField(bInClipboard, pFrame->getRootFrame()) );
116             }
117             break;
118 
119         case SwFieldIds::HiddenText:
120             {
121                 OUString const aStr( bName
122                     ? pField->GetFieldName()
123                     : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) );
124                 pRet = new SwHiddenPortion(aStr);
125             }
126             break;
127 
128         case SwFieldIds::Chapter:
129             if( !bName && pSh && !pSh->Imp()->IsUpdateExpFields() )
130             {
131                 static_cast<SwChapterField*>(pField)->ChangeExpansion(*pFrame,
132                     &static_txtattr_cast<SwTextField const*>(pHint)->GetTextNode());
133             }
134             {
135                 OUString const aStr( bName
136                     ? pField->GetFieldName()
137                     : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) );
138                 pRet = new SwFieldPortion( aStr );
139             }
140             break;
141 
142         case SwFieldIds::DocStat:
143             if( !bName && pSh && !pSh->Imp()->IsUpdateExpFields() )
144             {
145                 static_cast<SwDocStatField*>(pField)->ChangeExpansion( pFrame );
146             }
147             {
148                 OUString const aStr( bName
149                     ? pField->GetFieldName()
150                     : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) );
151                 pRet = new SwFieldPortion( aStr );
152             }
153             static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType= ATTR_PAGECOUNTFLD;
154             break;
155 
156         case SwFieldIds::PageNumber:
157         {
158             if( !bName && pSh && pSh->GetLayout() && !pSh->Imp()->IsUpdateExpFields() )
159             {
160                 SwPageNumberFieldType *pPageNr = static_cast<SwPageNumberFieldType *>(pField->GetTyp());
161 
162                 const SwRootFrame* pTmpRootFrame = pSh->GetLayout();
163                 const bool bVirt = pTmpRootFrame->IsVirtPageNum();
164 
165                 sal_uInt16 nVirtNum = pFrame->GetVirtPageNum();
166                 sal_uInt16 nNumPages = pTmpRootFrame->GetPageNum();
167                 SvxNumType nNumFormat = SvxNumType(-1);
168                 if(SVX_NUM_PAGEDESC == pField->GetFormat())
169                     nNumFormat = pFrame->FindPageFrame()->GetPageDesc()->GetNumType().GetNumberingType();
170                 static_cast<SwPageNumberField*>(pField)
171                     ->ChangeExpansion(nVirtNum, nNumPages);
172                 pPageNr->ChangeExpansion(pDoc,
173                                             bVirt, nNumFormat != SvxNumType(-1) ? &nNumFormat : nullptr);
174             }
175             {
176                 OUString const aStr( bName
177                     ? pField->GetFieldName()
178                     : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) );
179                 pRet = new SwFieldPortion( aStr );
180             }
181             static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType= ATTR_PAGENUMBERFLD;
182             break;
183         }
184         case SwFieldIds::GetExp:
185         {
186             if( !bName && pSh && !pSh->Imp()->IsUpdateExpFields() )
187             {
188                 SwGetExpField* pExpField = static_cast<SwGetExpField*>(pField);
189                 if( !::lcl_IsInBody( pFrame ) )
190                 {
191                     pExpField->ChgBodyTextFlag( false );
192                     pExpField->ChangeExpansion(*pFrame,
193                             *static_txtattr_cast<SwTextField const*>(pHint));
194                 }
195                 else if( !pExpField->IsInBodyText() )
196                 {
197                     // Was something else previously, thus: expand first, then convert it!
198                     pExpField->ChangeExpansion(*pFrame,
199                             *static_txtattr_cast<SwTextField const*>(pHint));
200                     pExpField->ChgBodyTextFlag( true );
201                 }
202             }
203             {
204                 OUString const aStr( bName
205                     ? pField->GetFieldName()
206                     : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) );
207                 pRet = new SwFieldPortion( aStr );
208             }
209             break;
210         }
211         case SwFieldIds::Database:
212         {
213             if( !bName )
214             {
215                 SwDBField* pDBField = static_cast<SwDBField*>(pField);
216                 pDBField->ChgBodyTextFlag( ::lcl_IsInBody( pFrame ) );
217             }
218             {
219                 OUString const aStr( bName
220                     ? pField->GetFieldName()
221                     : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) );
222                 pRet = new SwFieldPortion(aStr);
223             }
224             break;
225         }
226         case SwFieldIds::RefPageGet:
227             if( !bName && pSh && !pSh->Imp()->IsUpdateExpFields() )
228             {
229                 static_cast<SwRefPageGetField*>(pField)->ChangeExpansion(*pFrame,
230                         static_txtattr_cast<SwTextField const*>(pHint));
231             }
232             {
233                 OUString const aStr( bName
234                     ? pField->GetFieldName()
235                     : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) );
236                 pRet = new SwFieldPortion(aStr);
237             }
238             break;
239 
240         case SwFieldIds::JumpEdit:
241             if( !bName )
242                 pChFormat = static_cast<SwJumpEditField*>(pField)->GetCharFormat();
243             bNewFlyPor = true;
244             bPlaceHolder = true;
245             break;
246         case SwFieldIds::GetRef:
247             subType = static_cast<SwGetRefField*>(pField)->GetSubType();
248             {
249                 OUString const str( bName
250                     ? pField->GetFieldName()
251                     : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) );
252                 pRet = new SwFieldPortion(str);
253             }
254             if( subType == REF_BOOKMARK  )
255                 static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType = ATTR_BOOKMARKFLD;
256             else if( subType == REF_SETREFATTR )
257                 static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType = ATTR_SETREFATTRFLD;
258             break;
259         case SwFieldIds::DateTime:
260             subType = static_cast<SwDateTimeField*>(pField)->GetSubType();
261             {
262                 OUString const str( bName
263                     ? pField->GetFieldName()
264                     : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) );
265                 pRet = new SwFieldPortion(str);
266             }
267             if( subType & DATEFLD  )
268                 static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType= ATTR_DATEFLD;
269             else if( subType & TIMEFLD )
270                 static_cast<SwFieldPortion*>(pRet)->m_nAttrFieldType = ATTR_TIMEFLD;
271             break;
272         default:
273             {
274                 OUString const aStr( bName
275                     ? pField->GetFieldName()
276                     : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) );
277                 pRet = new SwFieldPortion(aStr);
278             }
279     }
280 
281     if( bNewFlyPor )
282     {
283         std::unique_ptr<SwFont> pTmpFnt;
284         if( !bName )
285         {
286             pTmpFnt.reset(new SwFont( *m_pFont ));
287             pTmpFnt->SetDiffFnt(&pChFormat->GetAttrSet(), &m_pFrame->GetDoc().getIDocumentSettingAccess());
288         }
289         OUString const aStr( bName
290             ? pField->GetFieldName()
291             : pField->ExpandField(bInClipboard, pFrame->getRootFrame()) );
292         pRet = new SwFieldPortion(aStr, std::move(pTmpFnt), bPlaceHolder);
293     }
294 
295     return pRet;
296 }
297 
298 static SwFieldPortion * lcl_NewMetaPortion(SwTextAttr & rHint, const bool bPrefix)
299 {
300     ::sw::Meta *const pMeta(
301         static_cast<SwFormatMeta &>(rHint.GetAttr()).GetMeta() );
302     OUString fix;
303     ::sw::MetaField *const pField( dynamic_cast< ::sw::MetaField * >(pMeta) );
304     OSL_ENSURE(pField, "lcl_NewMetaPortion: no meta field?");
305     if (pField)
306     {
307         OUString color;
308         pField->GetPrefixAndSuffix(bPrefix ? &fix : nullptr, bPrefix ? nullptr : &fix, &color);
309     }
310     return new SwFieldPortion( fix );
311 }
312 
313 /**
314  * Try to create a new portion with zero length, for an end of a hint
315  * (where there is no CH_TXTATR). Because there may be multiple hint ends at a
316  * given index, m_pByEndIter is used to keep track of the already created
317  * portions. But the portions created here may actually be deleted again,
318  * due to Underflow. In that case, m_pByEndIter must be decremented,
319  * so the portion will be created again on the next line.
320  */
321 SwExpandPortion * SwTextFormatter::TryNewNoLengthPortion(SwTextFormatInfo const & rInfo)
322 {
323     const TextFrameIndex nIdx(rInfo.GetIdx());
324 
325     // sw_redlinehide: because there is a dummy character at the start of these
326     // hints, it's impossible to have ends of hints from different nodes at the
327     // same view position, so it's sufficient to check the hints of the current
328     // node.  However, m_pByEndIter exists for the whole text frame, so
329     // it's necessary to iterate all hints for that purpose...
330     if (!m_pByEndIter)
331     {
332         m_pByEndIter.reset(new sw::MergedAttrIterByEnd(*rInfo.GetTextFrame()));
333     }
334     SwTextNode const* pNode(nullptr);
335     for (SwTextAttr const* pHint = m_pByEndIter->NextAttr(pNode); pHint;
336          pHint = m_pByEndIter->NextAttr(pNode))
337     {
338         SwTextAttr & rHint(const_cast<SwTextAttr&>(*pHint));
339         TextFrameIndex const nEnd(
340             rInfo.GetTextFrame()->MapModelToView(pNode, rHint.GetAnyEnd()));
341         if (nEnd > nIdx)
342         {
343             m_pByEndIter->PrevAttr();
344             break;
345         }
346         if (nEnd == nIdx)
347         {
348             if (RES_TXTATR_METAFIELD == rHint.Which())
349             {
350                 SwFieldPortion *const pPortion(
351                         lcl_NewMetaPortion(rHint, false));
352                 pPortion->SetNoLength(); // no CH_TXTATR at hint end!
353                 return pPortion;
354             }
355         }
356     }
357     return nullptr;
358 }
359 
360 SwLinePortion *SwTextFormatter::NewExtraPortion( SwTextFormatInfo &rInf )
361 {
362     SwTextAttr *pHint = GetAttr( rInf.GetIdx() );
363     SwLinePortion *pRet = nullptr;
364     if( !pHint )
365     {
366         pRet = new SwTextPortion;
367         pRet->SetLen(TextFrameIndex(1));
368         rInf.SetLen(TextFrameIndex(1));
369         return pRet;
370     }
371 
372     switch( pHint->Which() )
373     {
374     case RES_TXTATR_FLYCNT :
375         {
376             pRet = NewFlyCntPortion( rInf, pHint );
377             break;
378         }
379     case RES_TXTATR_FTN :
380         {
381             pRet = NewFootnotePortion( rInf, pHint );
382             break;
383         }
384     case RES_TXTATR_FIELD :
385     case RES_TXTATR_ANNOTATION :
386         {
387             pRet = NewFieldPortion( rInf, pHint );
388             break;
389         }
390     case RES_TXTATR_REFMARK :
391         {
392             pRet = new SwIsoRefPortion;
393             break;
394         }
395     case RES_TXTATR_TOXMARK :
396         {
397             pRet = new SwIsoToxPortion;
398             break;
399         }
400     case RES_TXTATR_METAFIELD:
401         {
402             pRet = lcl_NewMetaPortion( *pHint, true );
403             break;
404         }
405     default: ;
406     }
407     if( !pRet )
408     {
409         auto pFieldPortion = new SwFieldPortion( "" );
410         if (pHint->Which() == RES_TXTATR_CONTENTCONTROL)
411         {
412             pFieldPortion->SetContentControl(true);
413         }
414         pRet = pFieldPortion;
415         rInf.SetLen(TextFrameIndex(1));
416     }
417     return pRet;
418 }
419 
420 /**
421  * OOXML spec says that w:rPr inside w:pPr specifies formatting for the paragraph mark symbol (i.e. the control
422  * character than can be configured to be shown). However, in practice MSO also uses it as direct formatting
423  * for numbering in that paragraph. I don't know if the problem is in the spec or in MSWord.
424  */
425 static void checkApplyParagraphMarkFormatToNumbering(SwFont* pNumFnt, SwTextFormatInfo& rInf,
426                                                      const IDocumentSettingAccess* pIDSA,
427                                                      const SwAttrSet* pFormat)
428 {
429     if( !pIDSA->get(DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_NUMBERING ))
430         return;
431 
432     SwFormatAutoFormat const& rListAutoFormat(rInf.GetTextFrame()->GetTextNodeForParaProps()->GetAttr(RES_PARATR_LIST_AUTOFMT));
433     std::shared_ptr<SfxItemSet> pSet(rListAutoFormat.GetStyleHandle());
434 
435     // TODO remove this fallback (for WW8/RTF)
436     bool isDOCX = pIDSA->get(DocumentSettingId::ADD_VERTICAL_FLY_OFFSETS);
437     if (!isDOCX && !pSet)
438     {
439         TextFrameIndex const nTextLen(rInf.GetTextFrame()->GetText().getLength());
440         SwTextNode const* pNode(nullptr);
441         sw::MergedAttrIterReverse iter(*rInf.GetTextFrame());
442         for (SwTextAttr const* pHint = iter.PrevAttr(&pNode); pHint;
443              pHint = iter.PrevAttr(&pNode))
444         {
445             TextFrameIndex const nHintEnd(
446                 rInf.GetTextFrame()->MapModelToView(pNode, pHint->GetAnyEnd()));
447             if (nHintEnd < nTextLen)
448             {
449                 break; // only those at para end are interesting
450             }
451             // Formatting for the paragraph mark is usually set to apply only to the
452             // (non-existent) extra character at end of the text node, but there can be
453             // other hints too (ending at nTextLen), so look for all matching hints.
454             // Still the (non-existent) extra character at the end is preferred if it exists.
455             if (pHint->Which() == RES_TXTATR_AUTOFMT)
456             {
457                 pSet = pHint->GetAutoFormat().GetStyleHandle();
458                 // When we find an empty hint (start == end) we got what we are looking for.
459                 if (pHint->GetStart() == *pHint->End())
460                     break;
461             }
462         }
463     }
464 
465     // Check each item and in case it should be ignored, then clear it.
466     if (!pSet)
467         return;
468 
469     std::unique_ptr<SfxItemSet> const pCleanedSet = pSet->Clone();
470 
471     if (pCleanedSet->HasItem(RES_TXTATR_CHARFMT))
472     {
473         // Insert attributes of referenced char format into current set
474         const SwFormatCharFormat& rCharFormat = pCleanedSet->Get(RES_TXTATR_CHARFMT);
475         const SwAttrSet& rStyleAttrs = static_cast<const SwCharFormat *>(rCharFormat.GetRegisteredIn())->GetAttrSet();
476         SfxWhichIter aIter(rStyleAttrs);
477         sal_uInt16 nWhich = aIter.FirstWhich();
478         while (nWhich)
479         {
480             if (!SwTextNode::IsIgnoredCharFormatForNumbering(nWhich, /*bIsCharStyle=*/true)
481                 && !pCleanedSet->HasItem(nWhich)
482                 && !(pFormat && pFormat->HasItem(nWhich)) )
483             {
484                 // Copy from parent sets only allowed items which will not overwrite
485                 // values explicitly defined in current set (pCleanedSet) or in pFormat
486                 if (const SfxPoolItem* pItem = rStyleAttrs.GetItem(nWhich, true))
487                     pCleanedSet->Put(*pItem);
488             }
489             nWhich = aIter.NextWhich();
490         }
491 
492         // It is not required here anymore, all referenced items are inserted
493         pCleanedSet->ClearItem(RES_TXTATR_CHARFMT);
494     };
495 
496     SfxItemIter aIter(*pSet);
497     const SfxPoolItem* pItem = aIter.GetCurItem();
498     while (pItem)
499     {
500         if (SwTextNode::IsIgnoredCharFormatForNumbering(pItem->Which()))
501             pCleanedSet->ClearItem(pItem->Which());
502         else if (pFormat && pFormat->HasItem(pItem->Which()))
503             pCleanedSet->ClearItem(pItem->Which());
504         else if (pItem->Which() == RES_CHRATR_BACKGROUND)
505         {
506             bool bShadingWasImported = false;
507             // If Shading was imported, it should not be converted to a Highlight,
508             // but remain as Shading which is ignored for numbering.
509             if (pCleanedSet->HasItem(RES_CHRATR_GRABBAG))
510             {
511                 SfxGrabBagItem aGrabBag = pCleanedSet->Get(RES_CHRATR_GRABBAG, /*bSrchInParent=*/false);
512                 std::map<OUString, css::uno::Any>& rMap = aGrabBag.GetGrabBag();
513                 auto aIterator = rMap.find("CharShadingMarker");
514                 if (aIterator != rMap.end())
515                     aIterator->second >>= bShadingWasImported;
516             }
517 
518             // If used, BACKGROUND is converted to HIGHLIGHT. So also ignore if a highlight already exists.
519             if (bShadingWasImported
520                 || pCleanedSet->HasItem(RES_CHRATR_HIGHLIGHT)
521                 || (pFormat && pFormat->HasItem(RES_CHRATR_HIGHLIGHT)))
522             {
523                 pCleanedSet->ClearItem(pItem->Which());
524             }
525         }
526         pItem = aIter.NextItem();
527     };
528 
529     // SetDiffFnt resets the background color (why?), so capture it and re-apply if it had a value,
530     // because an existing value should override anything inherited from the paragraph marker.
531     const std::optional<Color> oFontBackColor = pNumFnt->GetBackColor();
532     // The same is true for the highlight color.
533     const Color aHighlight = pNumFnt->GetHighlightColor();
534 
535     pNumFnt->SetDiffFnt(pCleanedSet.get(), pIDSA);
536 
537     if (oFontBackColor)
538         pNumFnt->SetBackColor(oFontBackColor);
539     if (aHighlight != COL_TRANSPARENT)
540         pNumFnt->SetHighlightColor(aHighlight);
541 }
542 
543 static const SwRangeRedline* lcl_GetRedlineAtNodeInsertionOrDeletion( const SwTextNode& rTextNode,
544         bool& bIsMoved )
545 {
546     const SwDoc& rDoc = rTextNode.GetDoc();
547     SwRedlineTable::size_type nRedlPos = rDoc.getIDocumentRedlineAccess().GetRedlinePos( rTextNode, RedlineType::Any );
548 
549     if( SwRedlineTable::npos != nRedlPos )
550     {
551         const SwNodeOffset nNdIdx = rTextNode.GetIndex();
552         const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
553         for( ; nRedlPos < rTable.size() ; ++nRedlPos )
554         {
555             const SwRangeRedline* pTmp = rTable[ nRedlPos ];
556             if( RedlineType::Delete == pTmp->GetType() ||
557                 RedlineType::Insert == pTmp->GetType() )
558             {
559                 const SwPosition *pRStt = pTmp->Start(), *pREnd = pTmp->End();
560                 if( pRStt->nNode <= nNdIdx && pREnd->nNode > nNdIdx )
561                 {
562                     bIsMoved = pTmp->IsMoved();
563                     return pTmp;
564                 }
565             }
566         }
567     }
568     return nullptr;
569 }
570 
571 static bool lcl_setRedlineAttr( SwTextFormatInfo &rInf, const SwTextNode& rTextNode, const std::unique_ptr<SwFont>& pNumFnt )
572 {
573     if ( rInf.GetVsh()->GetLayout()->IsHideRedlines() )
574         return false;
575 
576     bool bIsMoved;
577     const SwRangeRedline* pRedlineNum = lcl_GetRedlineAtNodeInsertionOrDeletion( rTextNode, bIsMoved );
578     if (!pRedlineNum)
579         return false;
580 
581     // moved text: dark green with double underline or strikethrough
582     if ( bIsMoved )
583     {
584         pNumFnt->SetColor(COL_GREEN);
585         if ( RedlineType::Delete == pRedlineNum->GetType() )
586             pNumFnt->SetStrikeout(STRIKEOUT_DOUBLE);
587         else
588             pNumFnt->SetUnderline(LINESTYLE_DOUBLE);
589         return true;
590     }
591 
592     SwAttrPool& rPool = rInf.GetVsh()->GetDoc()->GetAttrPool();
593     SfxItemSetFixed<RES_CHRATR_BEGIN, RES_CHRATR_END-1> aSet(rPool);
594 
595     std::size_t aAuthor = (1 < pRedlineNum->GetStackCount())
596             ? pRedlineNum->GetAuthor( 1 )
597             : pRedlineNum->GetAuthor();
598 
599     if ( RedlineType::Delete == pRedlineNum->GetType() )
600         SW_MOD()->GetDeletedAuthorAttr(aAuthor, aSet);
601     else
602         SW_MOD()->GetInsertAuthorAttr(aAuthor, aSet);
603 
604     if (const SvxColorItem* pItem = aSet.GetItemIfSet(RES_CHRATR_COLOR))
605         pNumFnt->SetColor(pItem->GetValue());
606     if (const SvxUnderlineItem* pItem = aSet.GetItemIfSet(RES_CHRATR_UNDERLINE))
607         pNumFnt->SetUnderline(pItem->GetLineStyle());
608     if (const SvxCrossedOutItem* pItem = aSet.GetItemIfSet(RES_CHRATR_CROSSEDOUT))
609         pNumFnt->SetStrikeout( pItem->GetStrikeout() );
610 
611     return true;
612 }
613 
614 SwNumberPortion *SwTextFormatter::NewNumberPortion( SwTextFormatInfo &rInf ) const
615 {
616     if( rInf.IsNumDone() || rInf.GetTextStart() != m_nStart
617                 || rInf.GetTextStart() != rInf.GetIdx() )
618         return nullptr;
619 
620     SwNumberPortion *pRet = nullptr;
621     // sw_redlinehide: at this point it's certain that pTextNd is the node with
622     // the numbering of the frame; only the actual number-vector (GetNumString)
623     // depends on the hide-mode in the layout so other calls don't need to care
624     const SwTextNode *const pTextNd = GetTextFrame()->GetTextNodeForParaProps();
625     const SwNumRule* pNumRule = pTextNd->GetNumRule();
626 
627     // Has a "valid" number?
628     // sw_redlinehide: check that pParaPropsNode is the correct one
629     assert(pTextNd->IsNumbered(m_pFrame->getRootFrame()) == pTextNd->IsNumbered(nullptr));
630     if (pTextNd->IsNumbered(m_pFrame->getRootFrame()) && pTextNd->IsCountedInList())
631     {
632         int nLevel = pTextNd->GetActualListLevel();
633 
634         if (nLevel < 0)
635             nLevel = 0;
636 
637         if (nLevel >= MAXLEVEL)
638             nLevel = MAXLEVEL - 1;
639 
640         const SwNumFormat &rNumFormat = pNumRule->Get( nLevel );
641         const bool bLeft = SvxAdjust::Left == rNumFormat.GetNumAdjust();
642         const bool bCenter = SvxAdjust::Center == rNumFormat.GetNumAdjust();
643         const bool bLabelAlignmentPosAndSpaceModeActive(
644                 rNumFormat.GetPositionAndSpaceMode() == SvxNumberFormat::LABEL_ALIGNMENT );
645         const sal_uInt16 nMinDist = bLabelAlignmentPosAndSpaceModeActive
646                                 ? 0 : rNumFormat.GetCharTextDistance();
647 
648         if( SVX_NUM_BITMAP == rNumFormat.GetNumberingType() )
649         {
650             OUString referer;
651             if (auto const sh1 = rInf.GetVsh()) {
652                 if (auto const doc = sh1->GetDoc()) {
653                     auto const sh2 = doc->GetPersist();
654                     if (sh2 != nullptr && sh2->HasName()) {
655                         referer = sh2->GetMedium()->GetName();
656                     }
657                 }
658             }
659             pRet = new SwGrfNumPortion( pTextNd->GetLabelFollowedBy(),
660                                         rNumFormat.GetBrush(), referer,
661                                         rNumFormat.GetGraphicOrientation(),
662                                         rNumFormat.GetGraphicSize(),
663                                         bLeft, bCenter, nMinDist,
664                                         bLabelAlignmentPosAndSpaceModeActive );
665             tools::Long nTmpA = rInf.GetLast()->GetAscent();
666             tools::Long nTmpD = rInf.GetLast()->Height() - nTmpA;
667             if( !rInf.IsTest() )
668                 static_cast<SwGrfNumPortion*>(pRet)->SetBase( nTmpA, nTmpD, nTmpA, nTmpD );
669         }
670         else
671         {
672             // The SwFont is created dynamically and passed in the ctor,
673             // as the CharFormat only returns an SV-Font.
674             // In the dtor of SwNumberPortion, the SwFont is deleted.
675             const SwAttrSet* pFormat = rNumFormat.GetCharFormat() ?
676                                     &rNumFormat.GetCharFormat()->GetAttrSet() :
677                                     nullptr;
678             const IDocumentSettingAccess* pIDSA = pTextNd->getIDocumentSettingAccess();
679 
680             if( SVX_NUM_CHAR_SPECIAL == rNumFormat.GetNumberingType() )
681             {
682                 const std::optional<vcl::Font> pFormatFnt = rNumFormat.GetBulletFont();
683 
684                 // Build a new bullet font basing on the current paragraph font:
685                 std::unique_ptr<SwFont> pNumFnt(new SwFont( &rInf.GetCharAttr(), pIDSA ));
686 
687                 // #i53199#
688                 if ( !pIDSA->get(DocumentSettingId::DO_NOT_RESET_PARA_ATTRS_FOR_NUM_FONT) )
689                 {
690                     // i18463:
691                     // Underline style of paragraph font should not be considered
692                     // Overline style of paragraph font should not be considered
693                     // Weight style of paragraph font should not be considered
694                     // Posture style of paragraph font should not be considered
695                     pNumFnt->SetUnderline( LINESTYLE_NONE );
696                     pNumFnt->SetOverline( LINESTYLE_NONE );
697                     pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::Latin );
698                     pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::CJK );
699                     pNumFnt->SetItalic( ITALIC_NONE, SwFontScript::CTL );
700                     pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::Latin );
701                     pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::CJK );
702                     pNumFnt->SetWeight( WEIGHT_NORMAL, SwFontScript::CTL );
703                 }
704 
705                 // Apply the explicit attributes from the character style
706                 // associated with the numbering to the new bullet font.
707                 if( pFormat )
708                     pNumFnt->SetDiffFnt( pFormat, pIDSA );
709 
710                 checkApplyParagraphMarkFormatToNumbering(pNumFnt.get(), rInf, pIDSA, pFormat);
711 
712                 if ( pFormatFnt )
713                 {
714                     const SwFontScript nAct = pNumFnt->GetActual();
715                     pNumFnt->SetFamily( pFormatFnt->GetFamilyType(), nAct );
716                     pNumFnt->SetName( pFormatFnt->GetFamilyName(), nAct );
717                     pNumFnt->SetStyleName( pFormatFnt->GetStyleName(), nAct );
718                     pNumFnt->SetCharSet( pFormatFnt->GetCharSet(), nAct );
719                     pNumFnt->SetPitch( pFormatFnt->GetPitch(), nAct );
720                 }
721 
722                 // we do not allow a vertical font
723                 pNumFnt->SetVertical( pNumFnt->GetOrientation(),
724                                       m_pFrame->IsVertical() );
725 
726                 lcl_setRedlineAttr( rInf, *pTextNd, pNumFnt );
727 
728                 // --> OD 2008-01-23 #newlistelevelattrs#
729                 if (rNumFormat.GetBulletChar())
730                 {
731                     pRet = new SwBulletPortion(rNumFormat.GetBulletChar(),
732                         pTextNd->GetLabelFollowedBy(),
733                         std::move(pNumFnt),
734                         bLeft, bCenter, nMinDist,
735                         bLabelAlignmentPosAndSpaceModeActive);
736                 }
737             }
738             else
739             {
740                 // Show Changes mode shows the actual numbering (SwListRedlineType::HIDDEN) and
741                 // the original one (SwListRedlineType::ORIGTEXT) instead of the fake numbering
742                 // (SwListRedlineType::SHOW, which counts removed and inserted numbered paragraphs
743                 // in a single list)
744                 bool bHasHiddenNum = false;
745                 OUString aText( pTextNd->GetNumString(true, MAXLEVEL, m_pFrame->getRootFrame(), SwListRedlineType::HIDDEN) );
746                 const SwDoc& rDoc = pTextNd->GetDoc();
747                 const SwRedlineTable& rTable = rDoc.getIDocumentRedlineAccess().GetRedlineTable();
748                 if ( rTable.size() && !rInf.GetVsh()->GetLayout()->IsHideRedlines() )
749                 {
750                     OUString aHiddenText( pTextNd->GetNumString(true, MAXLEVEL, m_pFrame->getRootFrame(), SwListRedlineType::ORIGTEXT) );
751 
752                     if ( !aText.isEmpty() || !aHiddenText.isEmpty() )
753                     {
754                         if (aText != aHiddenText && !aHiddenText.isEmpty())
755                         {
756                             bHasHiddenNum = true;
757                             // show also original number after the actual one enclosed in [ and ],
758                             // and replace tabulator with space to avoid messy indentation
759                             // resulted by the longer numbering, e.g. "1.[2.]" instead of "1.".
760                             aText = aText +  "[" + aHiddenText + "]"
761                                      + pTextNd->GetLabelFollowedBy().replaceAll("\t", " ");
762                         }
763                         else if (!aText.isEmpty())
764                             aText += pTextNd->GetLabelFollowedBy();
765                     }
766                 }
767                 else if (!aText.isEmpty())
768                     aText += pTextNd->GetLabelFollowedBy();
769 
770                 // Not just an optimization ...
771                 // A number portion without text will be assigned a width of 0.
772                 // The succeeding text portion will flow into the BreakCut in the BreakLine,
773                 // although  we have rInf.GetLast()->GetFlyPortion()!
774                 if( !aText.isEmpty() )
775                 {
776 
777                     // Build a new numbering font basing on the current paragraph font:
778                     std::unique_ptr<SwFont> pNumFnt(new SwFont( &rInf.GetCharAttr(), pIDSA ));
779 
780                     // #i53199#
781                     if ( !pIDSA->get(DocumentSettingId::DO_NOT_RESET_PARA_ATTRS_FOR_NUM_FONT) )
782                     {
783                         // i18463:
784                         // Underline style of paragraph font should not be considered
785                         pNumFnt->SetUnderline( LINESTYLE_NONE );
786                         // Overline style of paragraph font should not be considered
787                         pNumFnt->SetOverline( LINESTYLE_NONE );
788                     }
789 
790                     // Apply the explicit attributes from the character style
791                     // associated with the numbering to the new bullet font.
792                     if( pFormat )
793                         pNumFnt->SetDiffFnt( pFormat, pIDSA );
794 
795                     checkApplyParagraphMarkFormatToNumbering(pNumFnt.get(), rInf, pIDSA, pFormat);
796 
797                     if ( !lcl_setRedlineAttr( rInf, *pTextNd, pNumFnt ) && bHasHiddenNum )
798                         pNumFnt->SetColor(NON_PRINTING_CHARACTER_COLOR);
799 
800                     // we do not allow a vertical font
801                     pNumFnt->SetVertical( pNumFnt->GetOrientation(), m_pFrame->IsVertical() );
802 
803                     pRet = new SwNumberPortion( aText, std::move(pNumFnt),
804                                                 bLeft, bCenter, nMinDist,
805                                                 bLabelAlignmentPosAndSpaceModeActive );
806                 }
807             }
808         }
809     }
810     return pRet;
811 }
812 
813 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
814