android: Update app-specific/MIME type icons
[LibreOffice.git] / sw / source / core / text / itratr.cxx
blob358d6d5068f30cbb9cd27ea3ee553cc716cdf90e
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 <svl/itemiter.hxx>
27 #include <svx/svdobj.hxx>
28 #include <vcl/svapp.hxx>
29 #include <fmtanchr.hxx>
30 #include <fmtfsize.hxx>
31 #include <fmtornt.hxx>
32 #include <fmtflcnt.hxx>
33 #include <fmtcntnt.hxx>
34 #include <fmtftn.hxx>
35 #include <frmatr.hxx>
36 #include <frmfmt.hxx>
37 #include <fmtfld.hxx>
38 #include <doc.hxx>
39 #include <IDocumentLayoutAccess.hxx>
40 #include <txatbase.hxx>
41 #include <viewsh.hxx>
42 #include <rootfrm.hxx>
43 #include <docary.hxx>
44 #include <ndtxt.hxx>
45 #include <fldbas.hxx>
46 #include <pam.hxx>
47 #include "itratr.hxx"
48 #include <htmltbl.hxx>
49 #include <swtable.hxx>
50 #include "redlnitr.hxx"
51 #include <redline.hxx>
52 #include <fmtsrnd.hxx>
53 #include "itrtxt.hxx"
54 #include <breakit.hxx>
55 #include <com/sun/star/i18n/WordType.hpp>
56 #include <com/sun/star/i18n/XBreakIterator.hpp>
57 #include <editeng/lrspitem.hxx>
58 #include <calbck.hxx>
59 #include <frameformats.hxx>
60 #include <sortedobjs.hxx>
61 #include <anchoredobject.hxx>
62 #include <flyfrm.hxx>
63 #include <flyfrms.hxx>
65 using namespace ::com::sun::star::i18n;
66 using namespace ::com::sun::star;
68 static sal_Int32 GetNextAttrImpl(SwTextNode const* pTextNode,
69 size_t nStartIndex, size_t nEndIndex, sal_Int32 nPosition);
71 SwAttrIter::SwAttrIter(SwTextNode const * pTextNode)
72 : m_pViewShell(nullptr)
73 , m_pFont(nullptr)
74 , m_pScriptInfo(nullptr)
75 , m_pLastOut(nullptr)
76 , m_nChgCnt(0)
77 , m_nStartIndex(0)
78 , m_nEndIndex(0)
79 , m_nPosition(0)
80 , m_nPropFont(0)
81 , m_pTextNode(pTextNode)
82 , m_pMergedPara(nullptr)
84 m_aFontCacheIds[SwFontScript::Latin] = m_aFontCacheIds[SwFontScript::CJK] = m_aFontCacheIds[SwFontScript::CTL] = nullptr;
87 SwAttrIter::SwAttrIter(SwTextNode& rTextNode, SwScriptInfo& rScrInf, SwTextFrame const*const pFrame)
88 : m_pViewShell(nullptr)
89 , m_pFont(nullptr)
90 , m_pScriptInfo(nullptr)
91 , m_pLastOut(nullptr)
92 , m_nChgCnt(0)
93 , m_nPropFont(0)
94 , m_pTextNode(&rTextNode)
95 , m_pMergedPara(nullptr)
97 CtorInitAttrIter(rTextNode, rScrInf, pFrame);
100 void SwAttrIter::Chg( SwTextAttr const *pHt )
102 assert(pHt && m_pFont && "No attribute of font available for change");
103 if( m_pRedline && m_pRedline->IsOn() )
104 m_pRedline->ChangeTextAttr( m_pFont, *pHt, true );
105 else
106 m_aAttrHandler.PushAndChg( *pHt, *m_pFont );
107 m_nChgCnt++;
110 void SwAttrIter::Rst( SwTextAttr const *pHt )
112 assert(pHt && m_pFont && "No attribute of font available for reset");
113 // get top from stack after removing pHt
114 if( m_pRedline && m_pRedline->IsOn() )
115 m_pRedline->ChangeTextAttr( m_pFont, *pHt, false );
116 else
117 m_aAttrHandler.PopAndChg( *pHt, *m_pFont );
118 m_nChgCnt--;
121 SwAttrIter::~SwAttrIter()
123 m_pRedline.reset();
124 delete m_pFont;
127 bool SwAttrIter::MaybeHasHints() const
129 return nullptr != m_pTextNode->GetpSwpHints() || nullptr != m_pMergedPara;
133 * Returns the attribute for a position
135 * Only if the attribute is exactly at the position @param nPos and
136 * does not have an EndIndex
138 * We need this function for attributes which should alter formatting without
139 * changing the content of the string.
140 * Such "degenerated" attributes are e.g.: fields which retain expanded text and
141 * line-bound Frames.
142 * In order to avoid ambiguities between different such attributes, we insert a
143 * special character at the start of the string, when creating such an attribute.
144 * The Formatter later on encounters such a special character and retrieves the
145 * degenerate attribute via GetAttr().
147 SwTextAttr *SwAttrIter::GetAttr(TextFrameIndex const nPosition) const
149 std::pair<SwTextNode const*, sal_Int32> const pos( m_pMergedPara
150 ? sw::MapViewToModel(*m_pMergedPara, nPosition)
151 : std::make_pair(m_pTextNode, sal_Int32(nPosition)));
152 return pos.first->GetTextAttrForCharAt(pos.second);
155 bool SwAttrIter::SeekAndChgAttrIter(TextFrameIndex const nNewPos, OutputDevice* pOut)
157 std::pair<SwTextNode const*, sal_Int32> const pos( m_pMergedPara
158 ? sw::MapViewToModel(*m_pMergedPara, nNewPos)
159 : std::make_pair(m_pTextNode, sal_Int32(nNewPos)));
160 bool bChg = m_nStartIndex && pos.first == m_pTextNode && pos.second == m_nPosition
161 ? m_pFont->IsFntChg()
162 : Seek( nNewPos );
163 if ( m_pLastOut.get() != pOut )
165 m_pLastOut = pOut;
166 m_pFont->SetFntChg( true );
167 bChg = true;
169 if( bChg )
171 // if the change counter is zero, we know the cache id of the wanted font
172 if ( !m_nChgCnt && !m_nPropFont )
173 m_pFont->SetFontCacheId( m_aFontCacheIds[ m_pFont->GetActual() ],
174 m_aFontIdx[ m_pFont->GetActual() ], m_pFont->GetActual() );
175 m_pFont->ChgPhysFnt( m_pViewShell, *pOut );
178 return bChg;
181 bool SwAttrIter::IsSymbol(TextFrameIndex const nNewPos)
183 Seek( nNewPos );
184 if ( !m_nChgCnt && !m_nPropFont )
185 m_pFont->SetFontCacheId( m_aFontCacheIds[ m_pFont->GetActual() ],
186 m_aFontIdx[ m_pFont->GetActual() ], m_pFont->GetActual() );
187 return m_pFont->IsSymbol( m_pViewShell );
190 bool SwTextFrame::IsSymbolAt(TextFrameIndex const nPos) const
192 SwTextInfo info(const_cast<SwTextFrame*>(this));
193 SwTextIter iter(const_cast<SwTextFrame*>(this), &info);
194 return iter.IsSymbol(nPos);
197 bool SwAttrIter::SeekStartAndChgAttrIter( OutputDevice* pOut, const bool bParaFont )
199 SwTextNode const*const pFirstTextNode(m_pMergedPara ? m_pMergedPara->pFirstNode : m_pTextNode);
200 if ( m_pRedline && m_pRedline->ExtOn() )
201 m_pRedline->LeaveExtend(*m_pFont, pFirstTextNode->GetIndex(), 0);
203 if (m_pTextNode != pFirstTextNode)
205 assert(m_pMergedPara);
206 m_pTextNode = m_pMergedPara->pFirstNode;
207 InitFontAndAttrHandler(*m_pMergedPara->pParaPropsNode, *m_pTextNode,
208 m_pMergedPara->mergedText, nullptr, nullptr);
211 // reset font to its original state
212 m_aAttrHandler.Reset();
213 m_aAttrHandler.ResetFont( *m_pFont );
215 m_nStartIndex = 0;
216 m_nEndIndex = 0;
217 m_nPosition = 0;
218 m_nChgCnt = 0;
219 if( m_nPropFont )
220 m_pFont->SetProportion( m_nPropFont );
221 if( m_pRedline )
223 m_pRedline->Clear( m_pFont );
224 if( !bParaFont )
225 m_nChgCnt = m_nChgCnt + m_pRedline->Seek(*m_pFont, pFirstTextNode->GetIndex(), 0, COMPLETE_STRING);
226 else
227 m_pRedline->Reset();
230 SwpHints const*const pHints(m_pTextNode->GetpSwpHints());
231 if (pHints && !bParaFont)
233 SwTextAttr *pTextAttr;
234 // While we've not reached the end of the StartArray && the TextAttribute starts at position 0...
235 while ((m_nStartIndex < pHints->Count()) &&
236 !((pTextAttr = pHints->Get(m_nStartIndex))->GetStart()))
238 // open the TextAttributes
239 Chg( pTextAttr );
240 m_nStartIndex++;
244 bool bChg = m_pFont->IsFntChg();
245 if ( m_pLastOut.get() != pOut )
247 m_pLastOut = pOut;
248 m_pFont->SetFntChg( true );
249 bChg = true;
251 if( bChg )
253 // if the application counter is zero, we know the cache id of the wanted font
254 if ( !m_nChgCnt && !m_nPropFont )
255 m_pFont->SetFontCacheId( m_aFontCacheIds[ m_pFont->GetActual() ],
256 m_aFontIdx[ m_pFont->GetActual() ], m_pFont->GetActual() );
257 m_pFont->ChgPhysFnt( m_pViewShell, *pOut );
259 return bChg;
262 // AMA: New AttrIter Nov 94
263 void SwAttrIter::SeekFwd(const sal_Int32 nOldPos, const sal_Int32 nNewPos)
265 SwpHints const*const pHints(m_pTextNode->GetpSwpHints());
266 SwTextAttr *pTextAttr;
267 const auto nHintsCount = pHints->Count();
269 if ( m_nStartIndex ) // If attributes have been opened at all ...
271 // Close attributes that are currently open, but stop at nNewPos+1
273 // As long as we've not yet reached the end of EndArray and the
274 // TextAttribute ends before or at the new position ...
275 while ((m_nEndIndex < nHintsCount) &&
276 ((pTextAttr = pHints->GetSortedByEnd(m_nEndIndex))->GetAnyEnd() <= nNewPos))
278 // Close the TextAttributes, whose StartPos were before or at
279 // the old nPos and are currently open
280 if (pTextAttr->GetStart() <= nOldPos) Rst( pTextAttr );
281 m_nEndIndex++;
284 else // skip the not opened ends
286 while ((m_nEndIndex < nHintsCount) &&
287 (pHints->GetSortedByEnd(m_nEndIndex)->GetAnyEnd() <= nNewPos))
289 m_nEndIndex++;
293 // As long as we've not yet reached the end of EndArray and the
294 // TextAttribute ends before or at the new position...
295 while ((m_nStartIndex < nHintsCount) &&
296 ((pTextAttr = pHints->Get(m_nStartIndex))->GetStart() <= nNewPos))
299 // open the TextAttributes, whose ends lie behind the new position
300 if ( pTextAttr->GetAnyEnd() > nNewPos ) Chg( pTextAttr );
301 m_nStartIndex++;
306 bool SwAttrIter::Seek(TextFrameIndex const nNewPos)
308 // note: nNewPos isn't necessarily an index returned from GetNextAttr
309 std::pair<SwTextNode const*, sal_Int32> const newPos( m_pMergedPara
310 ? sw::MapViewToModel(*m_pMergedPara, nNewPos)
311 : std::make_pair(m_pTextNode, sal_Int32(nNewPos)));
313 if ( m_pRedline && m_pRedline->ExtOn() )
314 m_pRedline->LeaveExtend(*m_pFont, newPos.first->GetIndex(), newPos.second);
315 if (m_pTextNode->GetIndex() < newPos.first->GetIndex())
317 // Skipping to a different node - first seek until the end of this node
318 // to get rid of all hint items
319 if (m_pTextNode->GetpSwpHints())
321 sal_Int32 nPos(m_nPosition);
324 sal_Int32 const nOldPos(nPos);
325 nPos = GetNextAttrImpl(m_pTextNode, m_nStartIndex, m_nEndIndex, nPos);
326 if (nPos <= m_pTextNode->Len())
328 SeekFwd(nOldPos, nPos);
330 else
332 SeekFwd(nOldPos, m_pTextNode->Len());
335 while (nPos < m_pTextNode->Len());
337 // Unapply current para items:
338 // the SwAttrHandler doesn't appear to be capable of *unapplying*
339 // items at all; it can only apply a previously effective item.
340 // So do this by recreating the font from scratch.
341 // Apply new para items:
342 assert(m_pMergedPara);
343 InitFontAndAttrHandler(*m_pMergedPara->pParaPropsNode, *newPos.first,
344 m_pMergedPara->mergedText, nullptr, nullptr);
345 // reset to next
346 m_pTextNode = newPos.first;
347 m_nStartIndex = 0;
348 m_nEndIndex = 0;
349 m_nPosition = 0;
350 assert(m_pRedline);
353 // sw_redlinehide: Seek(0) must move before the first character, which
354 // has a special case where the first node starts with delete redline.
355 if ((!nNewPos && !m_pMergedPara)
356 || newPos.first != m_pTextNode
357 || newPos.second < m_nPosition)
359 if (m_pMergedPara)
361 if (m_pTextNode != newPos.first)
363 m_pTextNode = newPos.first;
364 // sw_redlinehide: hope it's okay to use the current text node
365 // here; the AttrHandler shouldn't care about non-char items
366 InitFontAndAttrHandler(*m_pMergedPara->pParaPropsNode, *m_pTextNode,
367 m_pMergedPara->mergedText, nullptr, nullptr);
370 if (m_pMergedPara || m_pTextNode->GetpSwpHints())
372 if( m_pRedline )
373 m_pRedline->Clear( nullptr );
375 // reset font to its original state
376 m_aAttrHandler.Reset();
377 m_aAttrHandler.ResetFont( *m_pFont );
379 if( m_nPropFont )
380 m_pFont->SetProportion( m_nPropFont );
381 m_nStartIndex = 0;
382 m_nEndIndex = 0;
383 m_nPosition = 0;
384 m_nChgCnt = 0;
386 // Attention!
387 // resetting the font here makes it necessary to apply any
388 // changes for extended input directly to the font
389 if ( m_pRedline && m_pRedline->ExtOn() )
391 m_pRedline->UpdateExtFont( *m_pFont );
392 ++m_nChgCnt;
397 if (m_pTextNode->GetpSwpHints())
399 if (m_pMergedPara)
401 // iterate hint by hint: SeekFwd does not mix ends and starts,
402 // it always applies all the starts last, so it must be called once
403 // per position where hints start/end!
404 sal_Int32 nPos(m_nPosition);
407 sal_Int32 const nOldPos(nPos);
408 nPos = GetNextAttrImpl(m_pTextNode, m_nStartIndex, m_nEndIndex, nPos);
409 if (nPos <= newPos.second)
411 SeekFwd(nOldPos, nPos);
413 else
415 SeekFwd(nOldPos, newPos.second);
418 while (nPos < newPos.second);
420 else
422 SeekFwd(m_nPosition, newPos.second);
426 m_pFont->SetActual( m_pScriptInfo->WhichFont(nNewPos) );
428 if( m_pRedline )
429 m_nChgCnt = m_nChgCnt + m_pRedline->Seek(*m_pFont, m_pTextNode->GetIndex(), newPos.second, m_nPosition);
430 m_nPosition = newPos.second;
432 if( m_nPropFont )
433 m_pFont->SetProportion( m_nPropFont );
435 return m_pFont->IsFntChg();
438 static void InsertCharAttrs(SfxPoolItem const** pAttrs, SfxItemSet const& rItems)
440 SfxItemIter iter(rItems);
441 for (SfxPoolItem const* pItem = iter.GetCurItem(); pItem; pItem = iter.NextItem())
443 auto const nWhich(pItem->Which());
444 if (isCHRATR(nWhich) && RES_CHRATR_RSID != nWhich)
446 pAttrs[nWhich - RES_CHRATR_BEGIN] = pItem;
448 else if (nWhich == RES_TXTATR_UNKNOWN_CONTAINER)
450 pAttrs[RES_CHRATR_END - RES_CHRATR_BEGIN] = pItem;
455 // if return false: portion ends at start of redline, indexes unchanged
456 // if return true: portion end not known (past end of redline), indexes point to first hint past end of redline
457 static bool CanSkipOverRedline(
458 SwTextNode const& rStartNode, sal_Int32 const nStartRedline,
459 SwRangeRedline const& rRedline,
460 size_t & rStartIndex, size_t & rEndIndex,
461 bool const isTheAnswerYes)
463 size_t nStartIndex(rStartIndex);
464 size_t nEndIndex(rEndIndex);
465 SwPosition const*const pRLEnd(rRedline.End());
466 if (!pRLEnd->GetNode().IsTextNode() // if fully deleted...
467 || pRLEnd->GetContentIndex() == pRLEnd->GetNode().GetTextNode()->Len())
469 // shortcut: nothing follows redline
470 // current state is end state
471 return false;
473 std::vector<SwTextAttr*> activeCharFmts;
474 // can't compare the SwFont that's stored somewhere, it doesn't have compare
475 // operator, so try to recreate the situation with some temp arrays here
476 SfxPoolItem const* activeCharAttrsStart[RES_CHRATR_END - RES_CHRATR_BEGIN + 1] = { nullptr, };
477 if (rStartNode != pRLEnd->GetNode())
478 { // nodes' attributes are only needed if there are different nodes
479 InsertCharAttrs(activeCharAttrsStart, rStartNode.GetSwAttrSet());
481 if (SwpHints const*const pStartHints = rStartNode.GetpSwpHints())
483 // check hint ends of hints that start before and end within
484 sal_Int32 const nRedlineEnd(rStartNode == pRLEnd->GetNode()
485 ? pRLEnd->GetContentIndex()
486 : rStartNode.Len());
487 for ( ; nEndIndex < pStartHints->Count(); ++nEndIndex)
489 SwTextAttr *const pAttr(pStartHints->GetSortedByEnd(nEndIndex));
490 if (!pAttr->End())
492 continue;
494 if (nRedlineEnd < *pAttr->End())
496 break;
498 if (nStartRedline <= pAttr->GetStart())
500 continue;
502 if (pAttr->IsFormatIgnoreEnd())
504 continue;
506 switch (pAttr->Which())
508 // if any of these ends inside RL then we need a new portion
509 case RES_TXTATR_REFMARK:
510 case RES_TXTATR_TOXMARK:
511 case RES_TXTATR_META: // actually these 2 aren't allowed to overlap ???
512 case RES_TXTATR_METAFIELD:
513 case RES_TXTATR_INETFMT:
514 case RES_TXTATR_CJK_RUBY:
515 case RES_TXTATR_INPUTFIELD:
516 case RES_TXTATR_CONTENTCONTROL:
518 if (!isTheAnswerYes) return false; // always break
520 break;
521 // these are guaranteed not to overlap
522 // and come in order of application
523 case RES_TXTATR_AUTOFMT:
524 case RES_TXTATR_CHARFMT:
526 if (pAttr->Which() == RES_TXTATR_CHARFMT)
528 activeCharFmts.push_back(pAttr);
530 // pure formatting hints may end inside the redline &
531 // start again inside the redline, which must not cause
532 // a new text portion if they have the same items - so
533 // store the effective items & compare all at the end
534 SfxItemSet const& rSet((pAttr->Which() == RES_TXTATR_CHARFMT)
535 ? static_cast<SfxItemSet const&>(pAttr->GetCharFormat().GetCharFormat()->GetAttrSet())
536 : *pAttr->GetAutoFormat().GetStyleHandle());
537 InsertCharAttrs(activeCharAttrsStart, rSet);
539 break;
540 // SwTextNode::SetAttr puts it into AUTOFMT which is quite
541 // sensible so it doesn't actually exist as a hint
542 case RES_TXTATR_UNKNOWN_CONTAINER:
543 default: assert(false);
546 assert(nEndIndex == pStartHints->Count() ||
547 pRLEnd->GetContentIndex() < pStartHints->GetSortedByEnd(nEndIndex)->GetAnyEnd());
550 if (rStartNode != pRLEnd->GetNode())
552 nStartIndex = 0;
553 nEndIndex = 0;
556 // treat para properties as text properties
557 // ... with the FormatToTextAttr we get autofmts that correspond to the *effective* attr set difference
558 // effective attr set: para props + charfmts + autofmt *in that order*
559 // ... and the charfmt must be *nominally* the same
561 SfxPoolItem const* activeCharAttrsEnd[RES_CHRATR_END - RES_CHRATR_BEGIN + 1] = { nullptr, };
562 if (rStartNode != pRLEnd->GetNode())
563 { // nodes' attributes are only needed if there are different nodes
564 InsertCharAttrs(activeCharAttrsEnd,
565 pRLEnd->GetNode().GetTextNode()->GetSwAttrSet());
568 if (SwpHints *const pEndHints = pRLEnd->GetNode().GetTextNode()->GetpSwpHints())
570 // check hint starts of hints that start within and end after
571 #ifndef NDEBUG
572 sal_Int32 const nRedlineStart(rStartNode == pRLEnd->GetNode()
573 ? nStartRedline
574 : 0);
575 #endif
576 for ( ; nStartIndex < pEndHints->Count(); ++nStartIndex)
578 SwTextAttr *const pAttr(pEndHints->Get(nStartIndex));
579 // compare with < here, not <=, to get the effective formatting
580 // of the 1st char after the redline; should not cause problems
581 // with consecutive delete redlines because those are handed by
582 // GetNextRedln() and here we have the last end pos.
583 if (pRLEnd->GetContentIndex() < pAttr->GetStart())
585 break;
587 if (!pAttr->End())
588 continue;
589 if (pAttr->IsFormatIgnoreStart())
591 continue;
593 assert(nRedlineStart <= pAttr->GetStart()); // we wouldn't be here otherwise?
594 if (*pAttr->End() <= pRLEnd->GetContentIndex())
596 continue;
598 switch (pAttr->Which())
600 case RES_TXTATR_REFMARK:
601 case RES_TXTATR_TOXMARK:
602 case RES_TXTATR_META: // actually these 2 aren't allowed to overlap ???
603 case RES_TXTATR_METAFIELD:
604 case RES_TXTATR_INETFMT:
605 case RES_TXTATR_CJK_RUBY:
606 case RES_TXTATR_INPUTFIELD:
607 case RES_TXTATR_CONTENTCONTROL:
609 if (!isTheAnswerYes) return false;
611 break;
612 case RES_TXTATR_AUTOFMT:
613 case RES_TXTATR_CHARFMT:
615 // char formats must be *nominally* the same
616 if (pAttr->Which() == RES_TXTATR_CHARFMT)
618 auto iter = std::find_if(activeCharFmts.begin(), activeCharFmts.end(),
619 [&pAttr](const SwTextAttr* pCharFmt) { return *pCharFmt == *pAttr; });
620 if (iter != activeCharFmts.end())
621 activeCharFmts.erase(iter);
622 else if (!isTheAnswerYes)
623 return false;
625 SfxItemSet const& rSet((pAttr->Which() == RES_TXTATR_CHARFMT)
626 ? static_cast<SfxItemSet const&>(pAttr->GetCharFormat().GetCharFormat()->GetAttrSet())
627 : *pAttr->GetAutoFormat().GetStyleHandle());
628 InsertCharAttrs(activeCharAttrsEnd, rSet);
631 break;
632 // SwTextNode::SetAttr puts it into AUTOFMT which is quite
633 // sensible so it doesn't actually exist as a hint
634 case RES_TXTATR_UNKNOWN_CONTAINER:
635 default: assert(false);
638 if (rStartNode != pRLEnd->GetNode())
640 // need to iterate the nEndIndex forward too so the loop in the
641 // caller can look for the right ends in the next iteration
642 for (nEndIndex = 0; nEndIndex < pEndHints->Count(); ++nEndIndex)
644 SwTextAttr *const pAttr(pEndHints->GetSortedByEnd(nEndIndex));
645 if (!pAttr->End())
646 continue;
647 if (pRLEnd->GetContentIndex() < *pAttr->End())
649 break;
655 // if we didn't find a matching start for any end, then it really ends inside
656 if (!activeCharFmts.empty())
658 if (!isTheAnswerYes) return false;
660 for (size_t i = 0; i < SAL_N_ELEMENTS(activeCharAttrsStart); ++i)
662 // all of these are poolable
663 // assert(!activeCharAttrsStart[i] || activeCharAttrsStart[i]->GetItemPool()->IsItemPoolable(*activeCharAttrsStart[i]));
664 if (activeCharAttrsStart[i] != activeCharAttrsEnd[i])
666 if (!isTheAnswerYes) return false;
669 rStartIndex = nStartIndex;
670 rEndIndex = nEndIndex;
671 return true;
674 static sal_Int32 GetNextAttrImpl(SwTextNode const*const pTextNode,
675 size_t const nStartIndex, size_t const nEndIndex,
676 sal_Int32 const nPosition)
678 // note: this used to be COMPLETE_STRING, but was set to Len() + 1 below,
679 // which is rather silly, so set it to Len() instead
680 sal_Int32 nNext = pTextNode->Len();
681 if (SwpHints const*const pHints = pTextNode->GetpSwpHints())
683 // are there attribute starts left?
684 for (size_t i = nStartIndex; i < pHints->Count(); ++i)
686 SwTextAttr *const pAttr(pHints->Get(i));
687 if (!pAttr->IsFormatIgnoreStart())
689 nNext = pAttr->GetStart();
690 break;
693 // are there attribute ends left?
694 for (size_t i = nEndIndex; i < pHints->Count(); ++i)
696 SwTextAttr *const pAttr(pHints->GetSortedByEnd(i));
697 if (!pAttr->IsFormatIgnoreEnd())
699 sal_Int32 const nNextEnd = pAttr->GetAnyEnd();
700 nNext = std::min(nNext, nNextEnd); // pick nearest one
701 break;
705 // TODO: maybe use hints like FieldHints for this instead of looking at the text...
706 const sal_Int32 l = std::min(nNext, pTextNode->Len());
707 sal_Int32 p = nPosition;
708 const sal_Unicode* pStr = pTextNode->GetText().getStr();
709 while (p < l)
711 sal_Unicode aChar = pStr[p];
712 switch (aChar)
714 case CH_TXT_ATR_FORMELEMENT:
715 case CH_TXT_ATR_FIELDSTART:
716 case CH_TXT_ATR_FIELDSEP:
717 case CH_TXT_ATR_FIELDEND:
718 goto break_; // sigh...
719 default:
720 ++p;
723 break_:
724 assert(p <= nNext);
725 if (p < l)
727 // found a CH_TXT_ATR_FIELD*: if it's same as current position,
728 // skip behind it so that both before- and after-positions are returned
729 nNext = (nPosition < p) ? p : p + 1;
731 return nNext;
734 TextFrameIndex SwAttrIter::GetNextAttr() const
736 size_t nStartIndex(m_nStartIndex);
737 size_t nEndIndex(m_nEndIndex);
738 size_t nPosition(m_nPosition);
739 SwTextNode const* pTextNode(m_pTextNode);
740 SwRedlineTable::size_type nActRedline(m_pRedline ? m_pRedline->GetAct() : SwRedlineTable::npos);
742 while (true)
744 sal_Int32 nNext = GetNextAttrImpl(pTextNode, nStartIndex, nEndIndex, nPosition);
745 if( m_pRedline )
747 std::pair<sal_Int32, std::pair<SwRangeRedline const*, size_t>> const redline(
748 m_pRedline->GetNextRedln(nNext, pTextNode, nActRedline));
749 if (redline.second.first)
751 assert(m_pMergedPara);
752 assert(redline.second.first->End()->GetNodeIndex() <= m_pMergedPara->pLastNode->GetIndex()
753 || !redline.second.first->End()->GetNode().IsTextNode());
754 if (CanSkipOverRedline(*pTextNode, redline.first, *redline.second.first,
755 nStartIndex, nEndIndex, m_nPosition == redline.first))
756 { // if current position is start of the redline, must skip!
757 nActRedline += redline.second.second;
758 if (&redline.second.first->End()->GetNode() != pTextNode)
760 pTextNode = redline.second.first->End()->GetNode().GetTextNode();
761 nPosition = redline.second.first->End()->GetContentIndex();
763 else
765 nPosition = redline.second.first->End()->GetContentIndex();
768 else
770 return sw::MapModelToView(*m_pMergedPara, pTextNode, redline.first);
773 else
775 return m_pMergedPara
776 ? sw::MapModelToView(*m_pMergedPara, pTextNode, redline.first)
777 : TextFrameIndex(redline.first);
780 else
782 return TextFrameIndex(nNext);
787 namespace {
789 class SwMinMaxArgs
791 public:
792 VclPtr<OutputDevice> m_pOut;
793 SwViewShell const* m_pSh;
794 sal_uLong& m_rMin;
795 sal_uLong& m_rAbsMin;
796 tools::Long m_nRowWidth;
797 tools::Long m_nWordWidth;
798 tools::Long m_nWordAdd;
799 sal_Int32 m_nNoLineBreak;
800 SwMinMaxArgs(OutputDevice* pOutI, SwViewShell const* pShI, sal_uLong& rMinI, sal_uLong& rAbsI)
801 : m_pOut(pOutI)
802 , m_pSh(pShI)
803 , m_rMin(rMinI)
804 , m_rAbsMin(rAbsI)
805 , m_nRowWidth(0)
806 , m_nWordWidth(0)
807 , m_nWordAdd(0)
808 , m_nNoLineBreak(COMPLETE_STRING)
810 void Minimum( tools::Long nNew ) const {
811 if (static_cast<tools::Long>(m_rMin) < nNew)
812 m_rMin = nNew;
814 void NewWord() { m_nWordAdd = m_nWordWidth = 0; }
819 static bool lcl_MinMaxString( SwMinMaxArgs& rArg, SwFont* pFnt, const OUString &rText,
820 sal_Int32 nIdx, sal_Int32 nEnd )
822 bool bRet = false;
823 while( nIdx < nEnd )
825 sal_Int32 nStop = nIdx;
826 LanguageType eLang = pFnt->GetLanguage();
827 assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
829 bool bClear = CH_BLANK == rText[ nStop ];
830 Boundary aBndry( g_pBreakIt->GetBreakIter()->getWordBoundary( rText, nIdx,
831 g_pBreakIt->GetLocale( eLang ),
832 WordType::DICTIONARY_WORD, true ) );
833 nStop = aBndry.endPos;
834 if (nIdx <= aBndry.startPos && nIdx && nIdx - 1 != rArg.m_nNoLineBreak)
835 rArg.NewWord();
836 if( nStop == nIdx )
837 ++nStop;
838 if( nStop > nEnd )
839 nStop = nEnd;
841 SwDrawTextInfo aDrawInf(rArg.m_pSh, *rArg.m_pOut, rText, nIdx, nStop - nIdx);
842 tools::Long nCurrentWidth = pFnt->GetTextSize_( aDrawInf ).Width();
843 rArg.m_nRowWidth += nCurrentWidth;
844 if( bClear )
845 rArg.NewWord();
846 else
848 rArg.m_nWordWidth += nCurrentWidth;
849 if (static_cast<tools::Long>(rArg.m_rAbsMin) < rArg.m_nWordWidth)
850 rArg.m_rAbsMin = rArg.m_nWordWidth;
851 rArg.Minimum(rArg.m_nWordWidth + rArg.m_nWordAdd);
852 bRet = true;
854 nIdx = nStop;
856 return bRet;
859 bool SwTextNode::IsSymbolAt(const sal_Int32 nBegin) const
861 SwScriptInfo aScriptInfo;
862 SwAttrIter aIter( *const_cast<SwTextNode*>(this), aScriptInfo );
863 aIter.Seek( TextFrameIndex(nBegin) );
864 return aIter.GetFnt()->IsSymbol( getIDocumentLayoutAccess().GetCurrentViewShell() );
867 namespace {
869 class SwMinMaxNodeArgs
871 public:
872 sal_uLong m_nMaxWidth; // sum of all frame widths
873 tools::Long m_nMinWidth; // biggest frame
874 tools::Long m_nLeftRest; // space not already covered by frames in the left margin
875 tools::Long m_nRightRest; // space not already covered by frames in the right margin
876 tools::Long m_nLeftDiff; // Min/Max-difference of the frame in the left margin
877 tools::Long m_nRightDiff; // Min/Max-difference of the frame in the right margin
878 SwNodeOffset m_nIndex; // index of the node
879 void Minimum( tools::Long nNew ) {
880 if (nNew > m_nMinWidth)
881 m_nMinWidth = nNew;
887 static void lcl_MinMaxNode(SwFrameFormat* pNd, SwMinMaxNodeArgs& rIn)
889 const SwFormatAnchor& rFormatA = pNd->GetAnchor();
891 if ((RndStdIds::FLY_AT_PARA != rFormatA.GetAnchorId()) &&
892 (RndStdIds::FLY_AT_CHAR != rFormatA.GetAnchorId()))
894 return;
897 const SwNode *pAnchorNode = rFormatA.GetAnchorNode();
898 OSL_ENSURE(pAnchorNode, "Unexpected NULL arguments");
899 if (!pAnchorNode || rIn.m_nIndex != pAnchorNode->GetIndex())
900 return;
902 tools::Long nMin, nMax;
903 SwHTMLTableLayout *pLayout = nullptr;
904 const bool bIsDrawFrameFormat = pNd->Which()==RES_DRAWFRMFMT;
905 if( !bIsDrawFrameFormat )
907 // Does the frame contain a table at the start or the end?
908 const SwNodes& rNodes = pNd->GetDoc()->GetNodes();
909 const SwFormatContent& rFlyContent = pNd->GetContent();
910 SwNodeOffset nStt = rFlyContent.GetContentIdx()->GetIndex();
911 SwTableNode* pTableNd = rNodes[nStt+1]->GetTableNode();
912 if( !pTableNd )
914 SwNode *pNd2 = rNodes[nStt];
915 pNd2 = rNodes[pNd2->EndOfSectionIndex()-1];
916 if( pNd2->IsEndNode() )
917 pTableNd = pNd2->StartOfSectionNode()->GetTableNode();
920 if( pTableNd )
921 pLayout = pTableNd->GetTable().GetHTMLTableLayout();
924 const SwFormatHoriOrient& rOrient = pNd->GetHoriOrient();
925 sal_Int16 eHoriOri = rOrient.GetHoriOrient();
927 tools::Long nDiff;
928 if( pLayout )
930 nMin = pLayout->GetMin();
931 nMax = pLayout->GetMax();
932 nDiff = nMax - nMin;
934 else
936 if( bIsDrawFrameFormat )
938 const SdrObject* pSObj = pNd->FindSdrObject();
939 if( pSObj )
940 nMin = pSObj->GetCurrentBoundRect().GetWidth();
941 else
942 nMin = 0;
945 else
947 const SwFormatFrameSize &rSz = pNd->GetFrameSize();
948 nMin = rSz.GetWidth();
950 nMax = nMin;
951 nDiff = 0;
954 const SvxLRSpaceItem &rLR = pNd->GetLRSpace();
955 nMin += rLR.GetLeft();
956 nMin += rLR.GetRight();
957 nMax += rLR.GetLeft();
958 nMax += rLR.GetRight();
960 if( css::text::WrapTextMode_THROUGH == pNd->GetSurround().GetSurround() )
962 rIn.Minimum( nMin );
963 return;
966 // Frames, which are left- or right-aligned are only party considered
967 // when calculating the maximum, since the border is already being considered.
968 // Only if the frame extends into the text body, this part is being added
969 switch( eHoriOri )
971 case text::HoriOrientation::RIGHT:
973 if( nDiff )
975 rIn.m_nRightRest -= rIn.m_nRightDiff;
976 rIn.m_nRightDiff = nDiff;
978 if( text::RelOrientation::FRAME != rOrient.GetRelationOrient() )
980 if (rIn.m_nRightRest > 0)
981 rIn.m_nRightRest = 0;
983 rIn.m_nRightRest -= nMin;
984 break;
986 case text::HoriOrientation::LEFT:
988 if( nDiff )
990 rIn.m_nLeftRest -= rIn.m_nLeftDiff;
991 rIn.m_nLeftDiff = nDiff;
993 if (text::RelOrientation::FRAME != rOrient.GetRelationOrient() && rIn.m_nLeftRest < 0)
994 rIn.m_nLeftRest = 0;
995 rIn.m_nLeftRest -= nMin;
996 break;
998 default:
1000 rIn.m_nMaxWidth += nMax;
1001 rIn.Minimum(nMin);
1006 #define FLYINCNT_MIN_WIDTH 284
1009 * Changing this method very likely requires changing of GetScalingOfSelectedText
1010 * This one is called exclusively from import filters, so there is no layout.
1012 void SwTextNode::GetMinMaxSize( SwNodeOffset nIndex, sal_uLong& rMin, sal_uLong &rMax,
1013 sal_uLong& rAbsMin ) const
1015 SwViewShell const * pSh = GetDoc().getIDocumentLayoutAccess().GetCurrentViewShell();
1016 OutputDevice* pOut = nullptr;
1017 if( pSh )
1018 pOut = pSh->GetWin()->GetOutDev();
1019 if( !pOut )
1020 pOut = Application::GetDefaultDevice();
1022 MapMode aOldMap( pOut->GetMapMode() );
1023 pOut->SetMapMode( MapMode( MapUnit::MapTwip ) );
1025 rMin = 0;
1026 rMax = 0;
1027 rAbsMin = 0;
1029 SvxTextLeftMarginItem const& rTextLeftMargin(GetSwAttrSet().GetTextLeftMargin());
1030 SvxRightMarginItem const& rRightMargin(GetSwAttrSet().GetRightMargin());
1031 tools::Long nLROffset = rTextLeftMargin.GetTextLeft() + GetLeftMarginWithNum( true );
1032 short nFLOffs;
1033 // For enumerations a negative first line indentation is probably filled already
1034 if( !GetFirstLineOfsWithNum( nFLOffs ) || nFLOffs > nLROffset )
1035 nLROffset = nFLOffs;
1037 SwMinMaxNodeArgs aNodeArgs;
1038 aNodeArgs.m_nMinWidth = 0;
1039 aNodeArgs.m_nMaxWidth = 0;
1040 aNodeArgs.m_nLeftRest = nLROffset;
1041 aNodeArgs.m_nRightRest = rRightMargin.GetRight();
1042 aNodeArgs.m_nLeftDiff = 0;
1043 aNodeArgs.m_nRightDiff = 0;
1044 if( nIndex )
1046 sw::SpzFrameFormats* pSpzs = const_cast<sw::SpzFrameFormats*>(GetDoc().GetSpzFrameFormats());
1047 if(pSpzs)
1049 aNodeArgs.m_nIndex = nIndex;
1050 for(auto pFormat: *pSpzs)
1051 lcl_MinMaxNode(pFormat, aNodeArgs);
1054 if (aNodeArgs.m_nLeftRest < 0)
1055 aNodeArgs.Minimum(nLROffset - aNodeArgs.m_nLeftRest);
1056 aNodeArgs.m_nLeftRest -= aNodeArgs.m_nLeftDiff;
1057 if (aNodeArgs.m_nLeftRest < 0)
1058 aNodeArgs.m_nMaxWidth -= aNodeArgs.m_nLeftRest;
1060 if (aNodeArgs.m_nRightRest < 0)
1061 aNodeArgs.Minimum(rRightMargin.GetRight() - aNodeArgs.m_nRightRest);
1062 aNodeArgs.m_nRightRest -= aNodeArgs.m_nRightDiff;
1063 if (aNodeArgs.m_nRightRest < 0)
1064 aNodeArgs.m_nMaxWidth -= aNodeArgs.m_nRightRest;
1066 SwScriptInfo aScriptInfo;
1067 SwAttrIter aIter( *const_cast<SwTextNode*>(this), aScriptInfo );
1068 TextFrameIndex nIdx(0);
1069 aIter.SeekAndChgAttrIter( nIdx, pOut );
1070 TextFrameIndex nLen(m_Text.getLength());
1071 tools::Long nCurrentWidth = 0;
1072 tools::Long nAdd = 0;
1073 SwMinMaxArgs aArg( pOut, pSh, rMin, rAbsMin );
1074 while( nIdx < nLen )
1076 TextFrameIndex nNextChg = aIter.GetNextAttr();
1077 TextFrameIndex nStop = aScriptInfo.NextScriptChg( nIdx );
1078 if( nNextChg > nStop )
1079 nNextChg = nStop;
1080 SwTextAttr *pHint = nullptr;
1081 sal_Unicode cChar = CH_BLANK;
1082 nStop = nIdx;
1083 while( nStop < nLen && nStop < nNextChg &&
1084 CH_TAB != (cChar = m_Text[sal_Int32(nStop)]) &&
1085 CH_BREAK != cChar && CHAR_HARDBLANK != cChar &&
1086 CHAR_HARDHYPHEN != cChar && CHAR_SOFTHYPHEN != cChar &&
1087 CH_TXT_ATR_INPUTFIELDSTART != cChar &&
1088 CH_TXT_ATR_INPUTFIELDEND != cChar &&
1089 CH_TXT_ATR_FORMELEMENT != cChar &&
1090 CH_TXT_ATR_FIELDSTART != cChar &&
1091 CH_TXT_ATR_FIELDSEP != cChar &&
1092 CH_TXT_ATR_FIELDEND != cChar &&
1093 !pHint )
1095 // this looks like some defensive programming to handle dummy char
1096 // with missing hint? but it's rather silly because it may pass the
1097 // dummy char to lcl_MinMaxString in that case...
1098 if( ( CH_TXTATR_BREAKWORD != cChar && CH_TXTATR_INWORD != cChar )
1099 || ( nullptr == ( pHint = aIter.GetAttr( nStop ) ) ) )
1100 ++nStop;
1102 if (lcl_MinMaxString(aArg, aIter.GetFnt(), m_Text, sal_Int32(nIdx), sal_Int32(nStop)))
1104 nAdd = 20;
1106 nIdx = nStop;
1107 aIter.SeekAndChgAttrIter( nIdx, pOut );
1108 switch( cChar )
1110 case CH_BREAK :
1112 if (static_cast<tools::Long>(rMax) < aArg.m_nRowWidth)
1113 rMax = aArg.m_nRowWidth;
1114 aArg.m_nRowWidth = 0;
1115 aArg.NewWord();
1116 aIter.SeekAndChgAttrIter( ++nIdx, pOut );
1118 break;
1119 case CH_TAB :
1121 aArg.NewWord();
1122 aIter.SeekAndChgAttrIter( ++nIdx, pOut );
1124 break;
1125 case CHAR_SOFTHYPHEN:
1126 ++nIdx;
1127 break;
1128 case CHAR_HARDBLANK:
1129 case CHAR_HARDHYPHEN:
1131 OUString sTmp( cChar );
1132 SwDrawTextInfo aDrawInf( pSh,
1133 *pOut, sTmp, 0, 1, 0, false );
1134 nCurrentWidth = aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
1135 aArg.m_nWordWidth += nCurrentWidth;
1136 aArg.m_nRowWidth += nCurrentWidth;
1137 if (static_cast<tools::Long>(rAbsMin) < aArg.m_nWordWidth)
1138 rAbsMin = aArg.m_nWordWidth;
1139 aArg.Minimum(aArg.m_nWordWidth + aArg.m_nWordAdd);
1140 aArg.m_nNoLineBreak = sal_Int32(nIdx++);
1142 break;
1143 case CH_TXTATR_BREAKWORD:
1144 case CH_TXTATR_INWORD:
1146 if( !pHint )
1147 break;
1148 tools::Long nOldWidth = aArg.m_nWordWidth;
1149 tools::Long nOldAdd = aArg.m_nWordAdd;
1150 aArg.NewWord();
1152 switch( pHint->Which() )
1154 case RES_TXTATR_FLYCNT :
1156 SwFrameFormat *pFrameFormat = pHint->GetFlyCnt().GetFrameFormat();
1157 const SvxLRSpaceItem &rLR = pFrameFormat->GetLRSpace();
1158 if( RES_DRAWFRMFMT == pFrameFormat->Which() )
1160 const SdrObject* pSObj = pFrameFormat->FindSdrObject();
1161 if( pSObj )
1162 nCurrentWidth = pSObj->GetCurrentBoundRect().GetWidth();
1163 else
1164 nCurrentWidth = 0;
1166 else
1168 const SwFormatFrameSize& rTmpSize = pFrameFormat->GetFrameSize();
1169 if( RES_FLYFRMFMT == pFrameFormat->Which()
1170 && rTmpSize.GetWidthPercent() )
1172 // This is a hack for the following situation: In the paragraph there's a
1173 // text frame with relative size. Then let's take 0.5 cm as minimum width
1174 // and USHRT_MAX as maximum width
1175 // It were cleaner and maybe necessary later on to iterate over the content
1176 // of the text frame and call GetMinMaxSize recursively
1177 nCurrentWidth = FLYINCNT_MIN_WIDTH; // 0.5 cm
1178 rMax = std::max(rMax, sal_uLong(USHRT_MAX));
1180 else
1181 nCurrentWidth = pFrameFormat->GetFrameSize().GetWidth();
1183 nCurrentWidth += rLR.GetLeft();
1184 nCurrentWidth += rLR.GetRight();
1185 aArg.m_nWordAdd = nOldWidth + nOldAdd;
1186 aArg.m_nWordWidth = nCurrentWidth;
1187 aArg.m_nRowWidth += nCurrentWidth;
1188 if (static_cast<tools::Long>(rAbsMin) < aArg.m_nWordWidth)
1189 rAbsMin = aArg.m_nWordWidth;
1190 aArg.Minimum(aArg.m_nWordWidth + aArg.m_nWordAdd);
1191 break;
1193 case RES_TXTATR_FTN :
1195 const OUString aText = pHint->GetFootnote().GetNumStr();
1196 if( lcl_MinMaxString( aArg, aIter.GetFnt(), aText, 0,
1197 aText.getLength() ) )
1198 nAdd = 20;
1199 break;
1202 case RES_TXTATR_FIELD :
1203 case RES_TXTATR_ANNOTATION :
1205 SwField *pField = const_cast<SwField*>(pHint->GetFormatField().GetField());
1206 const OUString aText = pField->ExpandField(true, nullptr);
1207 if( lcl_MinMaxString( aArg, aIter.GetFnt(), aText, 0,
1208 aText.getLength() ) )
1209 nAdd = 20;
1210 break;
1212 default:
1213 aArg.m_nWordWidth = nOldWidth;
1214 aArg.m_nWordAdd = nOldAdd;
1216 aIter.SeekAndChgAttrIter( ++nIdx, pOut );
1218 break;
1219 case CH_TXT_ATR_INPUTFIELDSTART:
1220 case CH_TXT_ATR_INPUTFIELDEND:
1221 case CH_TXT_ATR_FORMELEMENT:
1222 case CH_TXT_ATR_FIELDSTART:
1223 case CH_TXT_ATR_FIELDSEP:
1224 case CH_TXT_ATR_FIELDEND:
1225 { // just skip it and continue with the content...
1226 aIter.SeekAndChgAttrIter( ++nIdx, pOut );
1228 break;
1231 if (static_cast<tools::Long>(rMax) < aArg.m_nRowWidth)
1232 rMax = aArg.m_nRowWidth;
1234 nLROffset += rRightMargin.GetRight();
1236 rAbsMin += nLROffset;
1237 rAbsMin += nAdd;
1238 rMin += nLROffset;
1239 rMin += nAdd;
1240 if (static_cast<tools::Long>(rMin) < aNodeArgs.m_nMinWidth)
1241 rMin = aNodeArgs.m_nMinWidth;
1242 if (static_cast<tools::Long>(rAbsMin) < aNodeArgs.m_nMinWidth)
1243 rAbsMin = aNodeArgs.m_nMinWidth;
1244 rMax += aNodeArgs.m_nMaxWidth;
1245 rMax += nLROffset;
1246 rMax += nAdd;
1247 if( rMax < rMin ) // e.g. Frames with flow through only contribute to the minimum
1248 rMax = rMin;
1249 pOut->SetMapMode( aOldMap );
1253 * Calculates the width of the text part specified by nStart and nEnd,
1254 * the height of the line containing nStart is divided by this width,
1255 * indicating the scaling factor, if the text part is rotated.
1256 * Having CH_BREAKs in the text part, this method returns the scaling
1257 * factor for the longest of the text parts separated by the CH_BREAK
1259 * Changing this method very likely requires changing of "GetMinMaxSize"
1261 sal_uInt16 SwTextFrame::GetScalingOfSelectedText(
1262 TextFrameIndex nStart, TextFrameIndex nEnd)
1264 assert(GetOffset() <= nStart && (!GetFollow() || nStart < GetFollow()->GetOffset()));
1265 SwViewShell const*const pSh = getRootFrame()->GetCurrShell();
1266 assert(pSh);
1267 OutputDevice *const pOut = &pSh->GetRefDev();
1268 assert(pOut);
1270 MapMode aOldMap( pOut->GetMapMode() );
1271 pOut->SetMapMode( MapMode( MapUnit::MapTwip ) );
1273 if (nStart == nEnd)
1275 assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
1277 SwScriptInfo aScriptInfo;
1278 SwAttrIter aIter(*GetTextNodeFirst(), aScriptInfo, this);
1279 aIter.SeekAndChgAttrIter( nStart, pOut );
1281 Boundary aBound = g_pBreakIt->GetBreakIter()->getWordBoundary(
1282 GetText(), sal_Int32(nStart),
1283 g_pBreakIt->GetLocale( aIter.GetFnt()->GetLanguage() ),
1284 WordType::DICTIONARY_WORD, true );
1286 if (sal_Int32(nStart) == aBound.startPos)
1288 // cursor is at left or right border of word
1289 pOut->SetMapMode( aOldMap );
1290 return 100;
1293 nStart = TextFrameIndex(aBound.startPos);
1294 nEnd = TextFrameIndex(aBound.endPos);
1296 if (nStart == nEnd)
1298 pOut->SetMapMode( aOldMap );
1299 return 100;
1303 SwScriptInfo aScriptInfo;
1304 SwAttrIter aIter(*GetTextNodeFirst(), aScriptInfo, this);
1306 // We do not want scaling attributes to be considered during this
1307 // calculation. For this, we push a temporary scaling attribute with
1308 // scaling value 100 and priority flag on top of the scaling stack
1309 SwAttrHandler& rAH = aIter.GetAttrHandler();
1310 SvxCharScaleWidthItem aItem(100, RES_CHRATR_SCALEW);
1311 SwTextAttrEnd aAttr( aItem, 0, COMPLETE_STRING );
1312 aAttr.SetPriorityAttr( true );
1313 rAH.PushAndChg( aAttr, *(aIter.GetFnt()) );
1315 TextFrameIndex nIdx = nStart;
1317 sal_uLong nWidth = 0;
1318 sal_uLong nProWidth = 0;
1320 while( nIdx < nEnd )
1322 aIter.SeekAndChgAttrIter( nIdx, pOut );
1324 // scan for end of portion
1325 TextFrameIndex const nNextChg = std::min(aIter.GetNextAttr(), aScriptInfo.NextScriptChg(nIdx));
1327 TextFrameIndex nStop = nIdx;
1328 sal_Unicode cChar = CH_BLANK;
1329 SwTextAttr* pHint = nullptr;
1331 // stop at special characters in [ nIdx, nNextChg ]
1332 while( nStop < nEnd && nStop < nNextChg )
1334 cChar = GetText()[sal_Int32(nStop)];
1335 if (
1336 CH_TAB == cChar ||
1337 CH_BREAK == cChar ||
1338 CHAR_HARDBLANK == cChar ||
1339 CHAR_HARDHYPHEN == cChar ||
1340 CHAR_SOFTHYPHEN == cChar ||
1341 CH_TXT_ATR_INPUTFIELDSTART == cChar ||
1342 CH_TXT_ATR_INPUTFIELDEND == cChar ||
1343 CH_TXT_ATR_FORMELEMENT == cChar ||
1344 CH_TXT_ATR_FIELDSTART == cChar ||
1345 CH_TXT_ATR_FIELDSEP == cChar ||
1346 CH_TXT_ATR_FIELDEND == cChar ||
1348 (CH_TXTATR_BREAKWORD == cChar || CH_TXTATR_INWORD == cChar) &&
1349 (nullptr == (pHint = aIter.GetAttr(nStop)))
1353 break;
1355 else
1356 ++nStop;
1359 // calculate text widths up to cChar
1360 if ( nStop > nIdx )
1362 SwDrawTextInfo aDrawInf(pSh, *pOut, GetText(), sal_Int32(nIdx), sal_Int32(nStop - nIdx));
1363 nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
1366 nIdx = nStop;
1367 aIter.SeekAndChgAttrIter( nIdx, pOut );
1369 if ( cChar == CH_BREAK )
1371 nWidth = std::max( nWidth, nProWidth );
1372 nProWidth = 0;
1373 nIdx++;
1375 else if ( cChar == CH_TAB )
1377 // tab receives width of one space
1378 SwDrawTextInfo aDrawInf(pSh, *pOut, OUStringChar(CH_BLANK), 0, 1);
1379 nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
1380 nIdx++;
1382 else if ( cChar == CHAR_SOFTHYPHEN )
1383 ++nIdx;
1384 else if ( cChar == CHAR_HARDBLANK || cChar == CHAR_HARDHYPHEN )
1386 OUString sTmp( cChar );
1387 SwDrawTextInfo aDrawInf(pSh, *pOut, sTmp, 0, 1);
1388 nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
1389 nIdx++;
1391 else if ( pHint && ( cChar == CH_TXTATR_BREAKWORD || cChar == CH_TXTATR_INWORD ) )
1393 switch( pHint->Which() )
1395 case RES_TXTATR_FTN :
1397 const OUString aText = pHint->GetFootnote().GetNumStr();
1398 SwDrawTextInfo aDrawInf(pSh, *pOut, aText, 0, aText.getLength());
1400 nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
1401 break;
1404 case RES_TXTATR_FIELD :
1405 case RES_TXTATR_ANNOTATION :
1407 SwField *pField = const_cast<SwField*>(pHint->GetFormatField().GetField());
1408 OUString const aText = pField->ExpandField(true, getRootFrame());
1409 SwDrawTextInfo aDrawInf(pSh, *pOut, aText, 0, aText.getLength());
1411 nProWidth += aIter.GetFnt()->GetTextSize_( aDrawInf ).Width();
1412 break;
1415 default:
1417 // any suggestions for a default action?
1419 } // end of switch
1420 nIdx++;
1422 else if (CH_TXT_ATR_INPUTFIELDSTART == cChar ||
1423 CH_TXT_ATR_INPUTFIELDEND == cChar ||
1424 CH_TXT_ATR_FORMELEMENT == cChar ||
1425 CH_TXT_ATR_FIELDSTART == cChar ||
1426 CH_TXT_ATR_FIELDSEP == cChar ||
1427 CH_TXT_ATR_FIELDEND == cChar)
1428 { // just skip it and continue with the content...
1429 ++nIdx;
1431 } // end of while
1433 nWidth = std::max( nWidth, nProWidth );
1435 // search for the line containing nStart
1436 if (HasPara())
1438 SwTextInfo aInf(this);
1439 SwTextIter aLine(this, &aInf);
1440 aLine.CharToLine( nStart );
1441 pOut->SetMapMode( aOldMap );
1442 return o3tl::narrowing<sal_uInt16>( nWidth ?
1443 ( ( 100 * aLine.GetCurr()->Height() ) / nWidth ) : 0 );
1445 // no frame or no paragraph, we take the height of the character
1446 // at nStart as line height
1448 aIter.SeekAndChgAttrIter( nStart, pOut );
1449 pOut->SetMapMode( aOldMap );
1451 SwDrawTextInfo aDrawInf(pSh, *pOut, GetText(), sal_Int32(nStart), 1);
1452 return o3tl::narrowing<sal_uInt16>( nWidth ? ((100 * aIter.GetFnt()->GetTextSize_( aDrawInf ).Height()) / nWidth ) : 0 );
1455 std::vector<SwFlyAtContentFrame*> SwTextFrame::GetSplitFlyDrawObjs() const
1457 std::vector<SwFlyAtContentFrame*> aObjs;
1458 const SwSortedObjs* pSortedObjs = GetDrawObjs();
1459 if (!pSortedObjs)
1461 return aObjs;
1464 for (const auto& pSortedObj : *pSortedObjs)
1466 SwFlyFrame* pFlyFrame = pSortedObj->DynCastFlyFrame();
1467 if (!pFlyFrame)
1469 continue;
1472 if (!pFlyFrame->IsFlySplitAllowed())
1474 continue;
1477 aObjs.push_back(static_cast<SwFlyAtContentFrame*>(pFlyFrame));
1480 return aObjs;
1483 SwFlyAtContentFrame* SwTextFrame::HasNonLastSplitFlyDrawObj() const
1485 const SwTextFrame* pFollow = GetFollow();
1486 if (!pFollow)
1488 return nullptr;
1491 if (mnOffset != pFollow->GetOffset())
1493 return nullptr;
1496 // At this point we know what we're part of a chain that is an anchor for split fly frames, but
1497 // we're not the last one. See if we have a matching fly.
1499 // Look up the master of the anchor.
1500 const SwTextFrame* pAnchor = this;
1501 while (pAnchor->IsFollow())
1503 pAnchor = pAnchor->FindMaster();
1505 for (const auto& pFly : pAnchor->GetSplitFlyDrawObjs())
1507 // Nominally all flys are anchored in the master; see if this fly is effectively anchored in
1508 // us.
1509 SwTextFrame* pFlyAnchor = pFly->FindAnchorCharFrame();
1510 if (pFlyAnchor != this)
1512 continue;
1514 if (pFly->GetFollow())
1516 return pFly;
1520 return nullptr;
1523 bool SwTextFrame::IsEmptyMasterWithSplitFly() const
1525 if (!IsEmptyMaster())
1527 return false;
1530 if (!m_pDrawObjs || m_pDrawObjs->size() != 1)
1532 return false;
1535 SwFlyFrame* pFlyFrame = (*m_pDrawObjs)[0]->DynCastFlyFrame();
1536 if (!pFlyFrame || !pFlyFrame->IsFlySplitAllowed())
1538 return false;
1541 if (mnOffset != GetFollow()->GetOffset())
1543 return false;
1546 return true;
1549 bool SwTextFrame::IsEmptyWithSplitFly() const
1551 if (IsFollow())
1553 return false;
1556 if (GetTextNodeFirst()->GetSwAttrSet().HasItem(RES_PAGEDESC))
1558 return false;
1561 if (getFrameArea().Bottom() <= GetUpper()->getFramePrintArea().Bottom())
1563 return false;
1566 // This is a master that doesn't fit the current parent.
1567 if (!m_pDrawObjs || m_pDrawObjs->size() != 1)
1569 return false;
1572 SwFlyFrame* pFlyFrame = (*m_pDrawObjs)[0]->DynCastFlyFrame();
1573 if (!pFlyFrame || !pFlyFrame->IsFlySplitAllowed())
1575 return false;
1578 // It has a split fly anchored to it.
1579 if (pFlyFrame->GetFrameFormat().GetVertOrient().GetPos() >= 0)
1581 return false;
1584 // Negative vertical offset means that visually it already may have a first line.
1585 // Consider that, we may need to split the frame, so the fly frame is on one page and the empty
1586 // paragraph's frame is on a next page.
1587 return true;
1590 SwTwips SwTextNode::GetWidthOfLeadingTabs() const
1592 SwTwips nRet = 0;
1594 sal_Int32 nIdx = 0;
1596 while ( nIdx < GetText().getLength() )
1598 const sal_Unicode cCh = GetText()[nIdx];
1599 if ( cCh!='\t' && cCh!=' ' )
1601 break;
1603 ++nIdx;
1606 if ( nIdx > 0 )
1608 SwPosition aPos( *this, nIdx );
1610 // Find the non-follow text frame:
1611 SwIterator<SwTextFrame, SwTextNode, sw::IteratorMode::UnwrapMulti> aIter(*this);
1612 for( SwTextFrame* pFrame = aIter.First(); pFrame; pFrame = aIter.Next() )
1614 // Only consider master frames:
1615 if (!pFrame->IsFollow() &&
1616 pFrame->GetTextNodeForFirstText() == this)
1618 SwRectFnSet aRectFnSet(pFrame);
1619 SwRect aRect;
1620 pFrame->GetCharRect( aRect, aPos );
1621 nRet = pFrame->IsRightToLeft() ?
1622 aRectFnSet.GetPrtRight(*pFrame) - aRectFnSet.GetRight(aRect) :
1623 aRectFnSet.GetLeft(aRect) - aRectFnSet.GetPrtLeft(*pFrame);
1624 break;
1629 return nRet;
1632 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */