tdf#35361 Add a Quick Look plugins for .od* files on macOS
[LibreOffice.git] / sw / source / core / text / itratr.cxx
blobbccc7377072d0ed6efd4f1f94cdadb88db9c35c2
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/config.h>
22 #include <algorithm>
24 #include <hintids.hxx>
25 #include <editeng/charscaleitem.hxx>
26 #include <editeng/cmapitem.hxx>
27 #include <svl/itemiter.hxx>
28 #include <svx/svdobj.hxx>
29 #include <vcl/svapp.hxx>
30 #include <fmtanchr.hxx>
31 #include <fmtfsize.hxx>
32 #include <fmtornt.hxx>
33 #include <fmtflcnt.hxx>
34 #include <fmtcntnt.hxx>
35 #include <fmtftn.hxx>
36 #include <fmtpdsc.hxx>
37 #include <frmatr.hxx>
38 #include <frmfmt.hxx>
39 #include <fmtfld.hxx>
40 #include <doc.hxx>
41 #include <IDocumentLayoutAccess.hxx>
42 #include <IDocumentSettingAccess.hxx>
43 #include <txatbase.hxx>
44 #include <viewsh.hxx>
45 #include <rootfrm.hxx>
46 #include <docary.hxx>
47 #include <ndtxt.hxx>
48 #include <fldbas.hxx>
49 #include <pam.hxx>
50 #include "itratr.hxx"
51 #include <htmltbl.hxx>
52 #include <swtable.hxx>
53 #include "redlnitr.hxx"
54 #include <redline.hxx>
55 #include <fmtsrnd.hxx>
56 #include "itrtxt.hxx"
57 #include <breakit.hxx>
58 #include <com/sun/star/i18n/WordType.hpp>
59 #include <com/sun/star/i18n/XBreakIterator.hpp>
60 #include <editeng/lrspitem.hxx>
61 #include <calbck.hxx>
62 #include <frameformats.hxx>
63 #include <sortedobjs.hxx>
64 #include <anchoredobject.hxx>
65 #include <flyfrm.hxx>
66 #include <flyfrms.hxx>
68 using namespace ::com::sun::star::i18n;
69 using namespace ::com::sun::star;
71 static sal_Int32 GetNextAttrImpl(SwTextNode const* pTextNode,
72 size_t nStartIndex, size_t nEndIndex, sal_Int32 nPosition);
74 SwAttrIter::SwAttrIter(SwTextNode const * pTextNode)
75 : m_pViewShell(nullptr)
76 , m_pFont(nullptr)
77 , m_pScriptInfo(nullptr)
78 , m_pLastOut(nullptr)
79 , m_nChgCnt(0)
80 , m_nStartIndex(0)
81 , m_nEndIndex(0)
82 , m_nPosition(0)
83 , m_nPropFont(0)
84 , m_pTextNode(pTextNode)
85 , m_pMergedPara(nullptr)
87 m_aFontCacheIds[SwFontScript::Latin] = m_aFontCacheIds[SwFontScript::CJK] = m_aFontCacheIds[SwFontScript::CTL] = nullptr;
90 SwAttrIter::SwAttrIter(SwTextNode& rTextNode, SwScriptInfo& rScrInf, SwTextFrame const*const pFrame)
91 : m_pViewShell(nullptr)
92 , m_pFont(nullptr)
93 , m_pScriptInfo(nullptr)
94 , m_pLastOut(nullptr)
95 , m_nChgCnt(0)
96 , m_nPropFont(0)
97 , m_pTextNode(&rTextNode)
98 , m_pMergedPara(nullptr)
100 CtorInitAttrIter(rTextNode, rScrInf, pFrame);
103 void SwAttrIter::Chg( SwTextAttr const *pHt )
105 assert(pHt && m_pFont && "No attribute of font available for change");
106 if( m_pRedline && m_pRedline->IsOn() )
107 m_pRedline->ChangeTextAttr( m_pFont, *pHt, true );
108 else
109 m_aAttrHandler.PushAndChg( *pHt, *m_pFont );
110 m_nChgCnt++;
113 void SwAttrIter::Rst( SwTextAttr const *pHt )
115 assert(pHt && m_pFont && "No attribute of font available for reset");
116 // get top from stack after removing pHt
117 if( m_pRedline && m_pRedline->IsOn() )
118 m_pRedline->ChangeTextAttr( m_pFont, *pHt, false );
119 else
120 m_aAttrHandler.PopAndChg( *pHt, *m_pFont );
121 m_nChgCnt--;
124 SwAttrIter::~SwAttrIter()
126 m_pRedline.reset();
127 delete m_pFont;
130 bool SwAttrIter::MaybeHasHints() const
132 return nullptr != m_pTextNode->GetpSwpHints() || nullptr != m_pMergedPara;
136 * Returns the attribute for a position
138 * Only if the attribute is exactly at the position @param nPos and
139 * does not have an EndIndex
141 * We need this function for attributes which should alter formatting without
142 * changing the content of the string.
143 * Such "degenerated" attributes are e.g.: fields which retain expanded text and
144 * line-bound Frames.
145 * In order to avoid ambiguities between different such attributes, we insert a
146 * special character at the start of the string, when creating such an attribute.
147 * The Formatter later on encounters such a special character and retrieves the
148 * degenerate attribute via GetAttr().
150 SwTextAttr *SwAttrIter::GetAttr(TextFrameIndex const nPosition) const
152 std::pair<SwTextNode const*, sal_Int32> const pos( m_pMergedPara
153 ? sw::MapViewToModel(*m_pMergedPara, nPosition)
154 : std::make_pair(m_pTextNode, sal_Int32(nPosition)));
155 return pos.first->GetTextAttrForCharAt(pos.second);
158 bool SwAttrIter::SeekAndChgAttrIter(TextFrameIndex const nNewPos, OutputDevice* pOut)
160 std::pair<SwTextNode const*, sal_Int32> const pos( m_pMergedPara
161 ? sw::MapViewToModel(*m_pMergedPara, nNewPos)
162 : std::make_pair(m_pTextNode, sal_Int32(nNewPos)));
163 bool bChg = m_nStartIndex && pos.first == m_pTextNode && pos.second == m_nPosition
164 ? m_pFont->IsFntChg()
165 : Seek( nNewPos );
166 if ( m_pLastOut.get() != pOut )
168 m_pLastOut = pOut;
169 m_pFont->SetFntChg( true );
170 bChg = true;
172 if( bChg )
174 // if the change counter is zero, we know the cache id of the wanted font
175 if ( !m_nChgCnt && !m_nPropFont )
176 m_pFont->SetFontCacheId( m_aFontCacheIds[ m_pFont->GetActual() ],
177 m_aFontIdx[ m_pFont->GetActual() ], m_pFont->GetActual() );
178 m_pFont->ChgPhysFnt( m_pViewShell, *pOut );
181 return bChg;
184 bool SwAttrIter::IsSymbol(TextFrameIndex const nNewPos)
186 Seek( nNewPos );
187 if ( !m_nChgCnt && !m_nPropFont )
188 m_pFont->SetFontCacheId( m_aFontCacheIds[ m_pFont->GetActual() ],
189 m_aFontIdx[ m_pFont->GetActual() ], m_pFont->GetActual() );
190 return m_pFont->IsSymbol( m_pViewShell );
193 bool SwTextFrame::IsSymbolAt(TextFrameIndex const nPos) const
195 SwTextInfo info(const_cast<SwTextFrame*>(this));
196 SwTextIter iter(const_cast<SwTextFrame*>(this), &info);
197 return iter.IsSymbol(nPos);
200 bool SwAttrIter::SeekStartAndChgAttrIter( OutputDevice* pOut, const bool bParaFont )
202 SwTextNode const*const pFirstTextNode(m_pMergedPara ? m_pMergedPara->pFirstNode : m_pTextNode);
203 if ( m_pRedline && m_pRedline->ExtOn() )
204 m_pRedline->LeaveExtend(*m_pFont, pFirstTextNode->GetIndex(), 0);
206 if (m_pTextNode != pFirstTextNode)
208 assert(m_pMergedPara);
209 m_pTextNode = m_pMergedPara->pFirstNode;
210 InitFontAndAttrHandler(*m_pMergedPara->pParaPropsNode, *m_pTextNode,
211 m_pMergedPara->mergedText, nullptr, nullptr);
214 // reset font to its original state
215 m_aAttrHandler.Reset();
216 m_aAttrHandler.ResetFont( *m_pFont );
218 m_nStartIndex = 0;
219 m_nEndIndex = 0;
220 m_nPosition = 0;
221 m_nChgCnt = 0;
222 if( m_nPropFont )
223 m_pFont->SetProportion( m_nPropFont );
224 if( m_pRedline )
226 m_pRedline->Clear( m_pFont );
227 if( !bParaFont )
228 m_nChgCnt = m_nChgCnt + m_pRedline->Seek(*m_pFont, pFirstTextNode->GetIndex(), 0, COMPLETE_STRING);
229 else
230 m_pRedline->Reset();
233 SwpHints const*const pHints(m_pTextNode->GetpSwpHints());
234 if (pHints && !bParaFont)
236 SwTextAttr *pTextAttr;
237 // While we've not reached the end of the StartArray && the TextAttribute starts at position 0...
238 while ((m_nStartIndex < pHints->Count()) &&
239 !((pTextAttr = pHints->Get(m_nStartIndex))->GetStart()))
241 // open the TextAttributes
242 Chg( pTextAttr );
243 m_nStartIndex++;
247 bool bChg = m_pFont->IsFntChg();
248 if ( m_pLastOut.get() != pOut )
250 m_pLastOut = pOut;
251 m_pFont->SetFntChg( true );
252 bChg = true;
254 if( bChg )
256 // if the application counter is zero, we know the cache id of the wanted font
257 if ( !m_nChgCnt && !m_nPropFont )
258 m_pFont->SetFontCacheId( m_aFontCacheIds[ m_pFont->GetActual() ],
259 m_aFontIdx[ m_pFont->GetActual() ], m_pFont->GetActual() );
260 m_pFont->ChgPhysFnt( m_pViewShell, *pOut );
262 return bChg;
265 // AMA: New AttrIter Nov 94
266 void SwAttrIter::SeekFwd(const sal_Int32 nOldPos, const sal_Int32 nNewPos)
268 SwpHints const*const pHints(m_pTextNode->GetpSwpHints());
269 SwTextAttr *pTextAttr;
270 const auto nHintsCount = pHints->Count();
272 if ( m_nStartIndex ) // If attributes have been opened at all ...
274 // Close attributes that are currently open, but stop at nNewPos+1
276 // As long as we've not yet reached the end of EndArray and the
277 // TextAttribute ends before or at the new position ...
278 while ((m_nEndIndex < nHintsCount) &&
279 ((pTextAttr = pHints->GetSortedByEnd(m_nEndIndex))->GetAnyEnd() <= nNewPos))
281 // Close the TextAttributes, whose StartPos were before or at
282 // the old nPos and are currently open
283 if (pTextAttr->GetStart() <= nOldPos) Rst( pTextAttr );
284 m_nEndIndex++;
287 else // skip the not opened ends
289 while ((m_nEndIndex < nHintsCount) &&
290 (pHints->GetSortedByEnd(m_nEndIndex)->GetAnyEnd() <= nNewPos))
292 m_nEndIndex++;
296 // As long as we've not yet reached the end of EndArray and the
297 // TextAttribute ends before or at the new position...
298 while ((m_nStartIndex < nHintsCount) &&
299 ((pTextAttr = pHints->Get(m_nStartIndex))->GetStart() <= nNewPos))
302 // open the TextAttributes, whose ends lie behind the new position
303 if ( pTextAttr->GetAnyEnd() > nNewPos ) Chg( pTextAttr );
304 m_nStartIndex++;
308 void SwAttrIter::SeekToEnd()
310 if (m_pTextNode->GetDoc().getIDocumentSettingAccess().get(
311 DocumentSettingId::APPLY_PARAGRAPH_MARK_FORMAT_TO_EMPTY_LINE_AT_END_OF_PARAGRAPH))
313 SfxItemPool & rPool{const_cast<SwAttrPool&>(m_pTextNode->GetDoc().GetAttrPool())};
314 SwFormatAutoFormat const& rListAutoFormat{m_pTextNode->GetAttr(RES_PARATR_LIST_AUTOFMT)};
315 std::shared_ptr<SfxItemSet> const pSet{rListAutoFormat.GetStyleHandle()};
316 if (!pSet)
318 return;
320 if (pSet->HasItem(RES_TXTATR_CHARFMT))
322 SwFormatCharFormat const& rCharFormat{pSet->Get(RES_TXTATR_CHARFMT)};
323 m_pEndCharFormatAttr.reset(new SwTextAttrEnd{
324 SfxPoolItemHolder{rPool, &rCharFormat}, -1, -1});
325 Chg(m_pEndCharFormatAttr.get());
327 // note: RES_TXTATR_CHARFMT should be cleared here but it looks like
328 // SwAttrHandler only looks at RES_CHRATR_* anyway
329 m_pEndAutoFormatAttr.reset(new SwTextAttrEnd{
330 SfxPoolItemHolder{rPool, &rListAutoFormat}, -1, -1});
331 Chg(m_pEndAutoFormatAttr.get());
335 bool SwAttrIter::Seek(TextFrameIndex const nNewPos)
337 // note: nNewPos isn't necessarily an index returned from GetNextAttr
338 std::pair<SwTextNode const*, sal_Int32> const newPos( m_pMergedPara
339 ? sw::MapViewToModel(*m_pMergedPara, nNewPos)
340 : std::make_pair(m_pTextNode, sal_Int32(nNewPos)));
342 if ( m_pRedline && m_pRedline->ExtOn() )
343 m_pRedline->LeaveExtend(*m_pFont, newPos.first->GetIndex(), newPos.second);
344 if (m_pTextNode->GetIndex() < newPos.first->GetIndex())
346 // Skipping to a different node - first seek until the end of this node
347 // to get rid of all hint items
348 if (m_pTextNode->GetpSwpHints())
350 sal_Int32 nPos(m_nPosition);
353 sal_Int32 const nOldPos(nPos);
354 nPos = GetNextAttrImpl(m_pTextNode, m_nStartIndex, m_nEndIndex, nPos);
355 if (nPos <= m_pTextNode->Len())
357 SeekFwd(nOldPos, nPos);
359 else
361 SeekFwd(nOldPos, m_pTextNode->Len());
364 while (nPos < m_pTextNode->Len());
366 // Unapply current para items:
367 // the SwAttrHandler doesn't appear to be capable of *unapplying*
368 // items at all; it can only apply a previously effective item.
369 // So do this by recreating the font from scratch.
370 // Apply new para items:
371 assert(m_pMergedPara);
372 InitFontAndAttrHandler(*m_pMergedPara->pParaPropsNode, *newPos.first,
373 m_pMergedPara->mergedText, nullptr, nullptr);
374 // reset to next
375 m_pTextNode = newPos.first;
376 m_nStartIndex = 0;
377 m_nEndIndex = 0;
378 m_nPosition = 0;
379 assert(m_pRedline);
382 // sw_redlinehide: Seek(0) must move before the first character, which
383 // has a special case where the first node starts with delete redline.
384 if ((!nNewPos && !m_pMergedPara)
385 || newPos.first != m_pTextNode
386 || newPos.second < m_nPosition)
388 if (m_pMergedPara)
390 if (m_pTextNode != newPos.first)
392 m_pTextNode = newPos.first;
393 // sw_redlinehide: hope it's okay to use the current text node
394 // here; the AttrHandler shouldn't care about non-char items
395 InitFontAndAttrHandler(*m_pMergedPara->pParaPropsNode, *m_pTextNode,
396 m_pMergedPara->mergedText, nullptr, nullptr);
399 // also reset it if the RES_PARATR_LIST_AUTOFMT has been applied!
400 if (m_pMergedPara || m_pTextNode->GetpSwpHints() || m_pEndAutoFormatAttr)
402 if( m_pRedline )
403 m_pRedline->Clear( nullptr );
405 // reset font to its original state
406 m_aAttrHandler.Reset();
407 m_aAttrHandler.ResetFont( *m_pFont );
408 m_pEndCharFormatAttr.reset();
409 m_pEndAutoFormatAttr.reset();
411 if( m_nPropFont )
412 m_pFont->SetProportion( m_nPropFont );
413 m_nStartIndex = 0;
414 m_nEndIndex = 0;
415 m_nPosition = 0;
416 m_nChgCnt = 0;
418 // Attention!
419 // resetting the font here makes it necessary to apply any
420 // changes for extended input directly to the font
421 if ( m_pRedline && m_pRedline->ExtOn() )
423 m_pRedline->UpdateExtFont( *m_pFont );
424 ++m_nChgCnt;
429 bool isToEnd{false};
430 if (m_pMergedPara)
432 if (!m_pMergedPara->extents.empty())
434 auto const& rLast{m_pMergedPara->extents.back()};
435 isToEnd = rLast.pNode == newPos.first && rLast.nEnd == newPos.second;
437 else
439 isToEnd = true;
442 else
444 isToEnd = newPos.second == m_pTextNode->Len();
447 if (m_pTextNode->GetpSwpHints())
449 if (m_pMergedPara)
451 // iterate hint by hint: SeekFwd does not mix ends and starts,
452 // it always applies all the starts last, so it must be called once
453 // per position where hints start/end!
454 sal_Int32 nPos(m_nPosition);
457 sal_Int32 const nOldPos(nPos);
458 nPos = GetNextAttrImpl(m_pTextNode, m_nStartIndex, m_nEndIndex, nPos);
459 if (nPos <= newPos.second)
461 SeekFwd(nOldPos, nPos);
463 else
465 SeekFwd(nOldPos, newPos.second);
468 while (nPos < newPos.second);
470 else
472 SeekFwd(m_nPosition, newPos.second);
476 if (isToEnd && !m_pEndAutoFormatAttr)
478 SeekToEnd();
481 m_pFont->SetActual( m_pScriptInfo->WhichFont(nNewPos) );
483 if( m_pRedline )
484 m_nChgCnt = m_nChgCnt + m_pRedline->Seek(*m_pFont, m_pTextNode->GetIndex(), newPos.second, m_nPosition);
485 m_nPosition = newPos.second;
487 if( m_nPropFont )
488 m_pFont->SetProportion( m_nPropFont );
490 return m_pFont->IsFntChg();
493 static void InsertCharAttrs(SfxPoolItem const** pAttrs, SfxItemSet const& rItems)
495 SfxItemIter iter(rItems);
496 for (SfxPoolItem const* pItem = iter.GetCurItem(); pItem; pItem = iter.NextItem())
498 auto const nWhich(pItem->Which());
499 if (isCHRATR(nWhich) && RES_CHRATR_RSID != nWhich)
501 pAttrs[nWhich - RES_CHRATR_BEGIN] = pItem;
503 else if (nWhich == RES_TXTATR_UNKNOWN_CONTAINER)
505 pAttrs[RES_CHRATR_END - RES_CHRATR_BEGIN] = pItem;
510 // if return false: portion ends at start of redline, indexes unchanged
511 // if return true: portion end not known (past end of redline), indexes point to first hint past end of redline
512 static bool CanSkipOverRedline(
513 SwTextNode const& rStartNode, sal_Int32 const nStartRedline,
514 SwRangeRedline const& rRedline,
515 size_t & rStartIndex, size_t & rEndIndex,
516 bool const isTheAnswerYes)
518 size_t nStartIndex(rStartIndex);
519 size_t nEndIndex(rEndIndex);
520 SwPosition const*const pRLEnd(rRedline.End());
521 if (!pRLEnd->GetNode().IsTextNode() // if fully deleted...
522 || pRLEnd->GetContentIndex() == pRLEnd->GetNode().GetTextNode()->Len())
524 // shortcut: nothing follows redline
525 // current state is end state
526 return false;
528 std::vector<SwTextAttr*> activeCharFmts;
529 // can't compare the SwFont that's stored somewhere, it doesn't have compare
530 // operator, so try to recreate the situation with some temp arrays here
531 SfxPoolItem const* activeCharAttrsStart[RES_CHRATR_END - RES_CHRATR_BEGIN + 1] = { nullptr, };
532 if (rStartNode != pRLEnd->GetNode())
533 { // nodes' attributes are only needed if there are different nodes
534 InsertCharAttrs(activeCharAttrsStart, rStartNode.GetSwAttrSet());
536 if (SwpHints const*const pStartHints = rStartNode.GetpSwpHints())
538 // check hint ends of hints that start before and end within
539 sal_Int32 const nRedlineEnd(rStartNode == pRLEnd->GetNode()
540 ? pRLEnd->GetContentIndex()
541 : rStartNode.Len());
542 for ( ; nEndIndex < pStartHints->Count(); ++nEndIndex)
544 SwTextAttr *const pAttr(pStartHints->GetSortedByEnd(nEndIndex));
545 if (!pAttr->End())
547 continue;
549 if (nRedlineEnd < *pAttr->End())
551 break;
553 if (nStartRedline <= pAttr->GetStart())
555 continue;
557 if (pAttr->IsFormatIgnoreEnd())
559 continue;
561 switch (pAttr->Which())
563 // if any of these ends inside RL then we need a new portion
564 case RES_TXTATR_REFMARK:
565 case RES_TXTATR_TOXMARK:
566 case RES_TXTATR_META: // actually these 2 aren't allowed to overlap ???
567 case RES_TXTATR_METAFIELD:
568 case RES_TXTATR_INETFMT:
569 case RES_TXTATR_CJK_RUBY:
570 case RES_TXTATR_INPUTFIELD:
571 case RES_TXTATR_CONTENTCONTROL:
573 if (!isTheAnswerYes) return false; // always break
575 break;
576 // these are guaranteed not to overlap
577 // and come in order of application
578 case RES_TXTATR_AUTOFMT:
579 case RES_TXTATR_CHARFMT:
581 if (pAttr->Which() == RES_TXTATR_CHARFMT)
583 activeCharFmts.push_back(pAttr);
585 // pure formatting hints may end inside the redline &
586 // start again inside the redline, which must not cause
587 // a new text portion if they have the same items - so
588 // store the effective items & compare all at the end
589 SfxItemSet const& rSet((pAttr->Which() == RES_TXTATR_CHARFMT)
590 ? static_cast<SfxItemSet const&>(pAttr->GetCharFormat().GetCharFormat()->GetAttrSet())
591 : *pAttr->GetAutoFormat().GetStyleHandle());
592 InsertCharAttrs(activeCharAttrsStart, rSet);
594 break;
595 // SwTextNode::SetAttr puts it into AUTOFMT which is quite
596 // sensible so it doesn't actually exist as a hint
597 case RES_TXTATR_UNKNOWN_CONTAINER:
598 default: assert(false);
601 assert(nEndIndex == pStartHints->Count() ||
602 pRLEnd->GetContentIndex() < pStartHints->GetSortedByEnd(nEndIndex)->GetAnyEnd());
605 if (rStartNode != pRLEnd->GetNode())
607 nStartIndex = 0;
608 nEndIndex = 0;
611 // treat para properties as text properties
612 // ... with the FormatToTextAttr we get autofmts that correspond to the *effective* attr set difference
613 // effective attr set: para props + charfmts + autofmt *in that order*
614 // ... and the charfmt must be *nominally* the same
616 SfxPoolItem const* activeCharAttrsEnd[RES_CHRATR_END - RES_CHRATR_BEGIN + 1] = { nullptr, };
617 if (rStartNode != pRLEnd->GetNode())
618 { // nodes' attributes are only needed if there are different nodes
619 InsertCharAttrs(activeCharAttrsEnd,
620 pRLEnd->GetNode().GetTextNode()->GetSwAttrSet());
623 if (SwpHints *const pEndHints = pRLEnd->GetNode().GetTextNode()->GetpSwpHints())
625 // check hint starts of hints that start within and end after
626 #ifndef NDEBUG
627 sal_Int32 const nRedlineStart(rStartNode == pRLEnd->GetNode()
628 ? nStartRedline
629 : 0);
630 #endif
631 for ( ; nStartIndex < pEndHints->Count(); ++nStartIndex)
633 SwTextAttr *const pAttr(pEndHints->Get(nStartIndex));
634 // compare with < here, not <=, to get the effective formatting
635 // of the 1st char after the redline; should not cause problems
636 // with consecutive delete redlines because those are handed by
637 // GetNextRedln() and here we have the last end pos.
638 if (pRLEnd->GetContentIndex() < pAttr->GetStart())
640 break;
642 if (!pAttr->End())
643 continue;
644 if (pAttr->IsFormatIgnoreStart())
646 continue;
648 assert(nRedlineStart <= pAttr->GetStart()); // we wouldn't be here otherwise?
649 if (*pAttr->End() <= pRLEnd->GetContentIndex())
651 continue;
653 switch (pAttr->Which())
655 case RES_TXTATR_REFMARK:
656 case RES_TXTATR_TOXMARK:
657 case RES_TXTATR_META: // actually these 2 aren't allowed to overlap ???
658 case RES_TXTATR_METAFIELD:
659 case RES_TXTATR_INETFMT:
660 case RES_TXTATR_CJK_RUBY:
661 case RES_TXTATR_INPUTFIELD:
662 case RES_TXTATR_CONTENTCONTROL:
664 if (!isTheAnswerYes) return false;
666 break;
667 case RES_TXTATR_AUTOFMT:
668 case RES_TXTATR_CHARFMT:
670 // char formats must be *nominally* the same
671 if (pAttr->Which() == RES_TXTATR_CHARFMT)
673 auto iter = std::find_if(activeCharFmts.begin(), activeCharFmts.end(),
674 [&pAttr](const SwTextAttr* pCharFmt) { return *pCharFmt == *pAttr; });
675 if (iter != activeCharFmts.end())
676 activeCharFmts.erase(iter);
677 else if (!isTheAnswerYes)
678 return false;
680 SfxItemSet const& rSet((pAttr->Which() == RES_TXTATR_CHARFMT)
681 ? static_cast<SfxItemSet const&>(pAttr->GetCharFormat().GetCharFormat()->GetAttrSet())
682 : *pAttr->GetAutoFormat().GetStyleHandle());
683 InsertCharAttrs(activeCharAttrsEnd, rSet);
686 break;
687 // SwTextNode::SetAttr puts it into AUTOFMT which is quite
688 // sensible so it doesn't actually exist as a hint
689 case RES_TXTATR_UNKNOWN_CONTAINER:
690 default: assert(false);
693 if (rStartNode != pRLEnd->GetNode())
695 // need to iterate the nEndIndex forward too so the loop in the
696 // caller can look for the right ends in the next iteration
697 for (nEndIndex = 0; nEndIndex < pEndHints->Count(); ++nEndIndex)
699 SwTextAttr *const pAttr(pEndHints->GetSortedByEnd(nEndIndex));
700 if (!pAttr->End())
701 continue;
702 if (pRLEnd->GetContentIndex() < *pAttr->End())
704 break;
710 // if we didn't find a matching start for any end, then it really ends inside
711 if (!activeCharFmts.empty())
713 if (!isTheAnswerYes) return false;
715 for (size_t i = 0; i < SAL_N_ELEMENTS(activeCharAttrsStart); ++i)
717 // all of these should be shareable (but we have no SfxItemPool to check it here)
718 // assert(!activeCharAttrsStart[i] || activeCharAttrsStart[i]->GetItemPool()->Shareable(*activeCharAttrsStart[i]));
719 if (!SfxPoolItem::areSame(activeCharAttrsStart[i], activeCharAttrsEnd[i]))
721 if (!isTheAnswerYes) return false;
724 rStartIndex = nStartIndex;
725 rEndIndex = nEndIndex;
726 return true;
729 static sal_Int32 GetNextAttrImpl(SwTextNode const*const pTextNode,
730 size_t const nStartIndex, size_t const nEndIndex,
731 sal_Int32 const nPosition)
733 // note: this used to be COMPLETE_STRING, but was set to Len() + 1 below,
734 // which is rather silly, so set it to Len() instead
735 sal_Int32 nNext = pTextNode->Len();
736 if (SwpHints const*const pHints = pTextNode->GetpSwpHints())
738 // are there attribute starts left?
739 for (size_t i = nStartIndex; i < pHints->Count(); ++i)
741 SwTextAttr *const pAttr(pHints->Get(i));
742 if (!pAttr->IsFormatIgnoreStart())
744 nNext = pAttr->GetStart();
745 break;
748 // are there attribute ends left?
749 for (size_t i = nEndIndex; i < pHints->Count(); ++i)
751 SwTextAttr *const pAttr(pHints->GetSortedByEnd(i));
752 if (!pAttr->IsFormatIgnoreEnd())
754 sal_Int32 const nNextEnd = pAttr->GetAnyEnd();
755 nNext = std::min(nNext, nNextEnd); // pick nearest one
756 break;
760 // TODO: maybe use hints like FieldHints for this instead of looking at the text...
761 const sal_Int32 l = std::min(nNext, pTextNode->Len());
762 sal_Int32 p = nPosition;
763 const sal_Unicode* pStr = pTextNode->GetText().getStr();
764 while (p < l)
766 sal_Unicode aChar = pStr[p];
767 switch (aChar)
769 case CH_TXT_ATR_FORMELEMENT:
770 case CH_TXT_ATR_FIELDSTART:
771 case CH_TXT_ATR_FIELDSEP:
772 case CH_TXT_ATR_FIELDEND:
773 goto break_; // sigh...
774 default:
775 ++p;
778 break_:
779 assert(p <= nNext);
780 if (p < l)
782 // found a CH_TXT_ATR_FIELD*: if it's same as current position,
783 // skip behind it so that both before- and after-positions are returned
784 nNext = (nPosition < p) ? p : p + 1;
786 return nNext;
789 TextFrameIndex SwAttrIter::GetNextAttr() const
791 size_t nStartIndex(m_nStartIndex);
792 size_t nEndIndex(m_nEndIndex);
793 size_t nPosition(m_nPosition);
794 SwTextNode const* pTextNode(m_pTextNode);
795 SwRedlineTable::size_type nActRedline(m_pRedline ? m_pRedline->GetAct() : SwRedlineTable::npos);
797 while (true)
799 sal_Int32 nNext = GetNextAttrImpl(pTextNode, nStartIndex, nEndIndex, nPosition);
800 if( m_pRedline )
802 std::pair<sal_Int32, std::pair<SwRangeRedline const*, size_t>> const redline(
803 m_pRedline->GetNextRedln(nNext, pTextNode, nActRedline));
804 if (redline.second.first)
806 assert(m_pMergedPara);
807 assert(redline.second.first->End()->GetNodeIndex() <= m_pMergedPara->pLastNode->GetIndex()
808 || !redline.second.first->End()->GetNode().IsTextNode());
809 if (CanSkipOverRedline(*pTextNode, redline.first, *redline.second.first,
810 nStartIndex, nEndIndex, m_nPosition == redline.first))
811 { // if current position is start of the redline, must skip!
812 nActRedline += redline.second.second;
813 if (&redline.second.first->End()->GetNode() != pTextNode)
815 pTextNode = redline.second.first->End()->GetNode().GetTextNode();
816 nPosition = redline.second.first->End()->GetContentIndex();
818 else
820 nPosition = redline.second.first->End()->GetContentIndex();
823 else
825 return sw::MapModelToView(*m_pMergedPara, pTextNode, redline.first);
828 else
830 return m_pMergedPara
831 ? sw::MapModelToView(*m_pMergedPara, pTextNode, redline.first)
832 : TextFrameIndex(redline.first);
835 else
837 return TextFrameIndex(nNext);
842 namespace
844 class FormatBreakTracker
846 private:
847 std::optional<SvxCaseMap> m_nCaseMap;
849 bool m_bNeedsBreak = false;
851 void SetCaseMap(SvxCaseMap nValue)
853 if (m_nCaseMap != nValue)
854 m_bNeedsBreak = true;
856 m_nCaseMap = nValue;
859 public:
860 void HandleItemSet(const SfxItemSet& rSet)
862 if (const SvxCaseMapItem* pItem = rSet.GetItem(RES_CHRATR_CASEMAP))
863 SetCaseMap(pItem->GetCaseMap());
866 void Reset() { m_bNeedsBreak = false; }
868 bool NeedsBreak() const { return m_bNeedsBreak; }
871 bool HasFormatBreakAttribute(FormatBreakTracker* pTracker, const SwTextAttr* pAttr)
873 pTracker->Reset();
875 switch (pAttr->Which())
877 case RES_TXTATR_AUTOFMT:
878 case RES_TXTATR_CHARFMT:
880 const SfxItemSet& rSet((pAttr->Which() == RES_TXTATR_CHARFMT)
881 ? static_cast<SfxItemSet const&>(
882 pAttr->GetCharFormat().GetCharFormat()->GetAttrSet())
883 : *pAttr->GetAutoFormat().GetStyleHandle());
885 pTracker->HandleItemSet(rSet);
887 break;
890 if (pAttr->IsFormatIgnoreStart() || pAttr->IsFormatIgnoreEnd())
891 pTracker->Reset();
893 return pTracker->NeedsBreak();
897 TextFrameIndex SwAttrIter::GetNextLayoutBreakAttr() const
899 size_t nStartIndex(m_nStartIndex);
900 SwTextNode const* pTextNode(m_pTextNode);
902 sal_Int32 nNext = std::numeric_limits<sal_Int32>::max();
904 auto* pHints = pTextNode->GetpSwpHints();
905 if (!pHints)
907 return TextFrameIndex{ nNext };
910 FormatBreakTracker stTracker;
911 stTracker.HandleItemSet(pTextNode->GetSwAttrSet());
913 for (size_t i = 0; i < pHints->Count(); ++i)
915 SwTextAttr* const pAttr(pHints->Get(i));
916 if (HasFormatBreakAttribute(&stTracker, pAttr))
918 if (i >= nStartIndex)
920 nNext = pAttr->GetStart();
921 break;
926 return TextFrameIndex{ nNext };
929 namespace {
931 class SwMinMaxArgs
933 public:
934 VclPtr<OutputDevice> m_pOut;
935 SwViewShell const* m_pSh;
936 sal_uLong& m_rMin;
937 sal_uLong& m_rAbsMin;
938 tools::Long m_nRowWidth;
939 tools::Long m_nWordWidth;
940 tools::Long m_nWordAdd;
941 sal_Int32 m_nNoLineBreak;
942 SwMinMaxArgs(OutputDevice* pOutI, SwViewShell const* pShI, sal_uLong& rMinI, sal_uLong& rAbsI)
943 : m_pOut(pOutI)
944 , m_pSh(pShI)
945 , m_rMin(rMinI)
946 , m_rAbsMin(rAbsI)
947 , m_nRowWidth(0)
948 , m_nWordWidth(0)
949 , m_nWordAdd(0)
950 , m_nNoLineBreak(COMPLETE_STRING)
952 void Minimum( tools::Long nNew ) const {
953 if (static_cast<tools::Long>(m_rMin) < nNew)
954 m_rMin = nNew;
956 void NewWord() { m_nWordAdd = m_nWordWidth = 0; }
961 static bool lcl_MinMaxString( SwMinMaxArgs& rArg, SwFont* pFnt, const OUString &rText,
962 sal_Int32 nIdx, sal_Int32 nEnd )
964 bool bRet = false;
965 while( nIdx < nEnd )
967 sal_Int32 nStop = nIdx;
968 LanguageType eLang = pFnt->GetLanguage();
969 assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
971 bool bClear = CH_BLANK == rText[ nStop ];
972 Boundary aBndry( g_pBreakIt->GetBreakIter()->getWordBoundary( rText, nIdx,
973 g_pBreakIt->GetLocale( eLang ),
974 WordType::DICTIONARY_WORD, true ) );
975 nStop = aBndry.endPos;
976 if (nIdx <= aBndry.startPos && nIdx && nIdx - 1 != rArg.m_nNoLineBreak)
977 rArg.NewWord();
978 if( nStop == nIdx )
979 ++nStop;
980 if( nStop > nEnd )
981 nStop = nEnd;
983 SwDrawTextInfo aDrawInf(rArg.m_pSh, *rArg.m_pOut, rText, nIdx, nStop - nIdx);
984 tools::Long nCurrentWidth = pFnt->GetTextSize_( aDrawInf ).Width();
985 rArg.m_nRowWidth += nCurrentWidth;
986 if( bClear )
987 rArg.NewWord();
988 else
990 rArg.m_nWordWidth += nCurrentWidth;
991 if (static_cast<tools::Long>(rArg.m_rAbsMin) < rArg.m_nWordWidth)
992 rArg.m_rAbsMin = rArg.m_nWordWidth;
993 rArg.Minimum(rArg.m_nWordWidth + rArg.m_nWordAdd);
994 bRet = true;
996 nIdx = nStop;
998 return bRet;
1001 bool SwTextNode::IsSymbolAt(const sal_Int32 nBegin) const
1003 SwScriptInfo aScriptInfo;
1004 SwAttrIter aIter( *const_cast<SwTextNode*>(this), aScriptInfo );
1005 aIter.Seek( TextFrameIndex(nBegin) );
1006 return aIter.GetFnt()->IsSymbol( getIDocumentLayoutAccess().GetCurrentViewShell() );
1009 namespace {
1011 class SwMinMaxNodeArgs
1013 public:
1014 sal_uLong m_nMaxWidth; // sum of all frame widths
1015 tools::Long m_nMinWidth; // biggest frame
1016 tools::Long m_nLeftRest; // space not already covered by frames in the left margin
1017 tools::Long m_nRightRest; // space not already covered by frames in the right margin
1018 tools::Long m_nLeftDiff; // Min/Max-difference of the frame in the left margin
1019 tools::Long m_nRightDiff; // Min/Max-difference of the frame in the right margin
1020 SwNodeOffset m_nIndex; // index of the node
1021 void Minimum( tools::Long nNew ) {
1022 if (nNew > m_nMinWidth)
1023 m_nMinWidth = nNew;
1029 static void lcl_MinMaxNode(SwFrameFormat* pNd, SwMinMaxNodeArgs& rIn)
1031 const SwFormatAnchor& rFormatA = pNd->GetAnchor();
1033 if ((RndStdIds::FLY_AT_PARA != rFormatA.GetAnchorId()) &&
1034 (RndStdIds::FLY_AT_CHAR != rFormatA.GetAnchorId()))
1036 return;
1039 const SwNode *pAnchorNode = rFormatA.GetAnchorNode();
1040 OSL_ENSURE(pAnchorNode, "Unexpected NULL arguments");
1041 if (!pAnchorNode || rIn.m_nIndex != pAnchorNode->GetIndex())
1042 return;
1044 tools::Long nMin, nMax;
1045 SwHTMLTableLayout *pLayout = nullptr;
1046 const bool bIsDrawFrameFormat = pNd->Which()==RES_DRAWFRMFMT;
1047 if( !bIsDrawFrameFormat )
1049 // Does the frame contain a table at the start or the end?
1050 const SwNodes& rNodes = pNd->GetDoc()->GetNodes();
1051 const SwFormatContent& rFlyContent = pNd->GetContent();
1052 SwNodeOffset nStt = rFlyContent.GetContentIdx()->GetIndex();
1053 SwTableNode* pTableNd = rNodes[nStt+1]->GetTableNode();
1054 if( !pTableNd )
1056 SwNode *pNd2 = rNodes[nStt];
1057 pNd2 = rNodes[pNd2->EndOfSectionIndex()-1];
1058 if( pNd2->IsEndNode() )
1059 pTableNd = pNd2->StartOfSectionNode()->GetTableNode();
1062 if( pTableNd )
1063 pLayout = pTableNd->GetTable().GetHTMLTableLayout();
1066 const SwFormatHoriOrient& rOrient = pNd->GetHoriOrient();
1067 sal_Int16 eHoriOri = rOrient.GetHoriOrient();
1069 tools::Long nDiff;
1070 if( pLayout )
1072 nMin = pLayout->GetMin();
1073 nMax = pLayout->GetMax();
1074 nDiff = nMax - nMin;
1076 else
1078 if( bIsDrawFrameFormat )
1080 const SdrObject* pSObj = pNd->FindSdrObject();
1081 if( pSObj )
1082 nMin = pSObj->GetCurrentBoundRect().GetWidth();
1083 else
1084 nMin = 0;
1087 else
1089 const SwFormatFrameSize &rSz = pNd->GetFrameSize();
1090 nMin = rSz.GetWidth();
1092 nMax = nMin;
1093 nDiff = 0;
1096 const SvxLRSpaceItem &rLR = pNd->GetLRSpace();
1097 nMin += rLR.ResolveLeft({});
1098 nMin += rLR.ResolveRight({});
1099 nMax += rLR.ResolveLeft({});
1100 nMax += rLR.ResolveRight({});
1102 if( css::text::WrapTextMode_THROUGH == pNd->GetSurround().GetSurround() )
1104 rIn.Minimum( nMin );
1105 return;
1108 // Frames, which are left- or right-aligned are only party considered
1109 // when calculating the maximum, since the border is already being considered.
1110 // Only if the frame extends into the text body, this part is being added
1111 switch( eHoriOri )
1113 case text::HoriOrientation::RIGHT:
1115 if( nDiff )
1117 rIn.m_nRightRest -= rIn.m_nRightDiff;
1118 rIn.m_nRightDiff = nDiff;
1120 if( text::RelOrientation::FRAME != rOrient.GetRelationOrient() )
1122 if (rIn.m_nRightRest > 0)
1123 rIn.m_nRightRest = 0;
1125 rIn.m_nRightRest -= nMin;
1126 break;
1128 case text::HoriOrientation::LEFT:
1130 if( nDiff )
1132 rIn.m_nLeftRest -= rIn.m_nLeftDiff;
1133 rIn.m_nLeftDiff = nDiff;
1135 if (text::RelOrientation::FRAME != rOrient.GetRelationOrient() && rIn.m_nLeftRest < 0)
1136 rIn.m_nLeftRest = 0;
1137 rIn.m_nLeftRest -= nMin;
1138 break;
1140 default:
1142 rIn.m_nMaxWidth += nMax;
1143 rIn.Minimum(nMin);
1148 #define FLYINCNT_MIN_WIDTH 284
1151 * Changing this method very likely requires changing of GetScalingOfSelectedText
1152 * This one is called exclusively from import filters, so there is no layout.
1154 void SwTextNode::GetMinMaxSize( SwNodeOffset nIndex, sal_uLong& rMin, sal_uLong &rMax,
1155 sal_uLong& rAbsMin ) const
1157 SwViewShell const * pSh = GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell();
1158 OutputDevice* pOut = nullptr;
1159 if( pSh )
1160 pOut = pSh->GetWin()->GetOutDev();
1161 if( !pOut )
1162 pOut = Application::GetDefaultDevice();
1164 MapMode aOldMap( pOut->GetMapMode() );
1165 pOut->SetMapMode( MapMode( MapUnit::MapTwip ) );
1167 rMin = 0;
1168 rMax = 0;
1169 rAbsMin = 0;
1171 SvxTextLeftMarginItem const& rTextLeftMargin(GetSwAttrSet().GetTextLeftMargin());
1172 SvxRightMarginItem const& rRightMargin(GetSwAttrSet().GetRightMargin());
1173 tools::Long nLROffset = rTextLeftMargin.ResolveTextLeft({}) + GetLeftMarginWithNum(true);
1174 short nFLOffs;
1175 // For enumerations a negative first line indentation is probably filled already
1176 if (!GetFirstLineOfsWithNum(nFLOffs, {}) || nFLOffs > nLROffset)
1177 nLROffset = nFLOffs;
1179 SwMinMaxNodeArgs aNodeArgs;
1180 aNodeArgs.m_nMinWidth = 0;
1181 aNodeArgs.m_nMaxWidth = 0;
1182 aNodeArgs.m_nLeftRest = nLROffset;
1183 aNodeArgs.m_nRightRest = rRightMargin.ResolveRight({});
1184 aNodeArgs.m_nLeftDiff = 0;
1185 aNodeArgs.m_nRightDiff = 0;
1186 if( nIndex )
1188 sw::SpzFrameFormats* pSpzs = const_cast<sw::SpzFrameFormats*>(GetDoc().GetSpzFrameFormats());
1189 if(pSpzs)
1191 aNodeArgs.m_nIndex = nIndex;
1192 for(auto pFormat: *pSpzs)
1193 lcl_MinMaxNode(pFormat, aNodeArgs);
1196 if (aNodeArgs.m_nLeftRest < 0)
1197 aNodeArgs.Minimum(nLROffset - aNodeArgs.m_nLeftRest);
1198 aNodeArgs.m_nLeftRest -= aNodeArgs.m_nLeftDiff;
1199 if (aNodeArgs.m_nLeftRest < 0)
1200 aNodeArgs.m_nMaxWidth -= aNodeArgs.m_nLeftRest;
1202 if (aNodeArgs.m_nRightRest < 0)
1203 aNodeArgs.Minimum(rRightMargin.ResolveRight({}) - aNodeArgs.m_nRightRest);
1204 aNodeArgs.m_nRightRest -= aNodeArgs.m_nRightDiff;
1205 if (aNodeArgs.m_nRightRest < 0)
1206 aNodeArgs.m_nMaxWidth -= aNodeArgs.m_nRightRest;
1208 SwScriptInfo aScriptInfo;
1209 SwAttrIter aIter( *const_cast<SwTextNode*>(this), aScriptInfo );
1210 TextFrameIndex nIdx(0);
1211 aIter.SeekAndChgAttrIter( nIdx, pOut );
1212 TextFrameIndex nLen(m_Text.getLength());
1213 tools::Long nCurrentWidth = 0;
1214 tools::Long nAdd = 0;
1215 SwMinMaxArgs aArg( pOut, pSh, rMin, rAbsMin );
1216 while( nIdx < nLen )
1218 TextFrameIndex nNextChg = aIter.GetNextAttr();
1219 TextFrameIndex nStop = aScriptInfo.NextScriptChg( nIdx );
1220 if( nNextChg > nStop )
1221 nNextChg = nStop;
1222 SwTextAttr *pHint = nullptr;
1223 sal_Unicode cChar = CH_BLANK;
1224 nStop = nIdx;
1225 while( nStop < nLen && nStop < nNextChg &&
1226 CH_TAB != (cChar = m_Text[sal_Int32(nStop)]) &&
1227 CH_BREAK != cChar && CHAR_HARDBLANK != cChar &&
1228 CHAR_HARDHYPHEN != cChar && CHAR_SOFTHYPHEN != cChar &&
1229 CH_TXT_ATR_INPUTFIELDSTART != cChar &&
1230 CH_TXT_ATR_INPUTFIELDEND != cChar &&
1231 CH_TXT_ATR_FORMELEMENT != cChar &&
1232 CH_TXT_ATR_FIELDSTART != cChar &&
1233 CH_TXT_ATR_FIELDSEP != cChar &&
1234 CH_TXT_ATR_FIELDEND != cChar &&
1235 !pHint )
1237 // this looks like some defensive programming to handle dummy char
1238 // with missing hint? but it's rather silly because it may pass the
1239 // dummy char to lcl_MinMaxString in that case...
1240 if( ( CH_TXTATR_BREAKWORD != cChar && CH_TXTATR_INWORD != cChar )
1241 || ( nullptr == ( pHint = aIter.GetAttr( nStop ) ) ) )
1242 ++nStop;
1244 if (lcl_MinMaxString(aArg, aIter.GetFnt(), m_Text, sal_Int32(nIdx), sal_Int32(nStop)))
1246 nAdd = 20;
1248 nIdx = nStop;
1249 aIter.SeekAndChgAttrIter( nIdx, pOut );
1250 switch( cChar )
1252 case CH_BREAK :
1254 if (static_cast<tools::Long>(rMax) < aArg.m_nRowWidth)
1255 rMax = aArg.m_nRowWidth;
1256 aArg.m_nRowWidth = 0;
1257 aArg.NewWord();
1258 aIter.SeekAndChgAttrIter( ++nIdx, pOut );
1260 break;
1261 case CH_TAB :
1263 aArg.NewWord();
1264 aIter.SeekAndChgAttrIter( ++nIdx, pOut );
1266 break;
1267 case CHAR_SOFTHYPHEN:
1268 ++nIdx;
1269 break;
1270 case CHAR_HARDBLANK:
1271 case CHAR_HARDHYPHEN:
1273 OUString sTmp( cChar );
1274 SwDrawTextInfo aDrawInf( pSh,
1275 *pOut, sTmp, 0, 1, 0, false );
1276 nCurrentWidth = aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
1277 aArg.m_nWordWidth += nCurrentWidth;
1278 aArg.m_nRowWidth += nCurrentWidth;
1279 if (static_cast<tools::Long>(rAbsMin) < aArg.m_nWordWidth)
1280 rAbsMin = aArg.m_nWordWidth;
1281 aArg.Minimum(aArg.m_nWordWidth + aArg.m_nWordAdd);
1282 aArg.m_nNoLineBreak = sal_Int32(nIdx++);
1284 break;
1285 case CH_TXTATR_BREAKWORD:
1286 case CH_TXTATR_INWORD:
1288 if( !pHint )
1289 break;
1290 tools::Long nOldWidth = aArg.m_nWordWidth;
1291 tools::Long nOldAdd = aArg.m_nWordAdd;
1292 aArg.NewWord();
1294 switch( pHint->Which() )
1296 case RES_TXTATR_FLYCNT :
1298 SwFrameFormat *pFrameFormat = pHint->GetFlyCnt().GetFrameFormat();
1299 const SvxLRSpaceItem &rLR = pFrameFormat->GetLRSpace();
1300 if( RES_DRAWFRMFMT == pFrameFormat->Which() )
1302 const SdrObject* pSObj = pFrameFormat->FindSdrObject();
1303 if( pSObj )
1304 nCurrentWidth = pSObj->GetCurrentBoundRect().GetWidth();
1305 else
1306 nCurrentWidth = 0;
1308 else
1310 const SwFormatFrameSize& rTmpSize = pFrameFormat->GetFrameSize();
1311 if( RES_FLYFRMFMT == pFrameFormat->Which()
1312 && rTmpSize.GetWidthPercent() )
1314 // This is a hack for the following situation: In the paragraph there's a
1315 // text frame with relative size. Then let's take 0.5 cm as minimum width
1316 // and USHRT_MAX as maximum width
1317 // It were cleaner and maybe necessary later on to iterate over the content
1318 // of the text frame and call GetMinMaxSize recursively
1319 nCurrentWidth = FLYINCNT_MIN_WIDTH; // 0.5 cm
1320 rMax = std::max(rMax, sal_uLong(USHRT_MAX));
1322 else
1323 nCurrentWidth = pFrameFormat->GetFrameSize().GetWidth();
1325 nCurrentWidth += rLR.ResolveLeft({});
1326 nCurrentWidth += rLR.ResolveRight({});
1327 aArg.m_nWordAdd = nOldWidth + nOldAdd;
1328 aArg.m_nWordWidth = nCurrentWidth;
1329 aArg.m_nRowWidth += nCurrentWidth;
1330 if (static_cast<tools::Long>(rAbsMin) < aArg.m_nWordWidth)
1331 rAbsMin = aArg.m_nWordWidth;
1332 aArg.Minimum(aArg.m_nWordWidth + aArg.m_nWordAdd);
1333 break;
1335 case RES_TXTATR_FTN :
1337 const OUString aText = pHint->GetFootnote().GetNumStr();
1338 if( lcl_MinMaxString( aArg, aIter.GetFnt(), aText, 0,
1339 aText.getLength() ) )
1340 nAdd = 20;
1341 break;
1344 case RES_TXTATR_FIELD :
1345 case RES_TXTATR_ANNOTATION :
1347 SwField *pField = const_cast<SwField*>(pHint->GetFormatField().GetField());
1348 const OUString aText = pField->ExpandField(true, nullptr);
1349 if( lcl_MinMaxString( aArg, aIter.GetFnt(), aText, 0,
1350 aText.getLength() ) )
1351 nAdd = 20;
1352 break;
1354 default:
1355 aArg.m_nWordWidth = nOldWidth;
1356 aArg.m_nWordAdd = nOldAdd;
1358 aIter.SeekAndChgAttrIter( ++nIdx, pOut );
1360 break;
1361 case CH_TXT_ATR_INPUTFIELDSTART:
1362 case CH_TXT_ATR_INPUTFIELDEND:
1363 case CH_TXT_ATR_FORMELEMENT:
1364 case CH_TXT_ATR_FIELDSTART:
1365 case CH_TXT_ATR_FIELDSEP:
1366 case CH_TXT_ATR_FIELDEND:
1367 { // just skip it and continue with the content...
1368 aIter.SeekAndChgAttrIter( ++nIdx, pOut );
1370 break;
1373 if (static_cast<tools::Long>(rMax) < aArg.m_nRowWidth)
1374 rMax = aArg.m_nRowWidth;
1376 nLROffset += rRightMargin.ResolveRight({});
1378 rAbsMin += nLROffset;
1379 rAbsMin += nAdd;
1380 rMin += nLROffset;
1381 rMin += nAdd;
1382 if (static_cast<tools::Long>(rMin) < aNodeArgs.m_nMinWidth)
1383 rMin = aNodeArgs.m_nMinWidth;
1384 if (static_cast<tools::Long>(rAbsMin) < aNodeArgs.m_nMinWidth)
1385 rAbsMin = aNodeArgs.m_nMinWidth;
1386 rMax += aNodeArgs.m_nMaxWidth;
1387 rMax += nLROffset;
1388 rMax += nAdd;
1389 if( rMax < rMin ) // e.g. Frames with flow through only contribute to the minimum
1390 rMax = rMin;
1391 pOut->SetMapMode( aOldMap );
1395 * Calculates the width of the text part specified by nStart and nEnd,
1396 * the height of the line containing nStart is divided by this width,
1397 * indicating the scaling factor, if the text part is rotated.
1398 * Having CH_BREAKs in the text part, this method returns the scaling
1399 * factor for the longest of the text parts separated by the CH_BREAK
1401 * Changing this method very likely requires changing of "GetMinMaxSize"
1403 sal_uInt16 SwTextFrame::GetScalingOfSelectedText(
1404 TextFrameIndex nStart, TextFrameIndex nEnd)
1406 assert(GetOffset() <= nStart && (!GetFollow() || nStart < GetFollow()->GetOffset()));
1407 SwViewShell const*const pSh = getRootFrame()->GetCurrShell();
1408 assert(pSh);
1409 OutputDevice *const pOut = &pSh->GetRefDev();
1410 assert(pOut);
1412 MapMode aOldMap( pOut->GetMapMode() );
1413 pOut->SetMapMode( MapMode( MapUnit::MapTwip ) );
1415 if (nStart == nEnd)
1417 assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
1419 SwScriptInfo aScriptInfo;
1420 SwAttrIter aIter(*GetTextNodeFirst(), aScriptInfo, this);
1421 aIter.SeekAndChgAttrIter( nStart, pOut );
1423 Boundary aBound = g_pBreakIt->GetBreakIter()->getWordBoundary(
1424 GetText(), sal_Int32(nStart),
1425 g_pBreakIt->GetLocale( aIter.GetFnt()->GetLanguage() ),
1426 WordType::DICTIONARY_WORD, true );
1428 if (sal_Int32(nStart) == aBound.startPos)
1430 // cursor is at left or right border of word
1431 pOut->SetMapMode( aOldMap );
1432 return 100;
1435 nStart = TextFrameIndex(aBound.startPos);
1436 nEnd = TextFrameIndex(aBound.endPos);
1438 if (nStart == nEnd)
1440 pOut->SetMapMode( aOldMap );
1441 return 100;
1445 SwScriptInfo aScriptInfo;
1446 SwAttrIter aIter(*GetTextNodeFirst(), aScriptInfo, this);
1448 // We do not want scaling attributes to be considered during this
1449 // calculation. For this, we push a temporary scaling attribute with
1450 // scaling value 100 and priority flag on top of the scaling stack
1451 SwAttrHandler& rAH = aIter.GetAttrHandler();
1452 SvxCharScaleWidthItem aItem(100, RES_CHRATR_SCALEW);
1453 SwTextAttrEnd aAttr( SfxPoolItemHolder(getRootFrame()->GetCurrShell()->GetAttrPool(), &aItem), 0, COMPLETE_STRING );
1454 aAttr.SetPriorityAttr( true );
1455 rAH.PushAndChg( aAttr, *(aIter.GetFnt()) );
1457 TextFrameIndex nIdx = nStart;
1459 sal_uLong nWidth = 0;
1460 sal_uLong nProWidth = 0;
1462 while( nIdx < nEnd )
1464 aIter.SeekAndChgAttrIter( nIdx, pOut );
1466 // scan for end of portion
1467 TextFrameIndex const nNextChg = std::min(aIter.GetNextAttr(), aScriptInfo.NextScriptChg(nIdx));
1469 TextFrameIndex nStop = nIdx;
1470 sal_Unicode cChar = CH_BLANK;
1471 SwTextAttr* pHint = nullptr;
1473 // stop at special characters in [ nIdx, nNextChg ]
1474 while( nStop < nEnd && nStop < nNextChg )
1476 cChar = GetText()[sal_Int32(nStop)];
1477 if (
1478 CH_TAB == cChar ||
1479 CH_BREAK == cChar ||
1480 CHAR_HARDBLANK == cChar ||
1481 CHAR_HARDHYPHEN == cChar ||
1482 CHAR_SOFTHYPHEN == cChar ||
1483 CH_TXT_ATR_INPUTFIELDSTART == cChar ||
1484 CH_TXT_ATR_INPUTFIELDEND == cChar ||
1485 CH_TXT_ATR_FORMELEMENT == cChar ||
1486 CH_TXT_ATR_FIELDSTART == cChar ||
1487 CH_TXT_ATR_FIELDSEP == cChar ||
1488 CH_TXT_ATR_FIELDEND == cChar ||
1490 (CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar) &&
1491 (nullptr == (pHint = aIter.GetAttr(nStop)))
1495 break;
1497 else
1498 ++nStop;
1501 // calculate text widths up to cChar
1502 if ( nStop > nIdx )
1504 SwDrawTextInfo aDrawInf(pSh, *pOut, GetText(), sal_Int32(nIdx), sal_Int32(nStop - nIdx));
1505 nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
1508 nIdx = nStop;
1509 aIter.SeekAndChgAttrIter( nIdx, pOut );
1511 if ( cChar == CH_BREAK )
1513 nWidth = std::max( nWidth, nProWidth );
1514 nProWidth = 0;
1515 nIdx++;
1517 else if ( cChar == CH_TAB )
1519 // tab receives width of one space
1520 SwDrawTextInfo aDrawInf(pSh, *pOut, OUStringChar(CH_BLANK), 0, 1);
1521 nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
1522 nIdx++;
1524 else if ( cChar == CHAR_SOFTHYPHEN )
1525 ++nIdx;
1526 else if ( cChar == CHAR_HARDBLANK || cChar == CHAR_HARDHYPHEN )
1528 OUString sTmp( cChar );
1529 SwDrawTextInfo aDrawInf(pSh, *pOut, sTmp, 0, 1);
1530 nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
1531 nIdx++;
1533 else if ( pHint && ( cChar == CH_TXTATR_BREAKWORD || cChar == CH_TXTATR_INWORD ) )
1535 switch( pHint->Which() )
1537 case RES_TXTATR_FTN :
1539 const OUString aText = pHint->GetFootnote().GetNumStr();
1540 SwDrawTextInfo aDrawInf(pSh, *pOut, aText, 0, aText.getLength());
1542 nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
1543 break;
1546 case RES_TXTATR_FIELD :
1547 case RES_TXTATR_ANNOTATION :
1549 SwField *pField = const_cast<SwField*>(pHint->GetFormatField().GetField());
1550 OUString const aText = pField->ExpandField(true, getRootFrame());
1551 SwDrawTextInfo aDrawInf(pSh, *pOut, aText, 0, aText.getLength());
1553 nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
1554 break;
1557 default:
1559 // any suggestions for a default action?
1561 } // end of switch
1562 nIdx++;
1564 else if (CH_TXT_ATR_INPUTFIELDSTART == cChar ||
1565 CH_TXT_ATR_INPUTFIELDEND == cChar ||
1566 CH_TXT_ATR_FORMELEMENT == cChar ||
1567 CH_TXT_ATR_FIELDSTART == cChar ||
1568 CH_TXT_ATR_FIELDSEP == cChar ||
1569 CH_TXT_ATR_FIELDEND == cChar)
1570 { // just skip it and continue with the content...
1571 ++nIdx;
1573 } // end of while
1575 nWidth = std::max( nWidth, nProWidth );
1577 // search for the line containing nStart
1578 if (HasPara())
1580 SwTextInfo aInf(this);
1581 SwTextIter aLine(this, &aInf);
1582 aLine.CharToLine( nStart );
1583 pOut->SetMapMode( aOldMap );
1584 return o3tl::narrowing<sal_uInt16>( nWidth ?
1585 ( ( 100 * aLine.GetCurr()->Height() ) / nWidth ) : 0 );
1587 // no frame or no paragraph, we take the height of the character
1588 // at nStart as line height
1590 aIter.SeekAndChgAttrIter( nStart, pOut );
1591 pOut->SetMapMode( aOldMap );
1593 SwDrawTextInfo aDrawInf(pSh, *pOut, GetText(), sal_Int32(nStart), 1);
1594 return o3tl::narrowing<sal_uInt16>( nWidth ? ((100 * aIter.GetFnt()->GetTextSize_( aDrawInf ).Height()) / nWidth ) : 0 );
1597 std::vector<SwFlyAtContentFrame*> SwTextFrame::GetSplitFlyDrawObjs() const
1599 std::vector<SwFlyAtContentFrame*> aObjs;
1600 const SwSortedObjs* pSortedObjs = GetDrawObjs();
1601 if (!pSortedObjs)
1603 return aObjs;
1606 for (const auto& pSortedObj : *pSortedObjs)
1608 SwFlyFrame* pFlyFrame = pSortedObj->DynCastFlyFrame();
1609 if (!pFlyFrame)
1611 continue;
1614 if (!pFlyFrame->IsFlySplitAllowed())
1616 continue;
1619 aObjs.push_back(static_cast<SwFlyAtContentFrame*>(pFlyFrame));
1622 return aObjs;
1625 bool SwTextFrame::HasSplitFlyDrawObjs() const
1627 return !GetSplitFlyDrawObjs().empty();
1630 SwFlyAtContentFrame* SwTextFrame::HasNonLastSplitFlyDrawObj() const
1632 const SwTextFrame* pFollow = GetFollow();
1633 if (!pFollow)
1635 return nullptr;
1638 if (mnOffset != pFollow->GetOffset())
1640 return nullptr;
1643 // At this point we know what we're part of a chain that is an anchor for split fly frames, but
1644 // we're not the last one. See if we have a matching fly.
1646 // Look up the master of the anchor.
1647 const SwTextFrame* pAnchor = this;
1648 while (pAnchor->IsFollow())
1650 pAnchor = pAnchor->FindMaster();
1652 for (const auto& pFly : pAnchor->GetSplitFlyDrawObjs())
1654 // Nominally all flys are anchored in the master; see if this fly is effectively anchored in
1655 // us.
1656 SwTextFrame* pFlyAnchor = pFly->FindAnchorCharFrame();
1657 if (pFlyAnchor != this)
1659 continue;
1661 if (pFly->GetFollow())
1663 return pFly;
1667 return nullptr;
1670 bool SwTextFrame::IsEmptyMasterWithSplitFly() const
1672 if (!IsEmptyMaster())
1674 return false;
1677 if (!m_pDrawObjs || m_pDrawObjs->size() != 1)
1679 return false;
1682 SwFlyFrame* pFlyFrame = (*m_pDrawObjs)[0]->DynCastFlyFrame();
1683 if (!pFlyFrame || !pFlyFrame->IsFlySplitAllowed())
1685 return false;
1688 if (mnOffset != GetFollow()->GetOffset())
1690 return false;
1693 return true;
1696 bool SwTextFrame::IsEmptyWithSplitFly() const
1698 if (IsFollow())
1700 return false;
1703 if (SvxBreak const eBreak = GetBreakItem().GetBreak();
1704 eBreak == SvxBreak::ColumnBefore || eBreak == SvxBreak::ColumnBoth
1705 || eBreak == SvxBreak::PageBefore || eBreak == SvxBreak::PageBoth
1706 || GetPageDescItem().GetPageDesc() != nullptr)
1708 return false;
1711 SwRectFnSet fnUpper(GetUpper());
1712 if (fnUpper.YDiff(fnUpper.GetBottom(getFrameArea()), fnUpper.GetPrtBottom(*GetUpper())) <= 0)
1714 return false;
1717 // This is a master that doesn't fit the current parent.
1718 if (!m_pDrawObjs || m_pDrawObjs->size() != 1)
1720 return false;
1723 SwFlyFrame* pFlyFrame = (*m_pDrawObjs)[0]->DynCastFlyFrame();
1724 if (!pFlyFrame || !pFlyFrame->IsFlySplitAllowed())
1726 return false;
1729 // It has a split fly anchored to it.
1730 if (pFlyFrame->GetFrameFormat()->GetVertOrient().GetPos() >= 0)
1732 return false;
1735 // Negative vertical offset means that visually it already may have a first line.
1736 // Consider that, we may need to split the frame, so the fly frame is on one page and the empty
1737 // paragraph's frame is on a next page.
1738 return true;
1741 SwTwips SwTextNode::GetWidthOfLeadingTabs() const
1743 SwTwips nRet = 0;
1745 sal_Int32 nIdx = 0;
1747 while ( nIdx < GetText().getLength() )
1749 const sal_Unicode cCh = GetText()[nIdx];
1750 if ( cCh!='\t' && cCh!=' ' )
1752 break;
1754 ++nIdx;
1757 if ( nIdx > 0 )
1759 SwPosition aPos( *this, nIdx );
1761 // Find the non-follow text frame:
1762 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this);
1763 for( SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next() )
1765 // Only consider master frames:
1766 if (!pFrame->IsFollow() &&
1767 pFrame->GetTextNodeForFirstText() == this)
1769 SwRectFnSet aRectFnSet(pFrame);
1770 SwRect aRect;
1771 pFrame->GetCharRect( aRect, aPos );
1772 nRet = pFrame->IsRightToLeft() ?
1773 aRectFnSet.GetPrtRight(*pFrame) - aRectFnSet.GetRight(aRect) :
1774 aRectFnSet.GetLeft(aRect) - aRectFnSet.GetPrtLeft(*pFrame);
1775 break;
1780 return nRet;
1783 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */