/* -*- 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 "accportions.hxx"
#include <osl/diagnose.h>
#include <rtl/ustring.hxx>
#include <com/sun/star/i18n/Boundary.hpp>
#include <com/sun/star/i18n/XBreakIterator.hpp>
#include <txttypes.hxx>

// for portion replacement in Special()
#include <viewopt.hxx>

// for GetWordBoundary(...), GetSentenceBoundary(...):
#include <breakit.hxx>
#include <txtfrm.hxx>

// for FillSpecialPos(...)
#include <crstate.hxx>

using namespace ::com::sun::star;

using i18n::Boundary;

// 'portion type' for terminating portions
#define POR_TERMINATE PortionType::NONE

// portion attributes
#define PORATTR_SPECIAL     1
#define PORATTR_READONLY    2
#define PORATTR_GRAY        4
#define PORATTR_TERM        128

/// returns the index of the first position whose value is smaller
/// or equal, and whose following value is equal or larger
template<typename T>
static size_t FindBreak(const std::vector<T>& rPositions, T nValue);

/// like FindBreak, but finds the last equal or larger position
template<typename T>
static size_t FindLastBreak(const std::vector<T>& rPositions, T nValue);


SwAccessiblePortionData::SwAccessiblePortionData(
    const SwTextFrame& rTextFrame,
    const SwViewOption* pViewOpt ) :
    m_rTextFrame(rTextFrame),
    m_nViewPosition( 0 ),
    m_pViewOptions( pViewOpt ),
    m_nBeforePortions( 0 ),
    m_bFinished( false )
{
    // reserve some space to reduce memory allocations
    m_aLineBreaks.reserve( 5 );
    m_ViewPositions.reserve( 10 );
    m_aAccessiblePositions.reserve( 10 );

    // always include 'first' line-break position
    m_aLineBreaks.push_back( 0 );
}

SwAccessiblePortionData::~SwAccessiblePortionData()
{
}

void SwAccessiblePortionData::Text(TextFrameIndex const nLength,
        PortionType nType)
{
    OSL_ENSURE((m_nViewPosition + nLength) <= TextFrameIndex(m_rTextFrame.GetText().getLength()),
                "portion exceeds model string!" );

    OSL_ENSURE( !m_bFinished, "We are already done!" );

    // ignore zero-length portions
    if (nLength == TextFrameIndex(0))
        return;

    // store 'old' positions
    m_ViewPositions.push_back( m_nViewPosition );
    m_aAccessiblePositions.push_back( m_aBuffer.getLength() );

    // store portion attributes
    sal_uInt8 nAttr = IsGrayPortionType(nType) ? PORATTR_GRAY : 0;
    m_aPortionAttrs.push_back( nAttr );

    // update buffer + nViewPosition
    m_aBuffer.append(m_rTextFrame.GetText().subView(sal_Int32(m_nViewPosition), sal_Int32(nLength)));
    m_nViewPosition += nLength;
}

void SwAccessiblePortionData::Special(
    TextFrameIndex const nLength, const OUString& rText, PortionType nType)
{
    OSL_ENSURE(m_nViewPosition >= TextFrameIndex(0), "illegal position");
    OSL_ENSURE((m_nViewPosition + nLength) <= TextFrameIndex(m_rTextFrame.GetText().getLength()),
                "portion exceeds model string!" );

    OSL_ENSURE( !m_bFinished, "We are already done!" );

    // construct string with representation; either directly from
    // rText, or use resources for special case portions
    OUString sDisplay;
    switch( nType )
    {
        case PortionType::PostIts:
        case PortionType::FlyCnt:
            sDisplay = OUString(u'\xfffc');
            break;
        case PortionType::Field:
        case PortionType::Hidden:
        case PortionType::Combined:
        case PortionType::IsoRef:
            // When the field content is empty, input a special character.
            if (rText.isEmpty())
                sDisplay = OUString(u'\xfffc');
            else
                sDisplay = rText;
            m_aFieldPosition.push_back(m_aBuffer.getLength());
            m_aFieldPosition.push_back(m_aBuffer.getLength() + rText.getLength());
            break;
        case PortionType::FootnoteNum:
            break;
        case PortionType::Footnote:
            {
                sDisplay = rText;
                sal_Int32 nStart=m_aBuffer.getLength();
                sal_Int32 nEnd=nStart + rText.getLength();
                m_vecPairPos.emplace_back(nStart,nEnd);
                break;
            }
            break;
        case PortionType::Number:
        case PortionType::Bullet:
            sDisplay = rText + " ";
            break;
        // There should probably be some special treatment to graphical bullets
        case PortionType::GrfNum:
            break;
        // #i111768# - apply patch from kstribley:
        // Include the control characters.
        case PortionType::ControlChar:
            sDisplay = rText + OUStringChar(m_rTextFrame.GetText()[sal_Int32(m_nViewPosition)]);
            break;
        case PortionType::Bookmark:
            if ( m_pViewOptions->IsShowBookmarks() )
                sDisplay = rText + " ";
            break;
        default:
            sDisplay = rText;
            break;
    }

    // ignore zero/zero portions (except for terminators)
    if ((nLength == TextFrameIndex(0)) && (sDisplay.getLength() == 0) && (nType != POR_TERMINATE))
        return;

    // special treatment for zero length portion at the beginning:
    // count as 'before' portion
    if ((nLength == TextFrameIndex(0)) && (m_nViewPosition == TextFrameIndex(0)))
        m_nBeforePortions++;

    // store the 'old' positions
    m_ViewPositions.push_back( m_nViewPosition );
    m_aAccessiblePositions.push_back( m_aBuffer.getLength() );

    // store portion attributes
    sal_uInt8 nAttr = PORATTR_SPECIAL;
    if( IsGrayPortionType(nType) )      nAttr |= PORATTR_GRAY;
    if (nLength == TextFrameIndex(0))   nAttr |= PORATTR_READONLY;
    if( nType == POR_TERMINATE )        nAttr |= PORATTR_TERM;
    m_aPortionAttrs.push_back( nAttr );

    // update buffer + nViewPosition
    m_aBuffer.append( sDisplay );
    m_nViewPosition += nLength;
}

void SwAccessiblePortionData::LineBreak()
{
    OSL_ENSURE( !m_bFinished, "We are already done!" );

    m_aLineBreaks.push_back( m_aBuffer.getLength() );
}

void SwAccessiblePortionData::Skip(TextFrameIndex const nLength)
{
    OSL_ENSURE( !m_bFinished, "We are already done!" );
    OSL_ENSURE( m_ViewPositions.empty(), "Never Skip() after portions" );
    OSL_ENSURE(nLength <= TextFrameIndex(m_rTextFrame.GetText().getLength()),
            "skip exceeds model string!" );

    m_nViewPosition += nLength;
}

void SwAccessiblePortionData::Finish()
{
    OSL_ENSURE( !m_bFinished, "We are already done!" );

    // include terminator values: always include two 'last character'
    // markers in the position arrays to make sure we always find one
    // position before the end
    Special( TextFrameIndex(0), OUString(), POR_TERMINATE );
    Special( TextFrameIndex(0), OUString(), POR_TERMINATE );
    LineBreak();
    LineBreak();

    m_sAccessibleString = m_aBuffer.makeStringAndClear();
    m_bFinished = true;
}

bool SwAccessiblePortionData::IsPortionAttrSet(
    size_t nPortionNo, sal_uInt8 nAttr ) const
{
    OSL_ENSURE( nPortionNo < m_aPortionAttrs.size(),
                "Illegal portion number" );
    return (m_aPortionAttrs[nPortionNo] & nAttr) != 0;
}

bool SwAccessiblePortionData::IsSpecialPortion( size_t nPortionNo ) const
{
    return IsPortionAttrSet(nPortionNo, PORATTR_SPECIAL);
}

bool SwAccessiblePortionData::IsGrayPortionType( PortionType nType ) const
{
    // gray portions?
    // Compare with: inftxt.cxx, SwTextPaintInfo::DrawViewOpt(...)
    bool bGray = false;
    switch( nType )
    {
        case PortionType::Footnote:
        case PortionType::IsoRef:
        case PortionType::Ref:
        case PortionType::QuoVadis:
        case PortionType::Number:
        case PortionType::Field:
        case PortionType::InputField:
        case PortionType::IsoTox:
        case PortionType::Tox:
        case PortionType::Hidden:
            bGray = !m_pViewOptions->IsPagePreview() &&
                !m_pViewOptions->IsReadonly() && m_pViewOptions->IsFieldShadings();
            break;
        case PortionType::Tab:       bGray = m_pViewOptions->IsTab();          break;
        case PortionType::SoftHyphen:  bGray = m_pViewOptions->IsSoftHyph();     break;
        case PortionType::Blank:     bGray = m_pViewOptions->IsHardBlank();    break;
        default:
            break; // bGray is false
    }
    return bGray;
}

const OUString& SwAccessiblePortionData::GetAccessibleString() const
{
    OSL_ENSURE( m_bFinished, "Shouldn't call this before we are done!" );

    return m_sAccessibleString;
}

void SwAccessiblePortionData::GetLineBoundary(
    Boundary& rBound,
    sal_Int32 nPos ) const
{
    FillBoundary( rBound, m_aLineBreaks,
                  FindBreak( m_aLineBreaks, nPos ) );
}

// #i89175#
sal_Int32 SwAccessiblePortionData::GetLineCount() const
{
    size_t nBreaks = m_aLineBreaks.size();
    // A non-empty paragraph has at least 4 breaks: one for each line3 and
    // 3 additional ones.
    // An empty paragraph has 3 breaks.
    // Less than 3 breaks is an error case.
    sal_Int32 nLineCount = ( nBreaks > 3 )
                           ? nBreaks - 3
                           : ( ( nBreaks == 3 ) ? 1 : 0 );
    return nLineCount;
}

sal_Int32 SwAccessiblePortionData::GetLineNo( const sal_Int32 nPos ) const
{
    sal_Int32 nLineNo = FindBreak( m_aLineBreaks, nPos );

    // handling of position after last character
    const sal_Int32 nLineCount( GetLineCount() );
    if ( nLineNo >= nLineCount )
    {
        nLineNo = nLineCount - 1;
    }

    return nLineNo;
}

void SwAccessiblePortionData::GetBoundaryOfLine( const sal_Int32 nLineNo,
                                                 i18n::Boundary& rLineBound )
{
    FillBoundary( rLineBound, m_aLineBreaks, nLineNo );
}

void SwAccessiblePortionData::GetLastLineBoundary(
    Boundary& rBound ) const
{
    OSL_ENSURE( m_aLineBreaks.size() >= 2, "need min + max value" );

    // The last two positions except the two delimiters are the ones
    // we are looking for, except for empty paragraphs (nBreaks==3)
    size_t nBreaks = m_aLineBreaks.size();
    FillBoundary( rBound, m_aLineBreaks, nBreaks <= 3 ? 0 : nBreaks-4 );
}

TextFrameIndex SwAccessiblePortionData::GetCoreViewPosition(sal_Int32 const nPos) const
{
    OSL_ENSURE( nPos >= 0, "illegal position" );
    OSL_ENSURE( nPos <= m_sAccessibleString.getLength(), "illegal position" );

    // find the portion number
    size_t nPortionNo = FindBreak( m_aAccessiblePositions, nPos );

    // get core view portion size
    TextFrameIndex nStartPos = m_ViewPositions[nPortionNo];

    // if it's a non-special portion, move into the portion, else
    // return the portion start
    if( ! IsSpecialPortion( nPortionNo ) )
    {
        // text portions have to be of the same width
        OSL_ENSURE( sal_Int32(m_ViewPositions[nPortionNo+1] - nStartPos) ==
                    ( m_aAccessiblePositions[nPortionNo+1] -
                      m_aAccessiblePositions[nPortionNo] ),
                    "accessibility portion disagrees with text model" );

        nStartPos += TextFrameIndex(nPos - m_aAccessiblePositions[nPortionNo]);
    }
    // else: return nStartPos unmodified

    OSL_ENSURE(nStartPos >= TextFrameIndex(0), "There's something weird in number of characters of SwTextFrame");
    return nStartPos;
}

void SwAccessiblePortionData::FillBoundary(
    Boundary& rBound,
    const AccessiblePositions& rPositions,
    size_t nPos )
{
    rBound.startPos = rPositions[nPos];
    rBound.endPos = rPositions[nPos+1];
}

template<typename T>
static size_t FindBreak(const std::vector<T>& rPositions, T const nValue)
{
    OSL_ENSURE( rPositions.size() >= 2, "need min + max value" );
    OSL_ENSURE( rPositions[0] <= nValue, "need min value" );
    OSL_ENSURE( rPositions[rPositions.size()-1] >= nValue,
                "need first terminator value" );
    OSL_ENSURE( rPositions[rPositions.size()-2] >= nValue,
                "need second terminator value" );

    size_t nMin = 0;
    size_t nMax = rPositions.size()-2;

    // loop until no more than two candidates are left
    while( nMin+1 < nMax )
    {
        // check loop invariants
        OSL_ENSURE( ( (nMin == 0) && (rPositions[nMin] <= nValue) ) ||
                    ( (nMin != 0) && (rPositions[nMin] < nValue) ),
                    "minvalue not minimal" );
        OSL_ENSURE( nValue <= rPositions[nMax], "max value not maximal" );

        // get middle (and ensure progress)
        size_t nMiddle = (nMin + nMax)/2;
        OSL_ENSURE( nMin < nMiddle, "progress?" );
        OSL_ENSURE( nMiddle < nMax, "progress?" );

        // check array
        OSL_ENSURE( rPositions[nMin] <= rPositions[nMiddle],
                    "garbled positions array" );
        OSL_ENSURE( rPositions[nMiddle] <= rPositions[nMax],
                    "garbled positions array" );

        if( nValue > rPositions[nMiddle] )
            nMin = nMiddle;
        else
            nMax = nMiddle;
    }

    // only two are left; we only need to check which one is the winner
    OSL_ENSURE( (nMax == nMin) || (nMax == nMin+1), "only two left" );
    if( (rPositions[nMin] < nValue) && (rPositions[nMin+1] <= nValue) )
        nMin = nMin+1;

    // finally, check to see whether the returned value is the 'right' position
    OSL_ENSURE( rPositions[nMin] <= nValue, "not smaller or equal" );
    OSL_ENSURE( nValue <= rPositions[nMin+1], "not equal or larger" );
    OSL_ENSURE( (nMin == 0) || (rPositions[nMin-1] <= nValue),
                "earlier value should have been returned" );

    OSL_ENSURE( nMin < rPositions.size()-1,
                "shouldn't return last position (due to terminator values)" );

    return nMin;
}

template<typename T>
static size_t FindLastBreak(const std::vector<T>& rPositions, T const nValue)
{
    size_t nResult = FindBreak( rPositions, nValue );

    // skip 'zero-length' portions
    // #i70538# consider size of <rPosition> and ignore last entry
    while ( nResult < rPositions.size() - 2 &&
            rPositions[nResult+1] <= nValue )
    {
        nResult++;
    }

    return nResult;
}

void SwAccessiblePortionData::GetSentenceBoundary(
    Boundary& rBound,
    sal_Int32 nPos )
{
    OSL_ENSURE( nPos >= 0, "illegal position; check before" );
    OSL_ENSURE( nPos < m_sAccessibleString.getLength(), "illegal position" );

    if( m_aSentences.empty() )
    {
        assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());

        m_aSentences.reserve(10);

        // use xBreak->endOfSentence to iterate over all words; store
        // positions in pSentences
        sal_Int32 nCurrent = 0;
        sal_Int32 nLength = m_sAccessibleString.getLength();
        do
        {
            m_aSentences.push_back( nCurrent );

            const TextFrameIndex nFramePos = GetCoreViewPosition(nCurrent);

            sal_Int32 nNew = g_pBreakIt->GetBreakIter()->endOfSentence(
                m_sAccessibleString, nCurrent,
                g_pBreakIt->GetLocale(m_rTextFrame.GetLangOfChar(nFramePos, 0, true))) + 1;

            if( (nNew < 0) && (nNew > nLength) )
                nNew = nLength;
            else if (nNew <= nCurrent)
                nNew = nCurrent + 1;   // ensure forward progress

            nCurrent = nNew;
        }
        while (nCurrent < nLength);

        // finish with two terminators
        m_aSentences.push_back( nLength );
        m_aSentences.push_back( nLength );
    }

    FillBoundary( rBound, m_aSentences, FindBreak( m_aSentences, nPos ) );
}

void SwAccessiblePortionData::GetAttributeBoundary(
    Boundary& rBound,
    sal_Int32 nPos) const
{
    // attribute boundaries can only occur on portion boundaries
    FillBoundary( rBound, m_aAccessiblePositions,
                  FindBreak( m_aAccessiblePositions, nPos ) );
}

sal_Int32 SwAccessiblePortionData::GetAccessiblePosition(TextFrameIndex const nPos) const
{
    OSL_ENSURE(nPos <= TextFrameIndex(m_rTextFrame.GetText().getLength()), "illegal position");

    // find the portion number
    // #i70538# - consider "empty" model portions - e.g. number portion
    size_t nPortionNo = FindLastBreak( m_ViewPositions, nPos );

    sal_Int32 nRet = m_aAccessiblePositions[nPortionNo];

    // if the view portion has more than one position, go into it;
    // else return that position
    TextFrameIndex nStartPos = m_ViewPositions[nPortionNo];
    TextFrameIndex nEndPos = m_ViewPositions[nPortionNo+1];
    if (!IsSpecialPortion(nPortionNo))
    {
        // text portions have to be of the same width
        OSL_ENSURE( sal_Int32(nEndPos - nStartPos) ==
                    ( m_aAccessiblePositions[nPortionNo+1] -
                      m_aAccessiblePositions[nPortionNo] ),
                    "accessibility portion disagrees with text model" );

        TextFrameIndex nWithinPortion = nPos - m_ViewPositions[nPortionNo];
        nRet += sal_Int32(nWithinPortion);
    }
    // else: return nRet unmodified

    OSL_ENSURE( (nRet >= 0) && (nRet <= m_sAccessibleString.getLength()),
                "too long!" );
    return nRet;
}

TextFrameIndex SwAccessiblePortionData::FillSpecialPos(
    sal_Int32 nPos,
    SwSpecialPos& rPos,
    SwSpecialPos*& rpPos ) const
{
    size_t nPortionNo = FindLastBreak( m_aAccessiblePositions, nPos );

    SwSPExtendRange nExtend(SwSPExtendRange::NONE);
    sal_Int32 nRefPos(0);
    TextFrameIndex nCorePos(0);

    if( nPortionNo < m_nBeforePortions )
    {
        nExtend = SwSPExtendRange::BEFORE;
        rpPos = &rPos;
    }
    else
    {
        TextFrameIndex nCoreEndPos = m_ViewPositions[nPortionNo+1];
        nCorePos = m_ViewPositions[nPortionNo];

        // skip backwards over zero-length portions, since GetCharRect()
        // counts all model-zero-length portions as belonging to the
        // previous portion
        size_t nCorePortionNo = nPortionNo;
        while (nCorePos == nCoreEndPos)
        {
            nCorePortionNo--;
            nCoreEndPos = nCorePos;
            nCorePos = m_ViewPositions[nCorePortionNo];

            OSL_ENSURE( nCorePos >= TextFrameIndex(0), "Can't happen." );
            OSL_ENSURE( nCorePortionNo >= m_nBeforePortions, "Can't happen." );
        }
        OSL_ENSURE( nCorePos != nCoreEndPos,
                    "portion with core-representation expected" );

        // if we have anything except plain text, compute nExtend + nRefPos
        if (IsSpecialPortion(nCorePortionNo))
        {
            // case 1: a non-text portion
            // reference position is the first accessibility for our
            // core portion
            nRefPos = m_aAccessiblePositions[ nCorePortionNo ];
            nExtend = SwSPExtendRange::NONE;
            rpPos = &rPos;
        }
        else if(nPortionNo != nCorePortionNo)
        {
            // case 2: a multi-character (text!) portion, followed by
            // zero-length portions
            // reference position is the first character of the next
            // portion, and we are 'behind'
            nRefPos = m_aAccessiblePositions[ nCorePortionNo+1 ];
            nExtend = SwSPExtendRange::BEHIND;
            rpPos = &rPos;
        }
        else
        {
            // case 3: regular text portion
            OSL_ENSURE( sal_Int32(nCoreEndPos - nCorePos) ==
                        ( m_aAccessiblePositions[nPortionNo+1] -
                          m_aAccessiblePositions[nPortionNo] ),
                        "text portion expected" );

            nCorePos += TextFrameIndex(nPos - m_aAccessiblePositions[nPortionNo]);
            rpPos = nullptr;
        }
    }
    if( rpPos != nullptr )
    {
        OSL_ENSURE( rpPos == &rPos, "Yes!" );
        OSL_ENSURE( nRefPos <= nPos, "wrong reference" );

        // get the line number, and adjust nRefPos for the line
        // (if necessary)
        size_t nRefLine = FindBreak( m_aLineBreaks, nRefPos );
        size_t nMyLine  = FindBreak( m_aLineBreaks, nPos );
        sal_Int32 nLineOffset = o3tl::narrowing<sal_Int32>( nMyLine - nRefLine );
        if( nLineOffset != 0 )
            nRefPos = m_aLineBreaks[ nMyLine ];

        // fill char offset and 'special position'
        rPos.nCharOfst = nPos - nRefPos;
        rPos.nExtendRange = nExtend;
        rPos.nLineOfst = nLineOffset;
    }

    return nCorePos;
}

bool SwAccessiblePortionData::FillBoundaryIFDateField( css::i18n::Boundary& rBound, const sal_Int32 nPos )
{
    if( m_aFieldPosition.size() < 2 )
        return false;
    for( size_t i = 0; i < m_aFieldPosition.size() - 1; i += 2 )
    {
        if( nPos < m_aFieldPosition[ i + 1 ]  &&  nPos >= m_aFieldPosition[ i ] )
        {
            rBound.startPos = m_aFieldPosition[i];
            rBound.endPos =  m_aFieldPosition[i + 1];
            return true;
        }
    }
    return false;
}

void SwAccessiblePortionData::AdjustAndCheck(
    sal_Int32 nPos,
    size_t& nPortionNo,
    TextFrameIndex& rCorePos,
    bool& bEdit) const
{
    // find portion and get mode position
    nPortionNo = FindBreak( m_aAccessiblePositions, nPos );
    rCorePos = m_ViewPositions[ nPortionNo ];

    // for special portions, make sure we're on a portion boundary
    // for text portions, add the in-portion offset
    if( IsSpecialPortion( nPortionNo ) )
        bEdit &= nPos == m_aAccessiblePositions[nPortionNo];
    else
        rCorePos += TextFrameIndex(nPos - m_aAccessiblePositions[nPortionNo]);
}

bool SwAccessiblePortionData::GetEditableRange(
    sal_Int32 nStart, sal_Int32 nEnd,
    TextFrameIndex& rCoreStart, TextFrameIndex& rCoreEnd) const
{
    bool bIsEditable = true;

    // get start and end portions
    size_t nStartPortion, nEndPortion;
    AdjustAndCheck( nStart, nStartPortion, rCoreStart, bIsEditable );
    AdjustAndCheck( nEnd,   nEndPortion,   rCoreEnd,   bIsEditable );

    // iterate over portions, and make sure there is no read-only portion
    // in-between
    size_t nLastPortion = nEndPortion;

    // don't count last portion if we're in front of a special portion
    if( IsSpecialPortion(nLastPortion) )
    {
        if (nLastPortion > 0)
            nLastPortion--;
        else
            // special case: because size_t is usually unsigned, we can't just
            // decrease nLastPortion to -1 (which would normally do the job, so
            // this whole if wouldn't be needed). Instead, we'll do this
            // special case and just increase the start portion beyond the last
            // portion to make sure the loop below will have zero iteration.
            nStartPortion = nLastPortion + 1;
    }

    for( size_t nPor = nStartPortion; nPor <= nLastPortion; nPor++ )
    {
        bIsEditable &= ! IsPortionAttrSet(nPor, PORATTR_READONLY);
    }

    return bIsEditable;
}

bool SwAccessiblePortionData::IsValidCorePosition(TextFrameIndex const nPos) const
{
    // a position is valid if it's within the core view positions that we know
    return (m_ViewPositions[0] <= nPos) && (nPos <= m_ViewPositions.back());
}

bool SwAccessiblePortionData::IsZeroCorePositionData()
{
    if (m_ViewPositions.empty()) return true;
    return m_ViewPositions[0] == TextFrameIndex(0)
        && m_ViewPositions.back() == TextFrameIndex(0);
}

bool SwAccessiblePortionData::IsIndexInFootnode(sal_Int32 nIndex)
{
    for (const auto & pairPos : m_vecPairPos)
    {
        if(nIndex >= pairPos.first && nIndex < pairPos.second )
        {
            return true;
        }
    }
    return false;
}

bool SwAccessiblePortionData::IsInGrayPortion( sal_Int32 nPos )
{
//    return IsGrayPortion( FindBreak( aAccessiblePositions, nPos ) );
    return IsPortionAttrSet( FindBreak( m_aAccessiblePositions, nPos ),
                             PORATTR_GRAY );
}

sal_Int32 SwAccessiblePortionData::GetFieldIndex(sal_Int32 nPos) const
{
    sal_Int32 nIndex = -1;
    if( m_aFieldPosition.size() >= 2 )
    {
        for( size_t i = 0; i < m_aFieldPosition.size() - 1; i += 2 )
        {
            if( nPos <= m_aFieldPosition[ i + 1 ]  &&  nPos >= m_aFieldPosition[ i ] )
            {
                nIndex = i/2;
                break;
            }
        }
    }
    return nIndex;
}

TextFrameIndex SwAccessiblePortionData::GetFirstValidCorePosition() const
{
    return m_ViewPositions[0];
}

TextFrameIndex SwAccessiblePortionData::GetLastValidCorePosition() const
{
    return m_ViewPositions.back();
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
