/* -*- 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 <sal/config.h>
 
#include <algorithm>
 
#include <editeng/numitem.hxx>
 
#include <com/sun/star/text/VertOrientation.hpp>
#include <comphelper/propertyvalue.hxx>
#include <editeng/brushitem.hxx>
#include <rtl/ustrbuf.hxx>
#include <vcl/font.hxx>
#include <vcl/settings.hxx>
#include <editeng/editids.hrc>
#include <editeng/numdef.hxx>
#include <vcl/graph.hxx>
#include <vcl/outdev.hxx>
#include <vcl/svapp.hxx>
#include <com/sun/star/text/XNumberingFormatter.hpp>
#include <com/sun/star/text/DefaultNumberingProvider.hpp>
#include <com/sun/star/text/XDefaultNumberingProvider.hpp>
#include <com/sun/star/style/NumberingType.hpp>
#include <com/sun/star/lang/IllegalArgumentException.hpp>
#include <com/sun/star/beans/PropertyValue.hpp>
#include <comphelper/fileformat.h>
#include <comphelper/processfactory.hxx>
#include <tools/mapunit.hxx>
#include <tools/stream.hxx>
#include <tools/debug.hxx>
#include <tools/GenericTypeSerializer.hxx>
#include <comphelper/configuration.hxx>
#include <libxml/xmlwriter.h>
#include <editeng/unonrule.hxx>
#include <sal/log.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <editeng/legacyitem.hxx>
 
constexpr sal_Int32 DEF_WRITER_LSPACE = 500; //Standard Indentation
constexpr sal_Int32 DEF_DRAW_LSPACE = 800; //Standard Indentation
 
constexpr sal_uInt16 NUMITEM_VERSION_03 = 0x03;
constexpr sal_uInt16 NUMITEM_VERSION_04 = 0x04;
 
using namespace ::com::sun::star;
using namespace ::com::sun::star::lang;
using namespace ::com::sun::star::uno;
using namespace ::com::sun::star::text;
using namespace ::com::sun::star::beans;
using namespace ::com::sun::star::style;
 
sal_Int32 SvxNumberType::nRefCount = 0;
css::uno::Reference<css::text::XNumberingFormatter> SvxNumberType::xFormatter;
static void lcl_getFormatter(css::uno::Reference<css::text::XNumberingFormatter>& _xFormatter)
{
    if(_xFormatter.is())
        return;
 
    try
    {
        const Reference<XComponentContext>&         xContext( ::comphelper::getProcessComponentContext() );
        Reference<XDefaultNumberingProvider> xRet = text::DefaultNumberingProvider::create(xContext);
        _xFormatter.set(xRet, UNO_QUERY);
    }
    catch(const Exception&)
    {
        SAL_WARN("editeng", "service missing: \"com.sun.star.text.DefaultNumberingProvider\"");
    }
}
 
SvxNumberType::SvxNumberType(SvxNumType nType) :
    nNumType(nType),
    bShowSymbol(true)
{
    nRefCount++;
}
 
SvxNumberType::SvxNumberType(const SvxNumberType& rType) :
    nNumType(rType.nNumType),
    bShowSymbol(rType.bShowSymbol)
{
    nRefCount++;
}
 
SvxNumberType::~SvxNumberType()
{
    if(!--nRefCount)
        xFormatter = nullptr;
}
 
OUString SvxNumberType::GetNumStr( sal_Int32 nNo ) const
{
    LanguageTag aLang = comphelper::IsFuzzing() ?
        LanguageTag(u"en-US"_ustr) :
        Application::GetSettings().GetLanguageTag();
    return GetNumStr( nNo, aLang.getLocale() );
}
 
static bool isArabicNumberingType(SvxNumType t)
{
    return t == SVX_NUM_ARABIC || t == SVX_NUM_ARABIC_ZERO || t == SVX_NUM_ARABIC_ZERO3
           || t == SVX_NUM_ARABIC_ZERO4 || t == SVX_NUM_ARABIC_ZERO5;
}
 
OUString SvxNumberType::GetNumStr( sal_Int32 nNo, const css::lang::Locale& rLocale, bool bIsLegal ) const
{
    lcl_getFormatter(xFormatter);
    if(!xFormatter.is())
        return OUString();
 
    if(bShowSymbol)
    {
        switch(nNumType)
        {
            case NumberingType::CHAR_SPECIAL:
            case NumberingType::BITMAP:
            break;
            default:
                {
                    // '0' allowed for ARABIC numberings
                    if(NumberingType::ARABIC == nNumType && 0 == nNo )
                        return OUString('0');
                    else
                    {
                        SvxNumType nActType = !bIsLegal || isArabicNumberingType(nNumType) ? nNumType : SVX_NUM_ARABIC;
                        static constexpr OUString sNumberingType = u"NumberingType"_ustr;
                        static constexpr OUString sValue = u"Value"_ustr;
                        Sequence< PropertyValue > aProperties
                        {
                            comphelper::makePropertyValue(sNumberingType, static_cast<sal_uInt16>(nActType)),
                            comphelper::makePropertyValue(sValue, nNo)
                        };
 
                        try
                        {
                            return xFormatter->makeNumberingString( aProperties, rLocale );
                        }
                        catch(const Exception&)
                        {
                        }
                    }
                }
        }
    }
    return OUString();
}
 
void SvxNumberType::dumpAsXml( xmlTextWriterPtr pWriter ) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxNumberType"));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("NumType"), BAD_CAST(OString::number(nNumType).getStr()));
    (void)xmlTextWriterEndElement(pWriter);
}
 
SvxNumberFormat::SvxNumberFormat( SvxNumType eType )
    : SvxNumberType(eType),
      eNumAdjust(SvxAdjust::Left),
      nInclUpperLevels(1),
      nStart(1),
      cBullet(SVX_DEF_BULLET),
      nBulletRelSize(100),
      nBulletColor(COL_BLACK),
      mePositionAndSpaceMode( LABEL_WIDTH_AND_POSITION ),
      nFirstLineOffset(0),
      nAbsLSpace(0),
      nCharTextDistance(0),
      meLabelFollowedBy( LISTTAB ),
      mnListtabPos( 0 ),
      mnFirstLineIndent( 0 ),
      mnIndentAt( 0 ),
      eVertOrient(text::VertOrientation::NONE)
{
}
 
SvxNumberFormat::SvxNumberFormat(const SvxNumberFormat& rFormat) :
    SvxNumberType(rFormat),
    mePositionAndSpaceMode( rFormat.mePositionAndSpaceMode )
{
    *this = rFormat;
}
 
SvxNumberFormat::SvxNumberFormat( SvStream &rStream )
    : nStart(0)
    , nBulletRelSize(100)
    , nFirstLineOffset(0)
    , nAbsLSpace(0)
    , nCharTextDistance(0)
{
    sal_uInt16 nTmp16(0);
    sal_Int32  nTmp32(0);
    rStream.ReadUInt16( nTmp16 ); // Version number
 
    rStream.ReadUInt16( nTmp16 ); SetNumberingType( static_cast<SvxNumType>(nTmp16) );
    rStream.ReadUInt16( nTmp16 ); eNumAdjust = static_cast<SvxAdjust>(nTmp16);
    rStream.ReadUInt16( nTmp16 ); nInclUpperLevels = nTmp16;
    rStream.ReadUInt16( nStart );
    rStream.ReadUInt16( nTmp16 ); cBullet = static_cast<sal_Unicode>(nTmp16);
 
    sal_Int16 temp = 0;
    rStream.ReadInt16( temp );
    nFirstLineOffset = temp;
    temp = 0;
    rStream.ReadInt16( temp );
    nAbsLSpace = temp;
    rStream.SeekRel(2); //skip old now unused nLSpace;
 
    rStream.ReadInt16( nCharTextDistance );
 
    sPrefix = rStream.ReadUniOrByteString( rStream.GetStreamCharSet() );
    sSuffix = rStream.ReadUniOrByteString( rStream.GetStreamCharSet() );
    sCharStyleName = rStream.ReadUniOrByteString( rStream.GetStreamCharSet() );
 
    sal_uInt16 hasGraphicBrush = 0;
    rStream.ReadUInt16( hasGraphicBrush );
    if ( hasGraphicBrush )
    {
        pGraphicBrush.reset(new SvxBrushItem(SID_ATTR_BRUSH));
        legacy::SvxBrush::Create(*pGraphicBrush, rStream, BRUSH_GRAPHIC_VERSION);
    }
    else pGraphicBrush = nullptr;
    rStream.ReadUInt16( nTmp16 ); eVertOrient = nTmp16;
 
    sal_uInt16 hasBulletFont = 0;
    rStream.ReadUInt16( hasBulletFont );
    if ( hasBulletFont )
    {
        pBulletFont.emplace();
        ReadFont( rStream, *pBulletFont );
    }
    else pBulletFont.reset();
 
    tools::GenericTypeSerializer aSerializer(rStream);
    aSerializer.readSize(aGraphicSize);
    aSerializer.readColor(nBulletColor);
 
    rStream.ReadUInt16( nBulletRelSize );
    rStream.ReadUInt16( nTmp16 ); SetShowSymbol( nTmp16 != 0 );
 
    rStream.ReadUInt16( nTmp16 ); mePositionAndSpaceMode = static_cast<SvxNumPositionAndSpaceMode>(nTmp16);
    rStream.ReadUInt16( nTmp16 ); meLabelFollowedBy = static_cast<LabelFollowedBy>(nTmp16);
    rStream.ReadInt32( nTmp32 ); mnListtabPos = nTmp32;
    rStream.ReadInt32( nTmp32 ); mnFirstLineIndent = nTmp32;
    rStream.ReadInt32( nTmp32 ); mnIndentAt = nTmp32;
}
 
SvxNumberFormat::~SvxNumberFormat()
{
}
 
void SvxNumberFormat::Store(SvStream &rStream, FontToSubsFontConverter pConverter)
{
    if(pConverter && pBulletFont)
    {
        cBullet = ConvertFontToSubsFontChar(pConverter, cBullet);
        OUString sFontName = GetFontToSubsFontName(pConverter);
        pBulletFont->SetFamilyName(sFontName);
    }
 
    tools::GenericTypeSerializer aSerializer(rStream);
 
    rStream.WriteUInt16( NUMITEM_VERSION_04 );
 
    rStream.WriteUInt16( GetNumberingType() );
    rStream.WriteUInt16( static_cast<sal_uInt16>(eNumAdjust) );
    rStream.WriteUInt16( nInclUpperLevels );
    rStream.WriteUInt16( nStart );
    rStream.WriteUInt16( cBullet );
 
    rStream.WriteInt16(
        sal_Int16(std::clamp<sal_Int32>(nFirstLineOffset, SAL_MIN_INT16, SAL_MAX_INT16)) );
        //TODO: better way to handle out-of-bounds value?
    rStream.WriteInt16(
        sal_Int16(std::clamp<sal_Int32>(nAbsLSpace, SAL_MIN_INT16, SAL_MAX_INT16)) );
        //TODO: better way to handle out-of-bounds value?
    rStream.WriteInt16( 0 ); // write a dummy for old now unused nLSpace
 
    rStream.WriteInt16( nCharTextDistance );
    rtl_TextEncoding eEnc = osl_getThreadTextEncoding();
    rStream.WriteUniOrByteString(sPrefix, eEnc);
    rStream.WriteUniOrByteString(sSuffix, eEnc);
    rStream.WriteUniOrByteString(sCharStyleName, eEnc);
    if(pGraphicBrush)
    {
        rStream.WriteUInt16( 1 );
 
        // in SD or SI force bullet itself to be stored,
        // for that purpose throw away link when link and graphic
        // are present, so Brush save is forced
        if(!pGraphicBrush->GetGraphicLink().isEmpty() && pGraphicBrush->GetGraphic())
        {
            pGraphicBrush->SetGraphicLink(u""_ustr);
        }
 
        legacy::SvxBrush::Store(*pGraphicBrush, rStream, BRUSH_GRAPHIC_VERSION);
    }
    else
        rStream.WriteUInt16( 0 );
 
    rStream.WriteUInt16( eVertOrient );
    if(pBulletFont)
    {
        rStream.WriteUInt16( 1 );
        WriteFont( rStream, *pBulletFont );
    }
    else
        rStream.WriteUInt16( 0 );
 
    aSerializer.writeSize(aGraphicSize);
 
    Color nTempColor = nBulletColor;
    if(COL_AUTO == nBulletColor)
        nTempColor = COL_BLACK;
 
    aSerializer.writeColor(nTempColor);
    rStream.WriteUInt16( nBulletRelSize );
    rStream.WriteUInt16( sal_uInt16(IsShowSymbol()) );
 
    rStream.WriteUInt16( mePositionAndSpaceMode );
    rStream.WriteUInt16( meLabelFollowedBy );
    rStream.WriteInt32( mnListtabPos );
    rStream.WriteInt32( mnFirstLineIndent );
    rStream.WriteInt32( mnIndentAt );
}
 
SvxNumberFormat& SvxNumberFormat::operator=( const SvxNumberFormat& rFormat )
{
    if (& rFormat == this) { return *this; }
 
    SvxNumberType::SetNumberingType(rFormat.GetNumberingType());
    eNumAdjust          = rFormat.eNumAdjust ;
    nInclUpperLevels    = rFormat.nInclUpperLevels ;
    nStart              = rFormat.nStart ;
    cBullet             = rFormat.cBullet ;
    mePositionAndSpaceMode = rFormat.mePositionAndSpaceMode;
    nFirstLineOffset    = rFormat.nFirstLineOffset;
    nAbsLSpace          = rFormat.nAbsLSpace ;
    nCharTextDistance   = rFormat.nCharTextDistance ;
    meLabelFollowedBy = rFormat.meLabelFollowedBy;
    mnListtabPos = rFormat.mnListtabPos;
    mnFirstLineIndent = rFormat.mnFirstLineIndent;
    mnIndentAt = rFormat.mnIndentAt;
    eVertOrient         = rFormat.eVertOrient;
    sPrefix             = rFormat.sPrefix;
    sSuffix             = rFormat.sSuffix;
    sListFormat         = rFormat.sListFormat;
    aGraphicSize        = rFormat.aGraphicSize  ;
    nBulletColor        = rFormat.nBulletColor   ;
    nBulletRelSize      = rFormat.nBulletRelSize;
    SetShowSymbol(rFormat.IsShowSymbol());
    sCharStyleName      = rFormat.sCharStyleName;
    pGraphicBrush.reset();
    if(rFormat.pGraphicBrush)
    {
        pGraphicBrush.reset( new SvxBrushItem(*rFormat.pGraphicBrush) );
    }
    pBulletFont.reset();
    if(rFormat.pBulletFont)
        pBulletFont = *rFormat.pBulletFont;
    mbIsLegal = rFormat.mbIsLegal;
    return *this;
}
 
bool  SvxNumberFormat::operator==( const SvxNumberFormat& rFormat) const
{
    if( GetNumberingType()  != rFormat.GetNumberingType() ||
        eNumAdjust          != rFormat.eNumAdjust ||
        nInclUpperLevels    != rFormat.nInclUpperLevels ||
        nStart              != rFormat.nStart ||
        cBullet             != rFormat.cBullet ||
        mePositionAndSpaceMode != rFormat.mePositionAndSpaceMode ||
        nFirstLineOffset    != rFormat.nFirstLineOffset ||
        nAbsLSpace          != rFormat.nAbsLSpace ||
        nCharTextDistance   != rFormat.nCharTextDistance ||
        meLabelFollowedBy != rFormat.meLabelFollowedBy ||
        mnListtabPos != rFormat.mnListtabPos ||
        mnFirstLineIndent != rFormat.mnFirstLineIndent ||
        mnIndentAt != rFormat.mnIndentAt ||
        eVertOrient         != rFormat.eVertOrient ||
        sPrefix             != rFormat.sPrefix     ||
        sSuffix             != rFormat.sSuffix     ||
        sListFormat         != rFormat.sListFormat ||
        aGraphicSize        != rFormat.aGraphicSize  ||
        nBulletColor        != rFormat.nBulletColor   ||
        nBulletRelSize      != rFormat.nBulletRelSize ||
        IsShowSymbol()      != rFormat.IsShowSymbol() ||
        sCharStyleName      != rFormat.sCharStyleName ||
        mbIsLegal           != rFormat.mbIsLegal
        )
        return false;
    if (
        (pGraphicBrush && !rFormat.pGraphicBrush) ||
        (!pGraphicBrush && rFormat.pGraphicBrush) ||
        (pGraphicBrush && *pGraphicBrush != *rFormat.pGraphicBrush)
       )
    {
        return false;
    }
    if (
        (pBulletFont && !rFormat.pBulletFont) ||
        (!pBulletFont && rFormat.pBulletFont) ||
        (pBulletFont && *pBulletFont != *rFormat.pBulletFont)
       )
    {
        return false;
    }
    return true;
}
 
void SvxNumberFormat::SetGraphicBrush( const SvxBrushItem* pBrushItem,
                    const Size* pSize, const sal_Int16* pOrient)
{
    if (!pBrushItem)
        pGraphicBrush.reset();
    else if ( !pGraphicBrush || (*pBrushItem != *pGraphicBrush) )
        pGraphicBrush.reset(pBrushItem->Clone());
 
    if(pOrient)
        eVertOrient = *pOrient;
    else
        eVertOrient = text::VertOrientation::NONE;
    if(pSize)
        aGraphicSize = *pSize;
    else
    {
        aGraphicSize.setWidth(0);
        aGraphicSize.setHeight(0);
    }
}
 
void SvxNumberFormat::SetGraphic( const OUString& rName )
{
    if( pGraphicBrush && pGraphicBrush->GetGraphicLink() == rName )
        return ;
 
    pGraphicBrush.reset( new SvxBrushItem( rName, u""_ustr, GPOS_AREA, 0 ) );
    if( eVertOrient == text::VertOrientation::NONE )
        eVertOrient = text::VertOrientation::TOP;
 
    aGraphicSize.setWidth(0);
    aGraphicSize.setHeight(0);
}
 
sal_Int16    SvxNumberFormat::GetVertOrient() const
{
    return eVertOrient;
}
 
void SvxNumberFormat::SetBulletFont(const vcl::Font* pFont)
{
    if (pFont)
        pBulletFont = *pFont;
    else
        pBulletFont.reset();
}
 
void SvxNumberFormat::SetPositionAndSpaceMode( SvxNumPositionAndSpaceMode ePositionAndSpaceMode )
{
    mePositionAndSpaceMode = ePositionAndSpaceMode;
}
 
sal_Int32 SvxNumberFormat::GetAbsLSpace() const
{
    return mePositionAndSpaceMode == LABEL_WIDTH_AND_POSITION
           ? nAbsLSpace
           : static_cast<sal_Int32>( GetFirstLineIndent() + GetIndentAt() );
}
sal_Int32 SvxNumberFormat::GetFirstLineOffset() const
{
    return mePositionAndSpaceMode == LABEL_WIDTH_AND_POSITION
           ? nFirstLineOffset
           : static_cast<sal_Int32>( GetFirstLineIndent() );
}
short SvxNumberFormat::GetCharTextDistance() const
{
    return mePositionAndSpaceMode == LABEL_WIDTH_AND_POSITION ? nCharTextDistance : 0;
}
 
void SvxNumberFormat::SetLabelFollowedBy( const LabelFollowedBy eLabelFollowedBy )
{
    meLabelFollowedBy = eLabelFollowedBy;
}
 
OUString SvxNumberFormat::GetLabelFollowedByAsString() const
{
    switch (meLabelFollowedBy)
    {
        case LISTTAB:
            return u"\t"_ustr;
        case SPACE:
            return u" "_ustr;
        case NEWLINE:
            return u"\n"_ustr;
        case NOTHING:
            // intentionally left blank.
            return OUString();
        default:
            SAL_WARN("editeng", "Unknown SvxNumberFormat::GetLabelFollowedBy() return value");
            assert(false);
    }
    return OUString();
}
 
void SvxNumberFormat::SetListtabPos( const tools::Long nListtabPos )
{
    mnListtabPos = nListtabPos;
}
void SvxNumberFormat::SetFirstLineIndent( const tools::Long nFirstLineIndent )
{
    mnFirstLineIndent = nFirstLineIndent;
}
void SvxNumberFormat::SetIndentAt( const tools::Long nIndentAt )
{
    mnIndentAt = nIndentAt;
}
 
Size SvxNumberFormat::GetGraphicSizeMM100(const Graphic* pGraphic)
{
    const MapMode aMapMM100( MapUnit::Map100thMM );
    const Size aSize = pGraphic->GetPrefSize();
    Size aRetSize;
    if ( pGraphic->GetPrefMapMode().GetMapUnit() == MapUnit::MapPixel )
    {
        OutputDevice* pOutDev = Application::GetDefaultDevice();
        MapMode aOldMap( pOutDev->GetMapMode() );
        pOutDev->SetMapMode( aMapMM100 );
        aRetSize = pOutDev->PixelToLogic( aSize );
        pOutDev->SetMapMode( aOldMap );
    }
    else
        aRetSize = OutputDevice::LogicToLogic( aSize, pGraphic->GetPrefMapMode(), aMapMM100 );
    return aRetSize;
}
 
OUString SvxNumberFormat::CreateRomanString( sal_Int32 nNo, bool bUpper )
{
    OUStringBuffer sRet;
 
    static constexpr char romans[][13] = {"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"};
    static constexpr sal_Int32 values[] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
 
    for (size_t i = 0; i < std::size(romans); ++i)
    {
        while(nNo - values[i] >= 0)
        {
            sRet.appendAscii(romans[i]);
            nNo -= values[i];
        }
    }
 
    return bUpper ? sRet.makeStringAndClear()
                  : sRet.makeStringAndClear().toAsciiLowerCase();
}
 
void SvxNumberFormat::SetPrefix(const OUString& rSet)
{
    // ListFormat manages the prefix. If badly changed via this function, sListFormat is invalidated
    if (sListFormat)
        sListFormat.reset();
 
    sPrefix = rSet;
}
 
void SvxNumberFormat::SetSuffix(const OUString& rSet)
{
    // ListFormat manages the suffix. If badly changed via this function, sListFormat is invalidated
    if (sListFormat)
        sListFormat.reset();
 
    sSuffix = rSet;
}
 
void SvxNumberFormat::SetListFormat(const OUString& rPrefix, const OUString& rSuffix, int nLevel)
{
    sPrefix = rPrefix;
    sSuffix = rSuffix;
 
    // Generate list format
    sListFormat = std::make_optional(sPrefix);
 
    for (int i = 1; i <= nInclUpperLevels; i++)
    {
        int nLevelId = nLevel - nInclUpperLevels + i;
        if (nLevelId < 0)
            // There can be cases with current level 1, but request to show 10 upper levels. Trim it
            continue;
 
        *sListFormat += "%";
        *sListFormat += OUString::number(nLevelId + 1);
        *sListFormat += "%";
        if (i != nInclUpperLevels)
            *sListFormat += "."; // Default separator for older ODT
    }
 
    *sListFormat += sSuffix;
}
 
void SvxNumberFormat::SetListFormat(std::optional<OUString> oSet)
{
    sPrefix.clear();
    sSuffix.clear();
 
    sListFormat = oSet;
 
    if (!oSet.has_value())
    {
        return;
    }
 
    // For backward compatibility and UI we should create something looking like
    // a prefix, suffix and included levels also. This is not possible in general case
    // since level format string is much more flexible. But for most cases is okay
 
    // If properly formatted, sListFormat should look something like "%1%…%10%"
    // with an optional prefix or suffix (which could theoretically include a percent symbol)
    const sal_Int32 nLen = sListFormat->getLength();
    sal_Int32 nFirstReplacement = sListFormat->indexOf('%');
    while (nFirstReplacement > -1 && nFirstReplacement < nLen - 1
           && ((*sListFormat)[nFirstReplacement + 1] < '1'
               || (*sListFormat)[nFirstReplacement + 1] > '9'))
    {
        nFirstReplacement = sListFormat->indexOf('%', nFirstReplacement + 1);
    }
 
    sal_Int32 nLastReplacement = nFirstReplacement == -1 ? -1 : sListFormat->lastIndexOf('%');
    while (nLastReplacement > 0
           && ((*sListFormat)[nLastReplacement - 1] < '0'
               || (*sListFormat)[nLastReplacement - 1] > '9'))
    {
        nLastReplacement = sListFormat->lastIndexOf('%', nLastReplacement);
    }
    if (nLastReplacement < nFirstReplacement)
        nLastReplacement = nFirstReplacement;
    else
        ++nLastReplacement;
 
    if (nFirstReplacement > 0)
        // Everything before first '%' will be prefix
        sPrefix = sListFormat->copy(0, nFirstReplacement);
    if (nLastReplacement >= 0 && nLastReplacement < nLen)
        // Everything beyond last '%' is a suffix
        sSuffix = sListFormat->copy(nLastReplacement);
 
    sal_uInt8 nPercents = 0;
    for (sal_Int32 i = nFirstReplacement > 0 ? nFirstReplacement : 0; i < nLastReplacement; i++)
    {
        if ((*sListFormat)[i] == '%')
            nPercents++;
    }
    nInclUpperLevels = nPercents/2;
    if (nInclUpperLevels < 1)
    {
        // There should be always at least one level. This will be not required
        // in future (when we get rid of prefix/suffix), but nowadays there
        // are too many conversions "list format" <-> "prefix/suffix/inclUpperLevel"
        nInclUpperLevels = 1;
    }
}
 
OUString SvxNumberFormat::GetListFormat(bool bIncludePrefixSuffix /*= true*/) const
{
    assert(sListFormat.has_value());
 
    if (bIncludePrefixSuffix)
        return *sListFormat;
 
    // Strip prefix & suffix from string
    return sListFormat->copy(sPrefix.getLength(), sListFormat->getLength() - sPrefix.getLength() - sSuffix.getLength());
}
 
OUString SvxNumberFormat::GetCharFormatName()const
{
    return sCharStyleName;
}
 
sal_Int32 SvxNumRule::nRefCount = 0;
static SvxNumberFormat* pStdNumFmt = nullptr;
static SvxNumberFormat* pStdOutlineNumFmt = nullptr;
SvxNumRule::SvxNumRule( SvxNumRuleFlags nFeatures,
                        sal_uInt16 nLevels,
                        bool bCont,
                        SvxNumRuleType eType,
                        SvxNumberFormat::SvxNumPositionAndSpaceMode
                                    eDefaultNumberFormatPositionAndSpaceMode )
    : nLevelCount(nLevels),
      nFeatureFlags(nFeatures),
      eNumberingType(eType),
      bContinuousNumbering(bCont)
{
    ++nRefCount;
    for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++)
    {
        if(i < nLevels)
        {
            aFmts[i].reset( new SvxNumberFormat(SVX_NUM_CHARS_UPPER_LETTER) );
            // It is a distinction between writer and draw
            if(nFeatures & SvxNumRuleFlags::CONTINUOUS)
            {
                if ( eDefaultNumberFormatPositionAndSpaceMode ==
                                    SvxNumberFormat::LABEL_WIDTH_AND_POSITION )
                {
                    aFmts[i]->SetAbsLSpace(o3tl::toTwips(DEF_WRITER_LSPACE * (i+1), o3tl::Length::mm100));
                    aFmts[i]->SetFirstLineOffset(o3tl::toTwips(-DEF_WRITER_LSPACE, o3tl::Length::mm100));
                }
                else if ( eDefaultNumberFormatPositionAndSpaceMode ==
                                            SvxNumberFormat::LABEL_ALIGNMENT )
                {
                    // first line indent of general numbering in inch: -0,25 inch
                    constexpr tools::Long cFirstLineIndent = o3tl::toTwips(-0.25, o3tl::Length::in);
                    // indent values of general numbering in inch:
                    //  0,5         0,75        1,0         1,25        1,5
                    //  1,75        2,0         2,25        2,5         2,75
                    constexpr tools::Long cIndentAt = o3tl::toTwips(0.25, o3tl::Length::in);
                    aFmts[i]->SetPositionAndSpaceMode( SvxNumberFormat::LABEL_ALIGNMENT );
                    aFmts[i]->SetLabelFollowedBy( SvxNumberFormat::LISTTAB );
                    aFmts[i]->SetListtabPos( cIndentAt * (i+2) );
                    aFmts[i]->SetFirstLineIndent( cFirstLineIndent );
                    aFmts[i]->SetIndentAt( cIndentAt * (i+2) );
                }
            }
            else
            {
                aFmts[i]->SetAbsLSpace( DEF_DRAW_LSPACE * i );
            }
        }
        else
            aFmts[i] = nullptr;
        aFmtsSet[i] = false;
    }
}
 
SvxNumRule::SvxNumRule(const SvxNumRule& rCopy)
{
    ++nRefCount;
    nLevelCount          = rCopy.nLevelCount         ;
    nFeatureFlags        = rCopy.nFeatureFlags       ;
    bContinuousNumbering = rCopy.bContinuousNumbering;
    eNumberingType       = rCopy.eNumberingType;
    for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++)
    {
        if(rCopy.aFmts[i])
            aFmts[i].reset( new SvxNumberFormat(*rCopy.aFmts[i]) );
        else
            aFmts[i].reset();
        aFmtsSet[i] = rCopy.aFmtsSet[i];
    }
}
 
SvxNumRule::SvxNumRule(SvxNumRule&& rCopy) noexcept
{
    ++nRefCount;
    nLevelCount          = rCopy.nLevelCount         ;
    nFeatureFlags        = rCopy.nFeatureFlags       ;
    bContinuousNumbering = rCopy.bContinuousNumbering;
    eNumberingType       = rCopy.eNumberingType;
    for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++)
    {
        if(rCopy.aFmts[i])
            aFmts[i] = std::move(rCopy.aFmts[i]);
        aFmtsSet[i] = rCopy.aFmtsSet[i];
    }
}
 
SvxNumRule::SvxNumRule( SvStream &rStream )
    : nLevelCount(0)
{
    sal_uInt16 nTmp16(0);
    rStream.ReadUInt16( nTmp16 ); // NUM_ITEM_VERSION
    rStream.ReadUInt16( nLevelCount );
 
    if (nLevelCount > SVX_MAX_NUM)
    {
        SAL_WARN("editeng", "nLevelCount: " << nLevelCount << " greater than max of: " << SVX_MAX_NUM);
        nLevelCount = SVX_MAX_NUM;
    }
 
    // first nFeatureFlags of old Versions
    rStream.ReadUInt16( nTmp16 ); nFeatureFlags = static_cast<SvxNumRuleFlags>(nTmp16);
    rStream.ReadUInt16( nTmp16 ); bContinuousNumbering = nTmp16;
    rStream.ReadUInt16( nTmp16 ); eNumberingType = static_cast<SvxNumRuleType>(nTmp16);
 
    for (sal_uInt16 i = 0; i < SVX_MAX_NUM; i++)
    {
        rStream.ReadUInt16( nTmp16 );
        bool hasNumberingFormat = nTmp16 & 1;
        aFmtsSet[i] = nTmp16 & 2; // fdo#68648 reset flag
        if ( hasNumberingFormat ){
            aFmts[i].reset( new SvxNumberFormat( rStream ) );
        }
        else
        {
            aFmts[i].reset();
            aFmtsSet[i] = false; // actually only false is valid
        }
    }
    //second nFeatureFlags for new versions
    rStream.ReadUInt16( nTmp16 ); nFeatureFlags = static_cast<SvxNumRuleFlags>(nTmp16);
}
 
void SvxNumRule::Store( SvStream &rStream )
{
    rStream.WriteUInt16( NUMITEM_VERSION_03 );
    rStream.WriteUInt16( nLevelCount );
    //first save of nFeatureFlags for old versions
    rStream.WriteUInt16( static_cast<sal_uInt16>(nFeatureFlags) );
    rStream.WriteUInt16( sal_uInt16(bContinuousNumbering) );
    rStream.WriteUInt16( static_cast<sal_uInt16>(eNumberingType) );
 
    FontToSubsFontConverter pConverter = nullptr;
    bool bConvertBulletFont = ( rStream.GetVersion() <= SOFFICE_FILEFORMAT_50 ) && ( rStream.GetVersion() );
    for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++)
    {
        sal_uInt16 nSetFlag(aFmtsSet[i] ? 2 : 0); // fdo#68648 store that too
        if(aFmts[i])
        {
            rStream.WriteUInt16( 1 | nSetFlag );
            if(bConvertBulletFont && aFmts[i]->GetBulletFont())
            {
                if(!pConverter)
                    pConverter =
                        CreateFontToSubsFontConverter(aFmts[i]->GetBulletFont()->GetFamilyName(),
                                    FontToSubsFontFlags::EXPORT);
            }
            aFmts[i]->Store(rStream, pConverter);
        }
        else
            rStream.WriteUInt16( 0 | nSetFlag );
    }
    //second save of nFeatureFlags for new versions
    rStream.WriteUInt16( static_cast<sal_uInt16>(nFeatureFlags) );
}
 
void SvxNumRule::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxNumRule"));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("levelCount"), BAD_CAST(OString::number(nLevelCount).getStr()));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("continuousNumbering"), BAD_CAST(OString::boolean(bContinuousNumbering).getStr()));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("numberingType"), BAD_CAST(OString::number(static_cast<int>(eNumberingType)).getStr()));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("featureFlags"), BAD_CAST(OString::number(static_cast<int>(nFeatureFlags)).getStr()));
    for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++)
    {
        if(aFmts[i])
        {
            (void)xmlTextWriterStartElement(pWriter, BAD_CAST("aFmts"));
            (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("i"), BAD_CAST(OString::number(i).getStr()));
            (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", aFmts[i].get());
            (void)xmlTextWriterEndElement(pWriter);
        }
    }
    (void)xmlTextWriterEndElement(pWriter);
}
 
 
SvxNumRule::~SvxNumRule()
{
    if(!--nRefCount)
    {
        delete pStdNumFmt;
        pStdNumFmt = nullptr;
        delete pStdOutlineNumFmt;
        pStdOutlineNumFmt = nullptr;
    }
}
 
SvxNumRule& SvxNumRule::operator=( const SvxNumRule& rCopy )
{
    if (this != &rCopy)
    {
        nLevelCount          = rCopy.nLevelCount;
        nFeatureFlags        = rCopy.nFeatureFlags;
        bContinuousNumbering = rCopy.bContinuousNumbering;
        eNumberingType       = rCopy.eNumberingType;
        for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++)
        {
            if(rCopy.aFmts[i])
                aFmts[i].reset( new SvxNumberFormat(*rCopy.aFmts[i]) );
            else
                aFmts[i].reset();
            aFmtsSet[i] = rCopy.aFmtsSet[i];
        }
    }
    return *this;
}
 
SvxNumRule& SvxNumRule::operator=( SvxNumRule&& rCopy ) noexcept
{
    if (this != &rCopy)
    {
        nLevelCount          = rCopy.nLevelCount;
        nFeatureFlags        = rCopy.nFeatureFlags;
        bContinuousNumbering = rCopy.bContinuousNumbering;
        eNumberingType       = rCopy.eNumberingType;
        for(sal_uInt16 i = 0; i < SVX_MAX_NUM; i++)
        {
            if(rCopy.aFmts[i])
                aFmts[i] = std::move(rCopy.aFmts[i]);
            aFmtsSet[i] = rCopy.aFmtsSet[i];
        }
    }
    return *this;
}
 
bool SvxNumRule::operator==( const SvxNumRule& rCopy) const
{
    if(nLevelCount != rCopy.nLevelCount ||
        nFeatureFlags != rCopy.nFeatureFlags ||
            bContinuousNumbering != rCopy.bContinuousNumbering ||
                eNumberingType != rCopy.eNumberingType)
            return false;
    for(sal_uInt16 i = 0; i < nLevelCount; i++)
    {
        if (
            (aFmtsSet[i] != rCopy.aFmtsSet[i]) ||
            (!aFmts[i] && rCopy.aFmts[i]) ||
            (aFmts[i] && !rCopy.aFmts[i]) ||
            (aFmts[i] && *aFmts[i] !=  *rCopy.aFmts[i])
           )
        {
            return false;
        }
    }
    return true;
}
 
const SvxNumberFormat*  SvxNumRule::Get(sal_uInt16 nLevel)const
{
    DBG_ASSERT(nLevel < SVX_MAX_NUM, "Wrong Level" );
    if( nLevel < SVX_MAX_NUM )
        return aFmtsSet[nLevel] ? aFmts[nLevel].get() : nullptr;
    else
        return nullptr;
}
 
const SvxNumberFormat&  SvxNumRule::GetLevel(sal_uInt16 nLevel)const
{
    if(!pStdNumFmt)
    {
        pStdNumFmt = new SvxNumberFormat(SVX_NUM_ARABIC);
        pStdOutlineNumFmt = new SvxNumberFormat(SVX_NUM_NUMBER_NONE);
    }
 
    DBG_ASSERT(nLevel < SVX_MAX_NUM, "Wrong Level" );
 
    return ( ( nLevel < SVX_MAX_NUM ) && aFmts[nLevel] ) ?
            *aFmts[nLevel] :  eNumberingType == SvxNumRuleType::NUMBERING ?
                                                        *pStdNumFmt : *pStdOutlineNumFmt;
}
 
void SvxNumRule::SetLevel( sal_uInt16 i, const SvxNumberFormat& rNumFmt, bool bIsValid )
{
    DBG_ASSERT(i < SVX_MAX_NUM, "Wrong Level" );
 
    if( i >= SVX_MAX_NUM )
        return;
 
    bool bReplace = !aFmtsSet[i];
    if (!bReplace)
    {
        const SvxNumberFormat *pFmt = Get(i);
        bReplace = pFmt == nullptr || rNumFmt != *pFmt;
    }
 
    if (bReplace)
    {
        aFmts[i].reset( new SvxNumberFormat(rNumFmt) );
        aFmtsSet[i] = bIsValid;
    }
}
 
void SvxNumRule::SetLevel(sal_uInt16 nLevel, const SvxNumberFormat* pFmt)
{
    DBG_ASSERT(nLevel < SVX_MAX_NUM, "Wrong Level" );
 
    if( nLevel < SVX_MAX_NUM )
    {
        aFmtsSet[nLevel] = nullptr != pFmt;
        if(pFmt)
            SetLevel(nLevel, *pFmt);
        else
        {
            aFmts[nLevel].reset();
        }
    }
}
 
OUString SvxNumRule::MakeNumString( const SvxNodeNum& rNum ) const
{
    OUStringBuffer aStr;
    if( SVX_NO_NUM > rNum.GetLevel() && !( SVX_NO_NUMLEVEL & rNum.GetLevel() ) )
    {
        const SvxNumberFormat& rMyNFmt = GetLevel( rNum.GetLevel() );
        aStr.append(rMyNFmt.GetPrefix());
        if( SVX_NUM_NUMBER_NONE != rMyNFmt.GetNumberingType() )
        {
            sal_uInt8 i = rNum.GetLevel();
 
            if( !IsContinuousNumbering() &&
                1 < rMyNFmt.GetIncludeUpperLevels() )       // only on own level?
            {
                sal_uInt8 n = rMyNFmt.GetIncludeUpperLevels();
                if( 1 < n )
                {
                    if( i+1 >= n )
                        i -= n - 1;
                    else
                        i = 0;
                }
            }
 
            for( ; i <= rNum.GetLevel(); ++i )
            {
                const SvxNumberFormat& rNFmt = GetLevel( i );
                if( SVX_NUM_NUMBER_NONE == rNFmt.GetNumberingType() )
                {
                    continue;
                }
 
                bool bDot = true;
                if( rNum.GetLevelVal()[ i ] )
                {
                    if(SVX_NUM_BITMAP != rNFmt.GetNumberingType())
                    {
                        const LanguageTag& rLang = Application::GetSettings().GetLanguageTag();
                        aStr.append(rNFmt.GetNumStr( rNum.GetLevelVal()[ i ], rLang.getLocale(), rMyNFmt.GetIsLegal() ));
                    }
                    else
                        bDot = false;
                }
                else
                    aStr.append("0");       // all 0-levels are a 0
                if( i != rNum.GetLevel() && bDot)
                    aStr.append(".");
            }
        }
 
        aStr.append(rMyNFmt.GetSuffix());
    }
    return aStr.makeStringAndClear();
}
 
// changes linked to embedded bitmaps
void SvxNumRule::UnLinkGraphics()
{
    for(sal_uInt16 i = 0; i < GetLevelCount(); i++)
    {
        SvxNumberFormat aFmt(GetLevel(i));
        const SvxBrushItem* pBrush = aFmt.GetBrush();
        if(SVX_NUM_BITMAP == aFmt.GetNumberingType())
        {
            if(pBrush && !pBrush->GetGraphicLink().isEmpty())
            {
                const Graphic* pGraphic = pBrush->GetGraphic();
                if (pGraphic)
                {
                    SvxBrushItem aTempItem(*pBrush);
                    aTempItem.SetGraphicLink(u""_ustr);
                    aTempItem.SetGraphic(*pGraphic);
                    sal_Int16    eOrient = aFmt.GetVertOrient();
                    aFmt.SetGraphicBrush( &aTempItem, &aFmt.GetGraphicSize(), &eOrient );
                }
            }
        }
        else if((SVX_NUM_BITMAP|LINK_TOKEN) == static_cast<int>(aFmt.GetNumberingType()))
            aFmt.SetNumberingType(SVX_NUM_BITMAP);
        SetLevel(i, aFmt);
    }
}
 
SvxNumBulletItem::SvxNumBulletItem(SvxNumRule const & rRule) :
    SfxPoolItem(SID_ATTR_NUMBERING_RULE, SfxItemType::SvxNumBulletItemType),
    maNumRule(rRule)
{
}
 
SvxNumBulletItem::SvxNumBulletItem(SvxNumRule && rRule) :
    SfxPoolItem(SID_ATTR_NUMBERING_RULE, SfxItemType::SvxNumBulletItemType),
    maNumRule(std::move(rRule))
{
}
 
SvxNumBulletItem::SvxNumBulletItem(SvxNumRule const & rRule, sal_uInt16 _nWhich ) :
    SfxPoolItem(_nWhich, SfxItemType::SvxNumBulletItemType),
    maNumRule(rRule)
{
}
 
SvxNumBulletItem::SvxNumBulletItem(SvxNumRule && rRule, sal_uInt16 _nWhich ) :
    SfxPoolItem(_nWhich, SfxItemType::SvxNumBulletItemType),
    maNumRule(std::move(rRule))
{
}
 
SvxNumBulletItem::SvxNumBulletItem(const SvxNumBulletItem& rCopy) :
    SfxPoolItem(rCopy),
    maNumRule(rCopy.maNumRule)
{
}
 
SvxNumBulletItem::~SvxNumBulletItem()
{
}
 
bool SvxNumBulletItem::operator==( const SfxPoolItem& rCopy) const
{
    return SfxPoolItem::operator==(rCopy) &&
        maNumRule == static_cast<const SvxNumBulletItem&>(rCopy).maNumRule;
}
 
SvxNumBulletItem* SvxNumBulletItem::Clone( SfxItemPool * ) const
{
    return new SvxNumBulletItem(*this);
}
 
bool SvxNumBulletItem::QueryValue( css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ ) const
{
    rVal <<= SvxCreateNumRule( maNumRule );
    return true;
}
 
bool SvxNumBulletItem::PutValue( const css::uno::Any& rVal, sal_uInt8 /*nMemberId*/ )
{
    uno::Reference< container::XIndexReplace > xRule;
    if( rVal >>= xRule )
    {
        try
        {
            SvxNumRule aNewRule( SvxGetNumRule( xRule ) );
            if( aNewRule.GetLevelCount() != maNumRule.GetLevelCount() ||
                aNewRule.GetNumRuleType() != maNumRule.GetNumRuleType() )
            {
                aNewRule = SvxConvertNumRule( aNewRule, maNumRule.GetLevelCount(), maNumRule.GetNumRuleType() );
            }
            maNumRule = std::move( aNewRule );
            return true;
        }
        catch(const lang::IllegalArgumentException&)
        {
        }
    }
    return false;
}
 
void SvxNumBulletItem::dumpAsXml(xmlTextWriterPtr pWriter) const
{
    (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SvxNumBulletItem"));
    (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("whichId"), BAD_CAST(OString::number(Which()).getStr()));
    maNumRule.dumpAsXml(pWriter);
    (void)xmlTextWriterEndElement(pWriter);
}
 
SvxNumRule SvxConvertNumRule( const SvxNumRule& rRule, sal_uInt16 nLevels, SvxNumRuleType eType )
{
    const sal_uInt16 nSrcLevels = rRule.GetLevelCount();
    SvxNumRule aNewRule(rRule.GetFeatureFlags(), nLevels, rRule.IsContinuousNumbering(), eType );
 
    for( sal_uInt16 nLevel = 0; (nLevel < nLevels) && (nLevel < nSrcLevels); nLevel++ )
        aNewRule.SetLevel( nLevel, rRule.GetLevel( nLevel ) );
 
    return aNewRule;
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'appendAscii' is required to be utilized.