Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/LibreOffice/editeng/source/editeng/   (Office von Apache Version 25.8.3.2©)  Datei vom 5.10.2025 mit Größe 222 kB image not shown  

Quelle  impedit3.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */



#include <vcl/svapp.hxx>
#include <vcl/metaact.hxx>
#include <vcl/gdimtf.hxx>
#include <vcl/settings.hxx>
#include <vcl/window.hxx>

#include <editeng/outliner.hxx>
#include <editeng/tstpitem.hxx>
#include <editeng/lspcitem.hxx>
#include <editeng/flditem.hxx>
#include <editeng/forbiddenruleitem.hxx>
#include "impedit.hxx"
#include <editeng/editeng.hxx>
#include <editeng/editview.hxx>
#include <editeng/escapementitem.hxx>
#include <editeng/txtrange.hxx>
#include <editeng/udlnitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/lrspitem.hxx>
#include <editeng/ulspitem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/wghtitem.hxx>
#include <editeng/postitem.hxx>
#include <editeng/rubyitem.hxx>
#include <editeng/langitem.hxx>
#include <editeng/frmdiritem.hxx>
#include <editeng/scriptspaceitem.hxx>
#include <editeng/charscaleitem.hxx>
#include <editeng/numitem.hxx>
#include <editeng/StripPortionsHelper.hxx>
#include <outleeng.hxx>
#include <TextPortion.hxx>
#include <tools/gen.hxx>

#include <svtools/colorcfg.hxx>
#include <svl/ctloptions.hxx>
#include <svl/asiancfg.hxx>

#include <svx/compatflags.hxx>
#include <sfx2/viewsh.hxx>

#include <editeng/hngpnctitem.hxx>
#include <editeng/forbiddencharacterstable.hxx>

#include <comphelper/configuration.hxx>
#include <comphelper/scopeguard.hxx>

#include <math.h>
#include <vcl/metric.hxx>
#include <com/sun/star/i18n/BreakIterator.hpp>
#include <com/sun/star/i18n/ScriptType.hpp>
#include <com/sun/star/i18n/InputSequenceChecker.hpp>
#include <vcl/pdfextoutdevdata.hxx>
#include <i18nlangtag/mslangid.hxx>
#include <i18nutil/kashida.hxx>

#include <comphelper/processfactory.hxx>
#include <comphelper/lok.hxx>
#include <rtl/ustrbuf.hxx>
#include <sal/log.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/sorted_vector.hxx>
#include <osl/diagnose.h>
#include <comphelper/string.hxx>
#include <cstddef>
#include <memory>
#include <set>

#include <vcl/outdev/ScopedStates.hxx>

#include <unicode/uchar.h>

using namespace ::com::sun::star;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::linguistic2;

constexpr OUString CH_HYPH = u"-"_ustr;

constexpr tools::Long WRONG_SHOW_MIN = 5;

#if (OSL_DEBUG_LEVEL > 1) || defined ( DBG_UTIL )
bool ImpEditEngine::bDebugPaint = false;
#endif

namespace {

struct TabInfo
{
    bool        bValid;

    SvxTabStop  aTabStop;
    sal_Int32   nTabPortion;
    tools::Long        nStartPosX;
    tools::Long        nTabPos;

    TabInfo()
        : bValid(false)
        , nTabPortion(0)
        , nStartPosX(0)
        , nTabPos(0)
        { }

};

}

AsianCompressionFlags GetCharTypeForCompression( sal_Unicode cChar )
{
    switch ( cChar )
    {
        case 0x3008: case 0x300A: case 0x300C: case 0x300E:
        case 0x3010: case 0x3014: case 0x3016: case 0x3018:
        case 0x301A: case 0x301D: case 0xFF09: case 0xFF3D:
        case 0xFF5D:
        {
            return AsianCompressionFlags::PunctuationRight;
        }
        case 0x3001: case 0x3002: case 0x3009: case 0x300B:
        case 0x300D: case 0x300F: case 0x3011: case 0x3015:
        case 0x3017: case 0x3019: case 0x301B: case 0x301E:
        case 0x301F: case 0xFF08: case 0xFF0C: case 0xFF0E:
        case 0xFF1A: case 0xFF1B: case 0xFF3B: case 0xFF5B:
        {
            return AsianCompressionFlags::PunctuationLeft;
        }
        default:
        {
            return ( ( 0x3040 <= cChar ) && ( 0x3100 > cChar ) ) ? AsianCompressionFlags::Kana : AsianCompressionFlags::Normal;
        }
    }
}

static void lcl_DrawRedLines( OutputDevice& rOutDev,
                              tools::Long nFontHeight,
                              const Point& rPoint,
                              size_t nIndex,
                              size_t nMaxEnd,
                              KernArraySpan pDXArray,
                              WrongList const * pWrongs,
                              Degree10 nOrientation,
                              const Point& rOrigin,
                              bool bVertical,
                              bool bIsRightToLeft )
{
    // But only if font is not too small...
    tools::Long nHeight = rOutDev.LogicToPixel(Size(0, nFontHeight)).Height();
    if (WRONG_SHOW_MIN >= nHeight)
        return;

    size_t nEnd, nStart = nIndex;
    bool bWrong = pWrongs->NextWrong(nStart, nEnd);

    while (bWrong)
    {
        if (nStart >= nMaxEnd)
            break;

        if (nStart < nIndex)  // Corrected
            nStart = nIndex;

        if (nEnd > nMaxEnd)
            nEnd = nMaxEnd;

        Point aPoint1(rPoint);
        if (bVertical)
        {
            // VCL doesn't know that the text is vertical, and is manipulating
            // the positions a little bit in y direction...
            tools::Long nOnePixel = rOutDev.PixelToLogic(Size(0, 1)).Height();
            tools::Long nCorrect = 2 * nOnePixel;
            aPoint1.AdjustY(-nCorrect);
            aPoint1.AdjustX(-nCorrect);
        }
        if (nStart > nIndex)
        {
            if (!bVertical)
            {
                // since for RTL portions rPoint is on the visual right end of the portion
                // (i.e. at the start of the first RTL char) we need to subtract the offset
                // for RTL portions...
                aPoint1.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nStart - nIndex - 1]);
            }
            else
                aPoint1.AdjustY(pDXArray[nStart - nIndex - 1]);
        }
        Point aPoint2(rPoint);
        assert(nEnd > nIndex && "RedLine: aPnt2?");
        if (!bVertical)
        {
            // since for RTL portions rPoint is on the visual right end of the portion
            // (i.e. at the start of the first RTL char) we need to subtract the offset
            // for RTL portions...
            aPoint2.AdjustX((bIsRightToLeft ? -1 : 1) * pDXArray[nEnd - nIndex - 1]);
        }
        else
        {
            aPoint2.AdjustY(pDXArray[nEnd - nIndex - 1]);
        }

        if (nOrientation)
        {
            rOrigin.RotateAround(aPoint1, nOrientation);
            rOrigin.RotateAround(aPoint2, nOrientation);
        }

        {
            vcl::ScopedAntialiasing a(rOutDev, true);
            rOutDev.DrawWaveLine(aPoint1, aPoint2);
        }

        nStart = nEnd + 1;
        if (nEnd < nMaxEnd)
            bWrong = pWrongs->NextWrong(nStart, nEnd);
        else
            bWrong = false;
    }
}

void ImpEditEngine::UpdateViews( EditView* pCurView )
{
    if ( !IsUpdateLayout() || IsFormatting() || maInvalidRect.IsEmpty() )
        return;

    DBG_ASSERT( IsFormatted(), "UpdateViews: Doc not formatted!" );

    for (EditView* pView : maEditViews)
    {
        pView->HideCursor();

        tools::Rectangle aClipRect(maInvalidRect);
        tools::Rectangle aVisArea( pView->GetVisArea() );
        aClipRect.Intersection( aVisArea );

        if ( !aClipRect.IsEmpty() )
        {
            // convert to window coordinates...
            aClipRect = pView->getImpl().GetWindowPos( aClipRect );

            // moved to one executing method to allow finer control
            pView->InvalidateWindow(aClipRect);

            pView->InvalidateOtherViewWindows( aClipRect );
        }
    }

    if ( pCurView )
    {
        bool bGotoCursor = pCurView->getImpl().DoAutoScroll();
        pCurView->ShowCursor( bGotoCursor );
    }

    maInvalidRect = tools::Rectangle();
    CallStatusHdl();
}

IMPL_LINK_NOARG(ImpEditEngine, OnlineSpellHdl, Timer *, void)
{
    if ( !Application::AnyInput( VclInputFlags::KEYBOARD ) && IsUpdateLayout() && IsFormatted() )
        DoOnlineSpelling();
    else
        maOnlineSpellTimer.Start();
}

IMPL_LINK_NOARG(ImpEditEngine, IdleFormatHdl, Timer *, void)
{
    maIdleFormatter.ResetRestarts();

    // #i97146# check if that view is still available
    // else probably the idle format timer fired while we're already
    // downing
    EditView* pView = maIdleFormatter.GetView();
    for (EditView* aEditView : maEditViews)
    {
        if( aEditView == pView )
        {
            FormatAndLayout( pView );
            break;
        }
    }
}

void ImpEditEngine::CheckIdleFormatter()
{
    maIdleFormatter.ForceTimeout();
    // If not idle, but still not formatted:
    if ( !IsFormatted() )
        FormatDoc();
}

bool ImpEditEngine::IsPageOverflow( ) const
{
    return mbNeedsChainingHandling;
}


void ImpEditEngine::FormatFullDoc()
{
    GetParaPortions().MarkAllSelectionsInvalid(0);
    FormatDoc();
}

tools::Long ImpEditEngine::FormatParagraphs(o3tl::sorted_vector<sal_Int32>& aRepaintParagraphList, bool bIsScaling)
{
    sal_Int32 nParaCount = GetParaPortions().Count();
    tools::Long nY = 0, nResult = 0;
    bool bGrow = false;

    for (sal_Int32 nParagraph = 0; nParagraph < nParaCount; nParagraph++)
    {
        ParaPortion& rParaPortion = GetParaPortions().getRef(nParagraph);
        if (rParaPortion.MustRepaint() || (rParaPortion.IsInvalid() && rParaPortion.IsVisible()))
        {
            // No formatting should be necessary for MustRepaint()!
            if (CreateLines(nParagraph, nY))
            {
                if (!bGrow && GetTextRanger())
                {
                    // For a change in height all below must be reformatted...
                    for (sal_Int32 n = nParagraph + 1; n < nParaCount; n++)
                    {
                        ParaPortion& rParaPortionToInvalidate = GetParaPortions().getRef(n);
                        rParaPortionToInvalidate.MarkSelectionInvalid(0);
                        rParaPortionToInvalidate.GetLines().Reset();
                    }
                }
                bGrow = true;
                if (IsCallParaInsertedOrDeleted())
                {
                    GetEditEnginePtr()->ParagraphHeightChanged(nParagraph);

                    for (EditView* pView : maEditViews)
                    {
                        pView->getImpl().ScrollStateChange();
                    }

                }
                rParaPortion.SetMustRepaint(false);
            }

            aRepaintParagraphList.insert(nParagraph);
        }
        nY += rParaPortion.GetHeight();
        if (!isInEmptyClusterAtTheEnd(rParaPortion, bIsScaling))
            nResult = nY; // The total height excluding trailing blank paragraphs
    }
    return nResult;
}

namespace
{
constexpr std::array<ScalingParameters, 12> constScaleLevels =
{
    ScalingParameters{ 1.000,  1.000,  1.0,  0.9 },
    ScalingParameters{ 0.925,  0.925,  1.0,  0.9 },
    ScalingParameters{ 0.850,  0.850,  1.0,  0.9 },
    ScalingParameters{ 0.850,  0.850,  1.0,  0.8 },
    ScalingParameters{ 0.775,  0.775,  1.0,  0.8 },
    ScalingParameters{ 0.700,  0.700,  1.0,  0.8 },
    ScalingParameters{ 0.625,  0.625,  1.0,  0.8 },
    ScalingParameters{ 0.550,  0.550,  1.0,  0.8 },
    ScalingParameters{ 0.475,  0.475,  1.0,  0.8 },
    ScalingParameters{ 0.400,  0.400,  1.0,  0.8 },
    ScalingParameters{ 0.325,  0.325,  1.0,  0.8 },
    ScalingParameters{ 0.250,  0.250,  1.0,  0.8 },
};

// end anonymous ns

void ImpEditEngine::ScaleContentToFitWindow(o3tl::sorted_vector<sal_Int32>& aRepaintParagraphList)
{
    if (!maCustomScalingParameters.areValuesDefault())
        maScalingParameters = maCustomScalingParameters;

    tools::Long nHeight = FormatParagraphs(aRepaintParagraphList, true);
    bool bOverflow = nHeight > (maMaxAutoPaperSize.Height() * mnColumns);

    size_t nCurrentScaleLevel = 0;
    while (bOverflow && nCurrentScaleLevel < constScaleLevels.size())
    {
        // Clean-up and reset paragraphs
        aRepaintParagraphList.clear();
        for (auto& pParaPortionToInvalidate : GetParaPortions())
        {
            pParaPortionToInvalidate->GetLines().Reset();
            pParaPortionToInvalidate->MarkSelectionInvalid(0);
            pParaPortionToInvalidate->SetMustRepaint(true);
        }

        // Get new scaling parameters
        maScalingParameters = constScaleLevels[nCurrentScaleLevel];

        // Try again with different scaling factor
        nHeight = FormatParagraphs(aRepaintParagraphList, true);
        bOverflow = nHeight > (maMaxAutoPaperSize.Height() * mnColumns);

        // Increase scale level
        nCurrentScaleLevel++;
    }
}

void ImpEditEngine::EnsureDocumentFormatted()
{
    if (!IsFormatted())
        FormatDoc();
}

void ImpEditEngine::FormatDoc()
{
    if (!IsUpdateLayout() || IsFormatting())
        return;

    mbIsFormatting = true;

    // Then I can also start the spell-timer...
    if (GetStatus().DoOnlineSpelling())
        StartOnlineSpellTimer();

    // Reserve, as it should match the current number of paragraphs
    o3tl::sorted_vector<sal_Int32> aRepaintParagraphList;
    aRepaintParagraphList.reserve(GetParaPortions().Count());

    if (maStatus.DoStretch())
        ScaleContentToFitWindow(aRepaintParagraphList);
    else
        FormatParagraphs(aRepaintParagraphList, false);

    maInvalidRect = tools::Rectangle(); // make empty

    // One can also get into the formatting through UpdateMode ON=>OFF=>ON...
    // enable optimization first after Vobis delivery...
    {
        tools::Long nNewHeight = CalcTextHeight();
        tools::Long nDiff = nNewHeight - mnCurTextHeight;
        if ( nDiff )
        {
            maInvalidRect.Union(tools::Rectangle::Normalize(
                { 0, nNewHeight }, { getWidthDirectionAware(maPaperSize), mnCurTextHeight }));
            maStatus.GetStatusWord() |= !IsEffectivelyVertical() ? EditStatusFlags::TextHeightChanged : EditStatusFlags::TEXTWIDTHCHANGED;
        }

        mnCurTextHeight = nNewHeight;

        if ( maStatus.AutoPageSize() )
            CheckAutoPageSize();
        else if ( nDiff )
        {
            for (EditView* pView : maEditViews)
            {
                ImpEditView& rImpView = pView->getImpl();
                if (rImpView.DoAutoHeight())
                {
                    Size aSz(rImpView.GetOutputArea().GetWidth(), mnCurTextHeight);
                    if ( aSz.Height() > maMaxAutoPaperSize.Height() )
                        aSz.setHeight( maMaxAutoPaperSize.Height() );
                    else if ( aSz.Height() < maMinAutoPaperSize.Height() )
                        aSz.setHeight( maMinAutoPaperSize.Height() );
                    rImpView.ResetOutputArea( tools::Rectangle(rImpView.GetOutputArea().TopLeft(), aSz));
                }
            }
        }

        if (!aRepaintParagraphList.empty())
        {
            auto CombineRepaintParasAreas = [&](const LineAreaInfo& rInfo) {
                if (aRepaintParagraphList.count(rInfo.nPortion))
                    maInvalidRect.Union(rInfo.aArea);
                return CallbackResult::Continue;
            };
            IterateLineAreas(CombineRepaintParasAreas, IterFlag::inclILS);
        }
    }

    mbIsFormatting = false;
    mbFormatted = true;

    CallStatusHdl();    // If Modified...
}

void ImpEditEngine::CheckAutoPageSize()
{
    Size aPrevPaperSize( GetPaperSize() );
    if ( GetStatus().AutoPageWidth() )
        maPaperSize.setWidth( !IsEffectivelyVertical() ? CalcTextWidth( true ) : GetTextHeight() );
    if ( GetStatus().AutoPageHeight() )
        maPaperSize.setHeight( !IsEffectivelyVertical() ? GetTextHeight() : CalcTextWidth( true ) );

    SetValidPaperSize( maPaperSize );    // consider Min, Max

    if ( maPaperSize == aPrevPaperSize )
        return;

    if ( ( !IsEffectivelyVertical() && ( maPaperSize.Width() != aPrevPaperSize.Width() ) )
         || ( IsEffectivelyVertical() && ( maPaperSize.Height() != aPrevPaperSize.Height() ) ) )
    {
        // If ahead is centered / right or tabs...
        maStatus.GetStatusWord() |= !IsEffectivelyVertical() ? EditStatusFlags::TEXTWIDTHCHANGED : EditStatusFlags::TextHeightChanged;
        for ( sal_Int32 nPara = 0; nPara < GetParaPortions().Count(); nPara++ )
        {
            // Only paragraphs which are not aligned to the left need to be
            // reformatted, the height can not be changed here anymore.
            ParaPortion& rParaPortion = GetParaPortions().getRef(nPara);
            SvxAdjust eJustification = GetJustification( nPara );
            if ( eJustification != SvxAdjust::Left )
            {
                rParaPortion.MarkSelectionInvalid(0);
                CreateLines( nPara, 0 );  // 0: For AutoPageSize no TextRange!
            }
        }
    }

    Size aInvSize = maPaperSize;
    if ( maPaperSize.Width() < aPrevPaperSize.Width() )
        aInvSize.setWidth( aPrevPaperSize.Width() );
    if ( maPaperSize.Height() < aPrevPaperSize.Height() )
        aInvSize.setHeight( aPrevPaperSize.Height() );

    Size aSz( aInvSize );
    if ( IsEffectivelyVertical() )
    {
        aSz.setWidth( aInvSize.Height() );
        aSz.setHeight( aInvSize.Width() );
    }
    maInvalidRect = tools::Rectangle( Point(), aSz );


    for (EditView* pView : maEditViews)
    {
        pView->getImpl().RecalcOutputArea();
    }
}

void ImpEditEngine::CheckPageOverflow()
{
    SAL_INFO("editeng.chaining""[CONTROL_STATUS] AutoPageSize is " << (( maStatus.GetControlWord() & EEControlBits::AUTOPAGESIZE ) ? "ON" : "OFF") );

    tools::Long nBoxHeight = GetMaxAutoPaperSize().Height();
    SAL_INFO("editeng.chaining""[OVERFLOW-CHECK] Current MaxAutoPaperHeight is " << nBoxHeight);

    tools::Long nTxtHeight = CalcTextHeight();
    SAL_INFO("editeng.chaining""[OVERFLOW-CHECK] Current Text Height is " << nTxtHeight);

    sal_uInt32 nParaCount = maParaPortionList.Count();
    bool bOnlyOneEmptyPara = false;
    if (nParaCount == 1)
    {
        const ParaPortion* pPPortion = GetParaPortions().SafeGetObject(0);
        bOnlyOneEmptyPara = pPPortion && pPPortion->GetLines().Count() == 1
                            && pPPortion->GetLines()[0].GetLen() == 0;
    }

    if (nTxtHeight > nBoxHeight && !bOnlyOneEmptyPara)
    {
        // which paragraph is the first to cause higher size of the box?
        ImplUpdateOverflowingParaNum( nBoxHeight); // XXX: currently only for horizontal text
        //maStatus.SetPageOverflow(true);
        mbNeedsChainingHandling = true;
    } else
    {
        // No overflow if within box boundaries
        //maStatus.SetPageOverflow(false);
        mbNeedsChainingHandling = false;
    }

}

static sal_Int32 ImplCalculateFontIndependentLineSpacing( const sal_Int32 nFontHeight )
{
    constexpr const double f120Percent = 12.0 / 10.0;
    return basegfx::fround(nFontHeight * f120Percent);  // + 20%
}

tools::Long ImpEditEngine::GetColumnWidth(const Size& rPaperSize) const
{
    assert(mnColumns >= 1);
    tools::Long nWidth = IsEffectivelyVertical() ? rPaperSize.Height() : rPaperSize.Width();
    return (nWidth - mnColumnSpacing * (mnColumns - 1)) / mnColumns;
}

bool ImpEditEngine::createLinesForEmptyParagraph(ParaPortion& rParaPortion)
{
    // fast special treatment...
    if (rParaPortion.GetTextPortions().Count())
        rParaPortion.GetTextPortions().Reset();
    if (rParaPortion.GetLines().Count())
        rParaPortion.GetLines().Reset();

    CreateAndInsertEmptyLine(rParaPortion);
    return FinishCreateLines(rParaPortion);
}

tools::Long ImpEditEngine::calculateMaxLineWidth(tools::Long nStartX, SvxLRSpaceIteconst& rLRItem,
                                                 const SvxFontUnitMetrics& rMetrics)
{
    const bool bAutoSize = IsEffectivelyVertical() ? maStatus.AutoPageHeight() : maStatus.AutoPageWidth();
    tools::Long nMaxLineWidth = GetColumnWidth(bAutoSize ? maMaxAutoPaperSize : maPaperSize);

    nMaxLineWidth -= scaleXSpacingValue(rLRItem.ResolveRight(rMetrics));
    nMaxLineWidth -= nStartX;

    // If PaperSize == long_max, one cannot take away any negative
    // first line indent. (Overflow)
    if (nMaxLineWidth < 0 && nStartX < 0)
        nMaxLineWidth
            = GetColumnWidth(maPaperSize) - scaleXSpacingValue(rLRItem.ResolveRight(rMetrics));

    // If still less than 0, it may be just the right edge.
    if (nMaxLineWidth <= 0)
        nMaxLineWidth = 1;

    return nMaxLineWidth;
}

void ImpEditEngine::populateRubyInfo(ParaPortion& rParaPortion, EditLine* pLine)
{
    ContentNode* const pNode = rParaPortion.GetNode();
    SvxFont aTmpFont(pNode->GetCharAttribs().GetDefFont());
    SvxFont aRubyStartFont = aTmpFont;

    sal_Int32 nTextPos = pLine->GetStart();
    const EditCharAttrib* pNextRubyAttr
        = pNode->GetCharAttribs().FindNextAttrib(EE_CHAR_RUBY, nTextPos);
    TextPortion* pTPRubyStart = nullptr;
    tools::Long nTPMaxAscent = 0;
    tools::Long nTPTotalWidth = 0;
    for (sal_Int32 nP = pLine->GetStartPortion(); pNextRubyAttr && nP <= pLine->GetEndPortion();
         ++nP)
    {
        const auto* pRuby = static_cast<const SvxRubyItem*>(pNextRubyAttr->GetItem());
        if (pRuby->GetText().isEmpty())
        {
            // Skip processing blank ruby spans
            pNextRubyAttr
                = rParaPortion.GetNode()->GetCharAttribs().FindNextAttrib(EE_CHAR_RUBY, nTextPos);
            pTPRubyStart = nullptr;
            continue;
        }

        SeekCursor(pNode, nTextPos, aTmpFont);

        TextPortion& rTP = rParaPortion.GetTextPortions()[nP];
        rTP.SetRubyInfos({});

        if (nTextPos == pNextRubyAttr->GetStart())
        {
            pTPRubyStart = &rTP;
            aRubyStartFont = aTmpFont;
            SeekCursor(pNode, nTextPos, aTmpFont);

            nTPMaxAscent = 0;
            nTPTotalWidth = 0;
        }

        nTextPos += rTP.GetLen();
        nTPTotalWidth += rTP.GetSize().getWidth();

        aTmpFont.SetPhysFont(*GetRefDevice());
        nTPMaxAscent = std::max(
            nTPMaxAscent, static_cast<tools::Long>(GetRefDevice()->GetFontMetric().GetAscent()));

        if (pTPRubyStart && nTextPos >= pNextRubyAttr->GetEnd())
        {
            auto pRubyInfo = std::make_unique<RubyPortionInfo>();

            // Get ruby text width

            // TODO: Style support is unimplemented. For now, use a hard-coded 50% scale
            aRubyStartFont.SetFontSize(aRubyStartFont.GetFontSize() / 2);
            aRubyStartFont.SetPhysFont(*GetRefDevice());

            auto aRubyMetrics = GetRefDevice()->GetFontMetric();
            auto nRubyAscent = static_cast<tools::Long>(aRubyMetrics.GetAscent());

            tools::Long nRubyWidth = aRubyStartFont
                                         .QuickGetTextSize(GetRefDevice(), pRuby->GetText(), 0,
                                                           pRuby->GetText().getLength(),
                                                           /*pDXArray=*/nullptr, /*bStacked=*/false)
                                         .Width();

            switch (pRuby->GetAdjustment())
            {
                case css::text::RubyAdjust_LEFT:
                    pRubyInfo->nXOffset = 0;
                    break;

                case css::text::RubyAdjust_RIGHT:
                    pRubyInfo->nXOffset = nTPTotalWidth - nRubyWidth;
                    break;

                default:
                case css::text::RubyAdjust_CENTER:
                    pRubyInfo->nXOffset = (nTPTotalWidth - nRubyWidth) / 2;
                    break;
            }

            switch (pRuby->GetPosition())
            {
                default:
                case css::text::RubyPosition::ABOVE:
                    pRubyInfo->nYOffset = nTPMaxAscent;
                    pRubyInfo->nMaxAscent = nTPMaxAscent + nRubyAscent;
                    break;

                case css::text::RubyPosition::BELOW:
                    pRubyInfo->nYOffset = -nRubyAscent;
                    pRubyInfo->nMaxAscent = 0;
                    break;
            }

            pTPRubyStart->SetRubyInfos(std::move(pRubyInfo));

            pNextRubyAttr
                = rParaPortion.GetNode()->GetCharAttribs().FindNextAttrib(EE_CHAR_RUBY, nTextPos);
            pTPRubyStart = nullptr;
        }
    }
}

bool ImpEditEngine::CreateLines( sal_Int32 nPara, sal_uInt32 nStartPosY )
{
    assert(GetParaPortions().exists(nPara) && "Portion paragraph index is not valid");
    ParaPortion& rParaPortion = GetParaPortions().getRef(nPara);

    // sal_Bool: Changes in the height of paragraph Yes / No - sal_True/sal_False
    assert(rParaPortion.GetNode() && "Portion without Node in CreateLines" );
    DBG_ASSERT( rParaPortion.IsVisible(), "Invisible paragraphs not formatted!" );
    DBG_ASSERT( rParaPortion.IsInvalid(), "CreateLines: Portion not invalid!" );

    bool bProcessingEmptyLine = rParaPortion.GetNode()->Len() == 0 ;
    bool bEmptyNodeWithPolygon = rParaPortion.GetNode()->Len() == 0 && GetTextRanger();


    // Fast special treatment for empty paragraphs...
    bool bEmptyParagraph = rParaPortion.GetNode()->Len() == 0 && !GetTextRanger();
    if (bEmptyParagraph)
        return createLinesForEmptyParagraph(rParaPortion);

    sal_Int64 nCurrentPosY = nStartPosY;
    // If we're allowed to skip parts outside and this cannot possibly fit in the given height,
    // bail out to avoid possibly formatting a lot of text that will not be used. For the first
    // paragraph still format at least a bit.
    if( mbSkipOutsideFormat && nPara != 0
        && !maStatus.AutoPageHeight() && maPaperSize.Height() < nCurrentPosY )
    {
        return false;
    }

    //If the paragraph SvxFrameDirection is Stacked, use STACKED
    const SvxFrameDirectionItem* pFrameDirItem = &GetParaAttrib(nPara, EE_PARA_WRITINGDIR);
    bool bStacked = pFrameDirItem->GetValue() == SvxFrameDirection::Stacked;
    if (bStacked)
        maStatus.TurnOnFlags(EEControlBits::STACKED);
    else
        maStatus.TurnOffFlags(EEControlBits::STACKED);

    // Initialization...

    if (rParaPortion.GetLines().Count() == 0)
    {
        rParaPortion.GetLines().Append(std::make_unique<EditLine>());
    }

    // Get Paragraph attributes...

    ContentNode* const pNode = rParaPortion.GetNode();

    bool bRightToLeftPara = IsRightToLeft( nPara );

    SvxAdjust eJustification = GetJustification( nPara );
    bool bHyphenatePara = pNode->GetContentAttribs().GetItem( EE_PARA_HYPHENATE ).GetValue();
    sal_Int32 nSpaceBefore      = 0;
    sal_Int32 nMinLabelWidth    = 0;
    sal_Int32 nSpaceBeforeAndMinLabelWidth = GetSpaceBeforeAndMinLabelWidth( pNode, &nSpaceBefore, &nMinLabelWidth&nbsp;);
    const SvxLRSpaceItem& rLRItem = GetLRSpaceItem( pNode );
    const SvxLineSpacingItem& rLSItem = pNode->GetContentAttribs().GetItem( EE_PARA_SBL );
    const bool bScriptSpace = pNode->GetContentAttribs().GetItem( EE_PARA_ASIANCJKSPACING ).GetValue();

    const short nInvalidDiff = rParaPortion.GetInvalidDiff();
    const sal_Int32 nInvalidStart = rParaPortion.GetInvalidPosStart();
    const sal_Int32 nInvalidEnd =  nInvalidStart + std::abs( nInvalidDiff );

    bool bQuickFormat = false;

    // Determine if quick format should be used
    if (!bEmptyNodeWithPolygon && !HasScriptType(nPara, i18n::ScriptType::COMPLEX))
    {
        if (rParaPortion.IsSimpleInvalid() &&
            rParaPortion.GetInvalidDiff() > 0 &&
            pNode->GetString().indexOf(CH_FEATURE, nInvalidStart) > nInvalidEnd)
        {
            bQuickFormat = true;
        }
        else if (rParaPortion.IsSimpleInvalid() && nInvalidDiff < 0)
        {
            // check if delete over the portion boundaries was done...
            sal_Int32 nStart = nInvalidStart;  // DOUBLE !!!!!!!!!!!!!!!
            sal_Int32 nEnd = nStart - nInvalidDiff;  // negative
            bQuickFormat = true;
            sal_Int32 nPos = 0;
            for (auto const& pTextPortion : rParaPortion.GetTextPortions())
            {
                // There must be no start / end in the deleted area.
                nPos = nPos + pTextPortion->GetLen();
                if (nPos > nStart && nPos < nEnd)
                {
                    bQuickFormat = false;
                    break;
                }
            }
        }
    }

    // Saving both layout mode and language (since I'm potentially changing both)
    GetRefDevice()->Push( vcl::PushFlags::TEXTLAYOUTMODE|vcl::PushFlags::TEXTLANGUAGE );

    ImplInitLayoutMode(*GetRefDevice(), nPara, -1);

    sal_Int32 nRealInvalidStart = nInvalidStart;

    if (bEmptyNodeWithPolygon)
    {
        TextPortion* pDummyPortion = new TextPortion( 0 );
        rParaPortion.GetTextPortions().Reset();
        rParaPortion.GetTextPortions().Append(pDummyPortion);
    }
    else if ( bQuickFormat )
    {
        // faster Method:
        RecalcTextPortion(rParaPortion, nInvalidStart, nInvalidDiff);
    }
    else    // nRealInvalidStart can be before InvalidStart, since Portions were deleted...
    {
        CreateTextPortions(rParaPortion, nRealInvalidStart);
    }

    // Search for line with InvalidPos, start one line before
    // Flag the line => do not remove it !


    sal_Int32 nLine = rParaPortion.GetLines().Count()-1;
    for ( sal_Int32 nL = 0; nL <= nLine; nL++ )
    {
        EditLine& rLine = rParaPortion.GetLines()[nL];
        if ( rLine.GetEnd() > nRealInvalidStart )  // not nInvalidStart!
        {
            nLine = nL;
            break;
        }
        rLine.SetValid();
    }
    // Begin one line before...
    // If it is typed at the end, the line in front cannot change.
    if (nLine && (!rParaPortion.IsSimpleInvalid() ||
                    (nInvalidEnd < pNode->Len()) ||
                    (nInvalidDiff <= 0)))
    {
        nLine--;
    }

    tools::Rectangle aBulletArea{Point(), Point()};

    if (!nLine)
    {
        aBulletArea = GetEditEnginePtr()->GetBulletArea(GetParaPortions().GetPos(&rParaPortion));
        if ( !aBulletArea.IsWidthEmpty() && aBulletArea.Right() > 0 )
            rParaPortion.SetBulletX(sal_Int32(scaleXSpacingValue(aBulletArea.Right())));
        else
            rParaPortion.SetBulletX( 0 ); // if Bullet is set incorrectly
    }

    // Reformat all lines from here...

    int nStartNextLineAfterMultiLineField = 0;

    sal_Int32 nDelFromLine = -1;
    bool bLineBreak = false;

    EditLine* pLine = &rParaPortion.GetLines()[nLine];
    sal_Int32 nIndex = pLine->GetStart();
    EditLine aSaveLine( *pLine );

    SvxFont aTmpFont( pNode->GetCharAttribs().GetDefFont() );

    KernArray aCharPositionArray;

    bool bSameLineAgain = false;    // For TextRanger, if the height changes.
    TabInfo aCurrentTab;

    bool bForceOneRun = bEmptyNodeWithPolygon;
    bool bCompressedChars = false;

    while ( ( nIndex < pNode->Len() ) || bForceOneRun )
    {
        assert(pLine);

        bForceOneRun = false;
        bool bFieldStartNextLine = false;

        bool bEOL = false;
        bool bEOC = false;
        sal_Int32 nPortionStart = 0;
        sal_Int32 nPortionEnd = 0;

        auto stMetrics = GetFontUnitMetrics(pNode);
        tools::Long nStartX
            = scaleXSpacingValue(rLRItem.ResolveTextLeft(stMetrics) + nSpaceBeforeAndMinLabelWidth);
        // Multiline hyperlink may need to know if the next line is bigger.
        tools::Long nStartXNextLine = nStartX;
        if ( nIndex == 0 )
        {
            tools::Long nFI = scaleXSpacingValue(rLRItem.ResolveTextFirstLineOffset(stMetrics));
            nStartX += nFI;

            if (!nLine && rParaPortion.GetBulletX() > nStartX)
            {
                nStartX = rParaPortion.GetBulletX();
            }
        }

        nStartX += nStartNextLineAfterMultiLineField;
        nStartNextLineAfterMultiLineField = 0;
        tools::Long nMaxLineWidth = calculateMaxLineWidth(nStartX, rLRItem, stMetrics);

        // Problem:
        // Since formatting starts a line _before_ the invalid position,
     // the positions unfortunately have to be redefined...
        // Solution:
        // The line before can only become longer, not smaller
        // =>...
        pLine->GetCharPosArray().clear();

        // tdf#162803: Stale kashida position data also needs to be cleared on each layout.
        pLine->GetKashidaArray().clear();

        sal_Int32 nTmpPos = nIndex;
        sal_Int32 nTmpPortion = pLine->GetStartPortion();
        tools::Long nTmpWidth = 0;
        tools::Long nXWidth = nMaxLineWidth;

        std::deque<tools::Long>* pTextRanges = nullptr;
        tools::Long nTextExtraYOffset = 0;
        tools::Long nTextXOffset = 0;
        tools::Long nTextLineHeight = 0;
        if ( GetTextRanger() )
        {
            GetTextRanger()->SetVertical( IsEffectivelyVertical() );

            tools::Long nTextY = nCurrentPosY + GetEditCursor(rParaPortion, *pLine, pLine->GetStart(), CursorFlags()).Top();
            if ( !bSameLineAgain )
            {
                SeekCursor( pNode, nTmpPos+1, aTmpFont );
                aTmpFont.SetPhysFont(*GetRefDevice());
                ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());

                if ( IsFixedCellHeight() )
                    nTextLineHeight = ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() );
                else
                    nTextLineHeight = aTmpFont.GetPhysTxtSize( GetRefDevice() ).Height();
                // Metrics can be greater
                FormatterFontMetric aTempFormatterMetrics;
                RecalcFormatterFontMetrics( aTempFormatterMetrics, aTmpFont );
                sal_uInt16 nLineHeight = aTempFormatterMetrics.GetHeight();
                if ( nLineHeight > nTextLineHeight )
                    nTextLineHeight = nLineHeight;
            }
            else
                nTextLineHeight = pLine->GetHeight();

            nXWidth = 0;
            while ( !nXWidth )
            {
                tools::Long nYOff = nTextY + nTextExtraYOffset;
                tools::Long nYDiff = nTextLineHeight;
                if ( IsEffectivelyVertical() )
                {
                    tools::Long nMaxPolygonX = GetTextRanger()->GetBoundRect().Right();
                    nYOff = nMaxPolygonX-nYOff;
                    nYDiff = -nTextLineHeight;
                }
                pTextRanges = GetTextRanger()->GetTextRanges( Range( nYOff, nYOff + nYDiff ) );
                assert( pTextRanges && "GetTextRanges?!" );
                tools::Long nMaxRangeWidth = 0;
                // Use the widest range...
                // The widest range could be a bit confusing, so normally it
                // is the first one. Best with gaps.
                assert(pTextRanges->size() % 2 == 0 && "textranges are always in pairs");
                if (!pTextRanges->empty())
                {
                    tools::Long nA = pTextRanges->at(0);
                    tools::Long nB = pTextRanges->at(1);
                    DBG_ASSERT( nA <= nB, "TextRange distorted?" );
                    tools::Long nW = nB - nA;
                    if ( nW > nMaxRangeWidth )
                    {
                        nMaxRangeWidth = nW;
                        nTextXOffset = nA;
                    }
                }
                nXWidth = nMaxRangeWidth;
                if (nXWidth)
                    nMaxLineWidth
                        = nXWidth - nStartX - scaleXSpacingValue(rLRItem.ResolveRight(stMetrics));
                else
                {
                    // Try further down in the polygon.
                    // Below the polygon use the Paper Width.
                    nTextExtraYOffset += std::max( static_cast<tools::Long>(nTextLineHeight / 10), tools::Long(1) );
                    if ( ( nTextY + nTextExtraYOffset  ) > GetTextRanger()->GetBoundRect().Bottom() )
                    {
                        nXWidth = getWidthDirectionAware(GetPaperSize());
                        if ( !nXWidth ) // AutoPaperSize
                            nXWidth = 0x7FFFFFFF;
                    }
                }
            }
        }

        // search for Portion that no longer fits in line...
        TextPortion* pPortion = nullptr;
        sal_Int32 nPortionLen = 0;
        bool bContinueLastPortion = false;
        bool bBrokenLine = false;
        bLineBreak = false;
        const EditCharAttrib* pNextFeature = pNode->GetCharAttribs().FindFeature( pLine->GetStart() );
        while ( ( nTmpWidth < nXWidth ) && !bEOL )
        {
            const sal_Int32 nTextPortions = rParaPortion.GetTextPortions().Count();
            assert(nTextPortions > 0);
            bContinueLastPortion = (nTmpPortion >= nTextPortions);
            if (bContinueLastPortion)
            {
                if (nTmpPos >= pNode->Len())
                    break;  // while

                // Continue with remainder. This only to have *some* valid
                // X-values and not endlessly create new lines until DOOM...
                // Happened in the scenario of tdf#104152 where inserting a
                // paragraph lead to a11y attempting to format the doc to
                // obtain content when notified.
                nTmpPortion = nTextPortions - 1;
                SAL_WARN("editeng","ImpEditEngine::CreateLines - continuation of a broken portion");
            }

            nPortionStart = nTmpPos;
            pPortion = &rParaPortion.GetTextPortions()[nTmpPortion];
            if ( !bContinueLastPortion && pPortion->GetKind() == PortionKind::HYPHENATOR )
            {
                // Throw away a Portion, if necessary correct the one before,
                // if the Hyph portion has swallowed a character...
                sal_Int32 nTmpLen = pPortion->GetLen();
                rParaPortion.GetTextPortions().Remove( nTmpPortion );
                if (nTmpPortion && nTmpLen)
                {
                    nTmpPortion--;
                    TextPortion& rPrev = rParaPortion.GetTextPortions()[nTmpPortion];
                    DBG_ASSERT( rPrev.GetKind() == PortionKind::TEXT, "Portion?!" );
                    nTmpWidth -= rPrev.GetSize().Width();
                    nTmpPos = nTmpPos - rPrev.GetLen();
                    rPrev.SetLen(rPrev.GetLen() + nTmpLen);
                    rPrev.setWidth(-1);
                }

                assert(nTmpPortion < rParaPortion.GetTextPortions().Count() && "No more Portions left!");
                pPortion = &rParaPortion.GetTextPortions()[nTmpPortion];
            }

            if (bContinueLastPortion)
            {
                // Note that this may point behind the portion and is only to
                // be used with the node's string offsets to generate X-values.
                nPortionLen = pNode->Len() - nPortionStart;
            }
            else
            {
                nPortionLen = pPortion->GetLen();
            }

            DBG_ASSERT( pPortion->GetKind() != PortionKind::HYPHENATOR, "CreateLines: Hyphenator-Portion!" );
            DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion in CreateLines ?!" );
            if ( pNextFeature && ( pNextFeature->GetStart() == nTmpPos ) )
            {
                SAL_WARN_IF( bContinueLastPortion,
                        "editeng","ImpEditEngine::CreateLines - feature in continued portion will be wrong");
                sal_uInt16 nWhich = pNextFeature->GetItem()->Which();
                switch ( nWhich )
                {
                    case EE_FEATURE_TAB:
                    {
                        tools::Long nOldTmpWidth = nTmpWidth;

                        // Search for Tab-Pos...
                        tools::Long nCurPos = nTmpWidth + nStartX;
                        // consider scaling
                        double fFontScalingX = maScalingParameters.fFontX;
                        if (maStatus.DoStretch() && (fFontScalingX != 1.0))
                            nCurPos = basegfx::fround<tools::Long>(double(nCurPos) / std::max(fFontScalingX, 0.01));

                        short nAllSpaceBeforeText = short(rLRItem.ResolveTextLeft({}));
                        aCurrentTab.aTabStop = pNode->GetContentAttribs().FindTabStop( nCurPos - nAllSpaceBeforeText , maEditDoc.GetDefTab() );
                        aCurrentTab.nTabPos = tools::Long(aCurrentTab.aTabStop.GetTabPos() + nAllSpaceBeforeText);
                        aCurrentTab.bValid = false;

                        // Switch direction in R2L para...
                        if ( bRightToLeftPara )
                        {
                            if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right )
                                aCurrentTab.aTabStop.GetAdjustment() = SvxTabAdjust::Left;
                            else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Left )
                                aCurrentTab.aTabStop.GetAdjustment() = SvxTabAdjust::Right;
                        }

                        if ( ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right ) ||
                             ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center ) ||
                             ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal ) )
                        {
                            // For LEFT / DEFAULT this tab is not considered.
                            aCurrentTab.bValid = true;
                            aCurrentTab.nStartPosX = nTmpWidth;
                            aCurrentTab.nTabPortion = nTmpPortion;
                        }

                        if (nMaxLineWidth < aCurrentTab.nTabPos && nTmpWidth != nMaxLineWidth - 1)
                            aCurrentTab.nTabPos = nMaxLineWidth - 1;

                        pPortion->SetKind(PortionKind::TAB);
                        pPortion->SetExtraValue( aCurrentTab.aTabStop.GetFill() );
                        pPortion->setWidth( aCurrentTab.nTabPos - (nTmpWidth+nStartX) );

                        // Height needed...
                        SeekCursor( pNode, nTmpPos+1, aTmpFont );
                        pPortion->setHeight( GetRefDevice()->GetTextHeight() );

                        DBG_ASSERT( pPortion->GetSize().Width() >= 0, "Tab incorrectly calculated!" );

                        nTmpWidth = aCurrentTab.nTabPos-nStartX;

                        // If this is the first token on the line,
                        // and nTmpWidth > maPaperSize.Width, => infinite loop!
                        if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
                        {
                            // What now?
                            // make the tab fitting
                            pPortion->setWidth( nXWidth-nOldTmpWidth );
                            nTmpWidth = nXWidth-1;
                            bEOL = true;
                            bBrokenLine = true;
                        }
                        KernArray& rArray = pLine->GetCharPosArray();
                        size_t nPos = nTmpPos - pLine->GetStart();
                        rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
                        bCompressedChars = false;
                    }
                    break;
                    case EE_FEATURE_LINEBR:
                    {
                        assert( pPortion );
                        pPortion->setWidth(0);
                        bEOL = true;
                        bLineBreak = true;
                        pPortion->SetKind( PortionKind::LINEBREAK );
                        bCompressedChars = false;
                        KernArray& rArray = pLine->GetCharPosArray();
                        size_t nPos = nTmpPos - pLine->GetStart();
                        rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
                    }
                    break;
                    case EE_FEATURE_FIELD:
                    {
                        SeekCursor( pNode, nTmpPos+1, aTmpFont );
                        aTmpFont.SetPhysFont(*GetRefDevice());
                        ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());

                        OUString aFieldValue = static_cast<const EditCharAttribField*>(pNextFeature)->GetFieldValue();
                        // get size, but also DXArray to allow length information in line breaking below
                        KernArray aTmpDXArray;
                        pPortion->SetSize(aTmpFont.QuickGetTextSize(GetRefDevice(),
                            aFieldValue, 0, aFieldValue.getLength(), &aTmpDXArray));

                        // So no scrolling for oversized fields
                        if (pPortion->GetSize().Width() > nXWidth - nTmpWidth)
                        {
                            // create ExtraPortionInfo on-demand, flush lineBreaksList
                            ExtraPortionInfo *pExtraInfo = pPortion->GetExtraInfos();

                            if(nullptr == pExtraInfo)
                            {
                                pExtraInfo = new ExtraPortionInfo();
                                pExtraInfo->nOrgWidth = nXWidth;
                                pPortion->SetExtraInfos(pExtraInfo);
                            }
                            else
                            {
                                pExtraInfo->lineBreaksList.clear();
                            }

                            // iterate over CellBreaks using XBreakIterator to be on the
                            // safe side with international texts/charSets
                            Reference < i18n::XBreakIterator > xBreakIterator(ImplGetBreakIterator());
                            const sal_Int32 nTextLength(aFieldValue.getLength());
                            const lang::Locale aLocale(GetLocale(EditPaM(pNode, nPortionStart)));
                            sal_Int32 nDone(0);
                            sal_Int32 nNextCellBreak(
                                xBreakIterator->nextCharacters(
                                        aFieldValue,
                                        0,
                                        aLocale,
                                        css::i18n::CharacterIteratorMode::SKIPCELL,
                                        0,
                                        nDone));
                            sal_Int32 nLastCellBreak(0);
                            sal_Int32 nLineStartX(0);
                            nLineStartX = -nTmpWidth;

                            // always add 1st line break (safe, we already know we are larger than nXWidth)
                            pExtraInfo->lineBreaksList.push_back(0);

                            for(sal_Int32 a(0); a < nTextLength; a++)
                            {
                                if(a == nNextCellBreak)
                                {
                                    // check width
                                    if(aTmpDXArray[a] - nLineStartX > nXWidth)
                                    {
                                        // new CellBreak does not fit in current line, need to
                                        // create a break at LastCellBreak - but do not add 1st
                                        // line break twice for very tall frames
                                        if(0 != a)
                                        {
                                            pExtraInfo->lineBreaksList.push_back(a);
                                            // the following lines may be different sized
                                            if (nStartX > nStartXNextLine)
                                            {
                                                nXWidth += nStartX - nStartXNextLine;
                                                pLine->SetNextLinePosXDiff(nStartX
                                                                           - nStartXNextLine);
                                                nStartXNextLine = nStartX;
                                            }
                                        }
                                        else
                                        {
                                            //even the 1. char does not fit..
                                            //this means the field should start on next line
                                            //except if the actual line is a full line already
                                            if (nLineStartX < 0 || nStartX > nStartXNextLine)
                                                bFieldStartNextLine = true;
                                        }

                                        // moveLineStart forward in X
                                        nLineStartX = aTmpDXArray[nLastCellBreak];
                                    }

                                    // update CellBreak iteration values
                                    nLastCellBreak = a;
                                    nNextCellBreak = xBreakIterator->nextCharacters(
                                        aFieldValue,
                                        a,
                                        aLocale,
                                        css::i18n::CharacterIteratorMode::SKIPCELL,
                                        1,
                                        nDone);
                                }
                            }
                            //next Line should start here... after this field end
                            if (!bFieldStartNextLine)
                                nStartNextLineAfterMultiLineField
                                    = aTmpDXArray[nTextLength - 1] - nLineStartX;
                            else if (pExtraInfo)
                            {
                                // if the 1. character does not fit,
                                // but there is pExtraInfo, then delete it
                                pPortion->SetExtraInfos(nullptr);
                                pExtraInfo = nullptr;
                            }
                        }
                        nTmpWidth += pPortion->GetSize().Width();
                        KernArray& rArray = pLine->GetCharPosArray();
                        size_t nPos = nTmpPos - pLine->GetStart();
                        rArray.insert(rArray.begin()+nPos, pPortion->GetSize().Width());
                        pPortion->SetKind(PortionKind::FIELD);
                        // If this is the first token on the line,
                        // and nTmpWidth > maPaperSize.Width, => infinite loop!
                        if ( ( nTmpWidth >= nXWidth ) && ( nTmpPortion == pLine->GetStartPortion() ) )
                        {
                            nTmpWidth = nXWidth-1;
                            bEOL = true;
                            bBrokenLine = true;
                        }
                        // Compression in Fields????
                        // I think this could be a little bit difficult and is not very useful
                        bCompressedChars = false;
                    }
                    break;
                    default:    OSL_FAIL( "What feature?" );
                }
                pNextFeature = pNode->GetCharAttribs().FindFeature( pNextFeature->GetStart() + 1  );
            }
            else
            {
                DBG_ASSERT( nPortionLen || bProcessingEmptyLine, "Empty Portion - Extra Space?!" );
                SeekCursor( pNode, nTmpPos+1, aTmpFont );
                aTmpFont.SetPhysFont(*GetRefDevice());
                ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());

                if (!bContinueLastPortion)
                    pPortion->SetRightToLeftLevel( GetRightToLeft( nPara, nTmpPos+1 ) );

                if (bContinueLastPortion)
                {
                     Size aSize = aTmpFont.QuickGetTextSize( GetRefDevice(), rParaPortion.GetNode()->GetString(), nTmpPos, nPortionLen, &aCharPositionArray, bStacked);
                     pPortion->adjustSize(aSize.Width(), 0);
                     if (pPortion->GetSize().Height() < aSize.Height())
                         pPortion->setHeight(aSize.Height());
                }
                else
                {
                    Size aSize = aTmpFont.QuickGetTextSize(GetRefDevice(), rParaPortion.GetNode()->GetString(), nTmpPos, nPortionLen, &aCharPositionArray, bStacked);
                    pPortion->SetSize(aSize);
                }

                // #i9050# Do Kerning also behind portions...
                if ( ( aTmpFont.GetFixKerning() > 0 ) && ( ( nTmpPos + nPortionLen ) < pNode->Len() ) )
                    pPortion->adjustSize(aTmpFont.GetFixKerning(), 0);
                if ( IsFixedCellHeight() )
                {
                    pPortion->setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) );
                }
                // The array is  generally flattened at the beginning
                // => Always simply quick inserts.
                size_t nPos = nTmpPos - pLine->GetStart();
                KernArray& rArray = pLine->GetCharPosArray();
                rArray.insert( rArray.begin() + nPos, aCharPositionArray.data(), aCharPositionArray.data() + nPortionLen);

                // And now check for Compression:
                if ( !bContinueLastPortion && nPortionLen && GetAsianCompressionMode() != CharCompressType::NONE )
                {
                    double* pDXArray = rArray.data() + nTmpPos - pLine->GetStart();
                    bCompressedChars |= ImplCalcAsianCompression(
                        pNode, pPortion, nTmpPos, pDXArray, 10000, false);
                }

                nTmpWidth += pPortion->GetSize().Width();

                sal_Int32 _nPortionEnd = nTmpPos + nPortionLen;
                if( bScriptSpace && ( _nPortionEnd < pNode->Len() ) && ( nTmpWidth < nXWidth ) && IsScriptChange( EditPaM( pNode, _nPortionEnd ) ) )
                {
                    bool bAllow = false;
                    sal_uInt16 nScriptTypeLeft = GetI18NScriptType( EditPaM( pNode, _nPortionEnd ) );
                    sal_uInt16 nScriptTypeRight = GetI18NScriptType( EditPaM( pNode, _nPortionEnd+1 ) );
                    if ( ( nScriptTypeLeft == i18n::ScriptType::ASIAN ) || ( nScriptTypeRight == i18n::ScriptType::ASIAN ) )
                        bAllow = true;

                    // No spacing within L2R/R2L nesting
                    if ( bAllow )
                    {
                        tools::Long nExtraSpace = pPortion->GetSize().Height() / 5;
                        nExtraSpace = scaleXSpacingValue(nExtraSpace);
                        pPortion->adjustSize(nExtraSpace, 0);
                        nTmpWidth += nExtraSpace;
                    }
                }
            }

            if ( aCurrentTab.bValid && ( nTmpPortion != aCurrentTab.nTabPortion ) )
            {
                tools::Long nWidthAfterTab = 0;
                for ( sal_Int32 n = aCurrentTab.nTabPortion+1; n <= nTmpPortion; n++  )
                {
                    const TextPortion& rTextPortion = rParaPortion.GetTextPortions()[n];
                    nWidthAfterTab += rTextPortion.GetSize().Width();
                }
                tools::Long nW = nWidthAfterTab;   // Length before tab position
                if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Right )
                {
                    // Do nothing
                }
                else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Center )
                {
                    nW = nWidthAfterTab/2;
                }
                else if ( aCurrentTab.aTabStop.GetAdjustment() == SvxTabAdjust::Decimal )
                {
                    EditPaM aSelectionStart(rParaPortion.GetNode(), nTmpPos);
                    EditPaM aSelectionEnd(rParaPortion.GetNode(), nTmpPos + nPortionLen);
                    EditSelection aSelection(aSelectionStart, aSelectionEnd);
                    OUString aText = GetSelected(aSelection);

                    sal_Int32 nDecPos = aText.indexOf( aCurrentTab.aTabStop.GetDecimal() );
                    if ( nDecPos != -1 )
                    {
                        nW -= rParaPortion.GetTextPortions()[nTmpPortion].GetSize().Width();
                        nW += aTmpFont.QuickGetTextSize(GetRefDevice(), rParaPortion.GetNode()->GetString(), nTmpPos, nDecPos, nullptr).Width();
                        aCurrentTab.bValid = false;
                    }
                }
                else
                {
                    OSL_FAIL( "CreateLines: Tab not handled!" );
                }
                tools::Long nMaxW = aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nStartX;
                if ( nW >= nMaxW )
                {
                    nW = nMaxW;
                    aCurrentTab.bValid = false;
                }
                TextPortion& rTabPortion = rParaPortion.GetTextPortions()[aCurrentTab.nTabPortion];
                rTabPortion.setWidth( aCurrentTab.nTabPos - aCurrentTab.nStartPosX - nW - nStartX );
                nTmpWidth = aCurrentTab.nStartPosX + rTabPortion.GetSize().Width() + nWidthAfterTab;
            }

            nTmpPos = nTmpPos + nPortionLen;
            nPortionEnd = nTmpPos;
            nTmpPortion++;
            if (maStatus.OneCharPerLine())
                bEOL = true;
        }

        DBG_ASSERT( pPortion, "no portion!?" );

        aCurrentTab.bValid = false;

        assert(pLine);

        // this was possibly a portion too far:
        bool bFixedEnd = false;
        if (maStatus.OneCharPerLine())
        {
            // State before Portion (apart from nTmpWidth):
            nTmpPos -= pPortion ? nPortionLen : 0;
            nPortionStart = nTmpPos;
            nTmpPortion--;

            bEOL = true;
            bEOC = false;

            // And now just one character:
            nTmpPos++;
            nTmpPortion++;
            nPortionEnd = nTmpPortion;
            // one Non-Feature-Portion has to be wrapped
            if ( pPortion && nPortionLen > 1 )
            {
                DBG_ASSERT( pPortion->GetKind() == PortionKind::TEXT, "Len>1, but no TextPortion?" );
                nTmpWidth -= pPortion->GetSize().Width();
                sal_Int32 nP = SplitTextPortion(rParaPortion, nTmpPos, pLine);
                nTmpWidth += rParaPortion.GetTextPortions()[nP].GetSize().Width();
            }
        }
        else if ( nTmpWidth >= nXWidth )
        {
            nPortionEnd = nTmpPos;
            nTmpPos -= pPortion ? nPortionLen : 0;
            nPortionStart = nTmpPos;
            nTmpPortion--;
            bEOL = false;
            bEOC = false;
            if( pPortion ) switch ( pPortion->GetKind() )
            {
                case PortionKind::TEXT:
                {
                    nTmpWidth -= pPortion->GetSize().Width();
                }
                break;
                case PortionKind::FIELD:
                case PortionKind::TAB:
                {
                    nTmpWidth -= pPortion->GetSize().Width();
                    bEOL = true;
                    bFixedEnd = true;
                }
                break;
                default:
                {
                    //  A feature is not wrapped:
                    DBG_ASSERT( ( pPortion->GetKind() == PortionKind::LINEBREAK ), "What Feature ?" );
                    bEOL = true;
                    bFixedEnd = true;
                }
            }
        }
        else
        {
            bEOL = true;
            bEOC = true;
            pLine->SetEnd( nPortionEnd );
            assert(rParaPortion.GetTextPortions().Count() && "No TextPortions?");
            pLine->SetEndPortion(rParaPortion.GetTextPortions().Count() - 1);
        }

        if (maStatus.OneCharPerLine())
        {
            pLine->SetEnd( nPortionEnd );
            pLine->SetEndPortion( nTmpPortion-1 );
        }
        else if ( bFixedEnd )
        {
            if (bFieldStartNextLine)
            {
                pLine->SetEnd(nPortionStart);
                pLine->SetEndPortion(nTmpPortion - 1);
            }
            else
            {
                pLine->SetEnd(nPortionStart + 1);
                pLine->SetEndPortion(nTmpPortion);
            }
        }
        else if ( bLineBreak || bBrokenLine )
        {
            if (bFieldStartNextLine)
            {
                pLine->SetEnd(nPortionStart);
                pLine->SetEndPortion(nTmpPortion - 2);
            }
            else
            {
                pLine->SetEnd(nPortionStart + 1);
                pLine->SetEndPortion(nTmpPortion - 1);
            }
            bEOC = false// was set above, maybe change the sequence of the if's?
        }
        else if ( !bEOL && !bContinueLastPortion )
        {
            DBG_ASSERT( pPortion && ((nPortionEnd-nPortionStart) == pPortion->GetLen()), "However, another portion?!" );
            tools::Long nRemainingWidth = !maStatus.IsSingleLine() ?
                nMaxLineWidth - nTmpWidth : pLine->GetCharPosArray()[pLine->GetCharPosArray().size() - 1] + 1;
            bool bCanHyphenate = ( aTmpFont.GetCharSet() != RTL_TEXTENCODING_SYMBOL );
            if ( bCompressedChars && pPortion && ( pPortion->GetLen() > 1 ) && pPortion->GetExtraInfos() && pPortion->GetExtraInfos()->bCompressed )
            {
                // I need the manipulated DXArray for determining the break position...
                double* pDXArray = pLine->GetCharPosArray().data() + (nPortionStart - pLine->GetStart());
                ImplCalcAsianCompression(
                    pNode, pPortion, nPortionStart, pDXArray, 10000, true);
            }
            if( pPortion )
                ImpBreakLine(rParaPortion, *pLine, pPortion, nPortionStart, nRemainingWidth, bCanHyphenate && bHyphenatePara);
        }


        // Line finished => adjust

        populateRubyInfo(rParaPortion, pLine);

        // CalcTextSize should be replaced by a continuous registering!
        Size aTextSize = pLine->CalcTextSize(rParaPortion);

        if ( aTextSize.Height() == 0 )
        {
            SeekCursor( pNode, pLine->GetStart()+1, aTmpFont );
            aTmpFont.SetPhysFont(*mpRefDev);
            ImplInitDigitMode(*mpRefDev, aTmpFont.GetLanguage());

            if ( IsFixedCellHeight() )
                aTextSize.setHeight( ImplCalculateFontIndependentLineSpacing( aTmpFont.GetFontHeight() ) );
            else
                aTextSize.setHeight( aTmpFont.GetPhysTxtSize(mpRefDev).Height() );
            pLine->SetHeight( static_cast<sal_uInt16>(aTextSize.Height()) );
        }

        // The font metrics can not be calculated continuously, if the font is
        // set anyway, because a large font only after wrapping suddenly ends
        // up in the next line => Font metrics too big.
        FormatterFontMetric aFormatterMetrics;
        sal_Int32 nTPos = pLine->GetStart();
        for ( sal_Int32 nP = pLine->GetStartPortion(); nP <= pLine->GetEndPortion(); nP++ )
        {
            const TextPortion& rTP = rParaPortion.GetTextPortions()[nP];
            // problem with hard font height attribute, when everything but the line break has this attribute
            if ( rTP.GetKind() != PortionKind::LINEBREAK )
            {
                SeekCursor( pNode, nTPos+1, aTmpFont );
                aTmpFont.SetPhysFont(*GetRefDevice());
                ImplInitDigitMode(*GetRefDevice(), aTmpFont.GetLanguage());
                RecalcFormatterFontMetrics( aFormatterMetrics, aTmpFont );

                if(const auto* pRubyInfo = rTP.GetRubyInfos(); pRubyInfo)
                {
                    aFormatterMetrics.nMaxAscent = std::max(aFormatterMetrics.nMaxAscent, pRubyInfo->nMaxAscent);
                }
            }
            nTPos = nTPos + rTP.GetLen();
        }
        sal_uInt16 nLineHeight = aFormatterMetrics.GetHeight();
        if ( nLineHeight > pLine->GetHeight() )
            pLine->SetHeight( nLineHeight );
        pLine->SetMaxAscent( aFormatterMetrics.nMaxAscent );

        bSameLineAgain = false;
        if ( GetTextRanger() && ( pLine->GetHeight() > nTextLineHeight ) )
        {
            // put down with the other size!
            bSameLineAgain = true;
        }

        if (!bSameLineAgain && !maStatus.IsOutliner())
        {
            if ( rLSItem.GetLineSpaceRule() == SvxLineSpaceRule::Min )
            {
                double fMinHeight = scaleYSpacingValue(rLSItem.GetLineHeight());
                sal_uInt16 nMinHeight = basegfx::fround(fMinHeight);

                sal_uInt16 nTxtHeight = pLine->GetHeight();
                if ( nTxtHeight < nMinHeight )
                {
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=91 H=93 G=91

¤ Dauer der Verarbeitung: 0.22 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.