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

Quelle  PresenterTextView.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 "PresenterTextView.hxx"
#include "PresenterCanvasHelper.hxx"
#include "PresenterGeometryHelper.hxx"
#include "PresenterTimer.hxx"

#include <algorithm>
#include <cmath>
#include <numeric>

#include <com/sun/star/accessibility/AccessibleTextType.hpp>
#include <com/sun/star/container/XEnumerationAccess.hpp>
#include <com/sun/star/i18n/BreakIterator.hpp>
#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
#include <com/sun/star/i18n/ScriptDirection.hpp>
#include <com/sun/star/i18n/WordType.hpp>
#include <com/sun/star/rendering/CompositeOperation.hpp>
#include <com/sun/star/rendering/TextDirection.hpp>
#include <com/sun/star/text/WritingMode2.hpp>
#include <o3tl/safeint.hxx>
#include <utility>
#include <comphelper/diagnose_ex.hxx>

using namespace ::com::sun::star;
using namespace ::com::sun::star::accessibility;
using namespace ::com::sun::star::uno;

const sal_Int64 CaretBlinkInterval = 500 * 1000 * 1000;

//#define SHOW_CHARACTER_BOXES

namespace {
    sal_Int32 Signum (const sal_Int32 nValue)
    {
        if (nValue < 0)
            return -1;
        else if (nValue > 0)
            return +1;
        else
            return 0;
    }
}

namespace sdext::presenter {

//===== PresenterTextView =====================================================

PresenterTextView::PresenterTextView (
    const Reference<XComponentContext>& rxContext,
    const Reference<rendering::XCanvas>& rxCanvas,
    const ::std::function<void (const css::awt::Rectangle&)>& rInvalidator)
    : mxCanvas(rxCanvas),
      maLocation(0,0),
      maSize(0,0),
      mpCaret(std::make_shared<PresenterTextCaret>(
          rxContext,
          [this] (sal_Int32 const nParagraphIndex, sal_Int32 const nCharacterIndex)
              { return this->GetCaretBounds(nParagraphIndex, nCharacterIndex); },
          rInvalidator)),
      mnLeftOffset(0),
      mnTopOffset(0),
      mbIsFormatPending(false)
{
    Reference<lang::XMultiComponentFactory> xFactory =
        rxContext->getServiceManager();
    if ( ! xFactory.is())
        return;

    // Create the break iterator that we use to break text into lines.
    mxBreakIterator = i18n::BreakIterator::create(rxContext);

    // Create the script type detector that is used to split paragraphs into
    // portions of the same text direction.
    mxScriptTypeDetector.set(
        xFactory->createInstanceWithContext(
            u"com.sun.star.i18n.ScriptTypeDetector"_ustr,
            rxContext),
        UNO_QUERY_THROW);
}

void PresenterTextView::SetText (const Reference<text::XText>& rxText)
{
    maParagraphs.clear();

    Reference<container::XEnumerationAccess> xParagraphAccess (rxText, UNO_QUERY);
    if ( ! xParagraphAccess.is())
        return;

    Reference<container::XEnumeration> xParagraphs =
        xParagraphAccess->createEnumeration();
    if ( ! xParagraphs.is())
        return;

    if ( ! mpFont || ! mpFont->PrepareFont(mxCanvas))
        return;

    sal_Int32 nCharacterCount (0);
    while (xParagraphs->hasMoreElements())
    {
        SharedPresenterTextParagraph pParagraph = std::make_shared<PresenterTextParagraph>(
            maParagraphs.size(),
            mxBreakIterator,
            mxScriptTypeDetector,
            Reference<text::XTextRange>(xParagraphs->nextElement(), UNO_QUERY),
            mpCaret);
        pParagraph->SetupCellArray(mpFont);
        pParagraph->SetCharacterOffset(nCharacterCount);
        nCharacterCount += pParagraph->GetCharacterCount();
        maParagraphs.push_back(pParagraph);
    }

    if (mpCaret)
        mpCaret->HideCaret();

    RequestFormat();
}

void PresenterTextView::SetTextChangeBroadcaster (
    const ::std::function<void ()>& rBroadcaster)
{
    maTextChangeBroadcaster = rBroadcaster;
}

void PresenterTextView::SetLocation (const css::geometry::RealPoint2D& rLocation)
{
    maLocation = rLocation;

    for (auto& rxParagraph : maParagraphs)
    {
        rxParagraph->SetOrigin(
            maLocation.X - mnLeftOffset,
            maLocation.Y - mnTopOffset);
    }
}

void PresenterTextView::SetSize (const css::geometry::RealSize2D& rSize)
{
    maSize = rSize;
    RequestFormat();
}

double PresenterTextView::GetTotalTextHeight()
{
    if (mbIsFormatPending)
    {
        if ( ! mpFont->PrepareFont(mxCanvas))
            return 0;
        Format();
    }

    return std::accumulate(maParagraphs.begin(), maParagraphs.end(), double(0),
        [](const double& nTotalHeight, const SharedPresenterTextParagraph& rxParagraph) {
            return nTotalHeight + rxParagraph->GetTotalTextHeight();
        });
}

void PresenterTextView::SetFont (const PresenterTheme::SharedFontDescriptor& rpFont)
{
    mpFont = rpFont;
    RequestFormat();
}

void PresenterTextView::SetOffset(
    const double nLeft,
    const double nTop)
{
    mnLeftOffset = nLeft;
    mnTopOffset = nTop;

    // Trigger an update of the text origin stored at the individual paragraphs.
    SetLocation(maLocation);
}

void PresenterTextView::MoveCaret (
    const sal_Int32 nDistance,
    const sal_Int16 nTextType)
{
    if ( ! mpCaret)
        return;

    // When the caret has not been visible yet then move it to the beginning
    // of the text.
    if (mpCaret->GetParagraphIndex() < 0)
    {
        mpCaret->SetPosition(0,0);
        return;
    }

    sal_Int32 nParagraphIndex (mpCaret->GetParagraphIndex());
    sal_Int32 nCharacterIndex (mpCaret->GetCharacterIndex());
    switch (nTextType)
    {
        default:
        case AccessibleTextType::CHARACTER:
            nCharacterIndex += nDistance;
            break;

        case AccessibleTextType::WORD:
        {
            sal_Int32 nRemainingDistance (nDistance);
            while (nRemainingDistance != 0)
            {
                SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
                if (pParagraph)
                {
                    const sal_Int32 nDelta (Signum(nDistance));
                    nCharacterIndex = pParagraph->GetWordBoundary(nCharacterIndex, nDelta);
                    if (nCharacterIndex < 0)
                    {
                        // Go to previous or next paragraph.
                        nParagraphIndex += nDelta;
                        if (nParagraphIndex < 0)
                        {
                            nParagraphIndex = 0;
                            nCharacterIndex = 0;
                            nRemainingDistance = 0;
                        }
                        else if (o3tl::make_unsigned(nParagraphIndex) >= maParagraphs.size())
                        {
                            nParagraphIndex = maParagraphs.size()-1;
                            pParagraph = GetParagraph(nParagraphIndex);
                            if (pParagraph)
                                nCharacterIndex = pParagraph->GetCharacterCount();
                            nRemainingDistance = 0;
                        }
                        else
                        {
                            nRemainingDistance -= nDelta;

                            // Move caret one character to the end of
                            // the previous or the start of the next paragraph.
                            pParagraph = GetParagraph(nParagraphIndex);
                            if (pParagraph)
                            {
                                if (nDistance<0)
                                    nCharacterIndex = pParagraph->GetCharacterCount();
                                else
                                    nCharacterIndex = 0;
                            }
                        }
                    }
                    else
                        nRemainingDistance -= nDelta;
                }
                else
                    break;
            }
            break;
        }
    }

    // Move the caret to the new position.
    mpCaret->SetPosition(nParagraphIndex, nCharacterIndex);
}

void PresenterTextView::Paint (
    const css::awt::Rectangle& rUpdateBox)
{
    if ( ! mxCanvas.is())
        return;
    if ( ! mpFont->PrepareFont(mxCanvas))
        return;

    if (mbIsFormatPending)
        Format();

    // Setup the clipping rectangle.  Horizontally we make it a little
    // larger to allow characters (and the caret) to stick out of their
    // bounding boxes.  This can happen on some characters (like the
    // uppercase J) for typographical reasons.
    const sal_Int32 nAdditionalLeftBorder (10);
    const sal_Int32 nAdditionalRightBorder (5);
    double nX (maLocation.X - mnLeftOffset);
    double nY (maLocation.Y - mnTopOffset);
    const sal_Int32 nClipLeft (::std::max(
        PresenterGeometryHelper::Round(maLocation.X)-nAdditionalLeftBorder, rUpdateBox.X));
    const sal_Int32 nClipTop (::std::max(
        PresenterGeometryHelper::Round(maLocation.Y), rUpdateBox.Y));
    const sal_Int32 nClipRight (::std::min(
        PresenterGeometryHelper::Round(maLocation.X+maSize.Width)+nAdditionalRightBorder, rUpdateBox.X+rUpdateBox.Width));
    const sal_Int32 nClipBottom (::std::min(
        PresenterGeometryHelper::Round(maLocation.Y+maSize.Height), rUpdateBox.Y+rUpdateBox.Height));
    if (nClipLeft>=nClipRight || nClipTop>=nClipBottom)
        return;

    const awt::Rectangle aClipBox(
        nClipLeft,
        nClipTop,
        nClipRight - nClipLeft,
        nClipBottom - nClipTop);
    Reference<rendering::XPolyPolygon2D> xClipPolygon (
        PresenterGeometryHelper::CreatePolygon(aClipBox, mxCanvas->getDevice()));

    const rendering::ViewState aViewState(
        geometry::AffineMatrix2D(1,0,0, 0,1,0),
        xClipPolygon);

    rendering::RenderState aRenderState (
        geometry::AffineMatrix2D(1,0,nX, 0,1,nY),
        nullptr,
        Sequence<double>(4),
        rendering::CompositeOperation::SOURCE);
    PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor);

    for (const auto& rxParagraph : maParagraphs)
    {
        rxParagraph->Paint(
            mxCanvas,
            maSize,
            mpFont,
            aViewState,
            aRenderState,
            mnTopOffset,
            nClipTop,
            nClipBottom);
    }

    aRenderState.AffineTransform.m02 = 0;
    aRenderState.AffineTransform.m12 = 0;

#ifdef SHOW_CHARACTER_BOXES
    PresenterCanvasHelper::SetDeviceColor(aRenderState, 0x00808080);
    for (sal_Int32 nParagraphIndex(0), nParagraphCount(GetParagraphCount());
         nParagraphIndex<nParagraphCount;
         ++nParagraphIndex)
    {
        const SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
        if ( ! pParagraph)
            continue;
        for (sal_Int32 nCharacterIndex(0),nCharacterCount(pParagraph->GetCharacterCount());
             nCharacterIndex<nCharacterCount; ++nCharacterIndex)
        {
            const awt::Rectangle aBox (pParagraph->GetCharacterBounds(nCharacterIndex, false));
            mxCanvas->drawPolyPolygon (
                PresenterGeometryHelper::CreatePolygon(
                    aBox,
                    mxCanvas->getDevice()),
                aViewState,
                aRenderState);
        }
    }
    PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor);
#endif

    if (mpCaret && mpCaret->IsVisible())
    {
        mxCanvas->fillPolyPolygon (
            PresenterGeometryHelper::CreatePolygon(
                mpCaret->GetBounds(),
                mxCanvas->getDevice()),
            aViewState,
            aRenderState);
    }
}

const SharedPresenterTextCaret& PresenterTextView::GetCaret() const
{
    return mpCaret;
}

awt::Rectangle PresenterTextView::GetCaretBounds (
    sal_Int32 nParagraphIndex,
    const sal_Int32 nCharacterIndex) const
{
    SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));

    if (pParagraph)
        return pParagraph->GetCharacterBounds(nCharacterIndex, true);
    else
        return awt::Rectangle(0,0,0,0);
}

//----- private ---------------------------------------------------------------

void PresenterTextView::RequestFormat()
{
    mbIsFormatPending = true;
}

void PresenterTextView::Format()
{
    mbIsFormatPending = false;

    double nY (0);
    for (const auto& rxParagraph : maParagraphs)
    {
        rxParagraph->Format(nY, maSize.Width, mpFont);
        nY += rxParagraph->GetTotalTextHeight();
    }

    if (maTextChangeBroadcaster)
        maTextChangeBroadcaster();
}

sal_Int32 PresenterTextView::GetParagraphCount() const
{
    return maParagraphs.size();
}

SharedPresenterTextParagraph PresenterTextView::GetParagraph (
    const sal_Int32 nParagraphIndex) const
{
    if (nParagraphIndex < 0)
        return SharedPresenterTextParagraph();
    else if (o3tl::make_unsigned(nParagraphIndex)>=maParagraphs.size())
        return SharedPresenterTextParagraph();
    else
        return maParagraphs[nParagraphIndex];
}

//===== PresenterTextParagraph ================================================

PresenterTextParagraph::PresenterTextParagraph (
    const sal_Int32 nParagraphIndex,
    const Reference<i18n::XBreakIterator>& rxBreakIterator,
    const Reference<i18n::XScriptTypeDetector>& rxScriptTypeDetector,
    const Reference<text::XTextRange>& rxTextRange,
    SharedPresenterTextCaret xCaret)
    : mnParagraphIndex(nParagraphIndex),
      mpCaret(std::move(xCaret)),
      mxBreakIterator(rxBreakIterator),
      mxScriptTypeDetector(rxScriptTypeDetector),
      mnVerticalOffset(0),
      mnXOrigin(0),
      mnYOrigin(0),
      mnWidth(0),
      mnAscent(0),
      mnDescent(0),
      mnLineHeight(-1),
      mnWritingMode (text::WritingMode2::LR_TB),
      mnCharacterOffset(0)
{
    if (!rxTextRange.is())
        return;

    Reference<beans::XPropertySet> xProperties (rxTextRange, UNO_QUERY);
    try
    {
        xProperties->getPropertyValue(u"WritingMode"_ustr) >>= mnWritingMode;
    }
    catch(beans::UnknownPropertyException&)
    {
        // Ignore the exception.  Use the default value.
    }

    msParagraphText = rxTextRange->getString();
}

void PresenterTextParagraph::Paint (
    const Reference<rendering::XCanvas>& rxCanvas,
    const geometry::RealSize2D& rSize,
    const PresenterTheme::SharedFontDescriptor& rpFont,
    const rendering::ViewState& rViewState,
    rendering::RenderState& rRenderState,
    const double nTopOffset,
    const double nClipTop,
    const double nClipBottom)
{
    if (mnLineHeight <= 0)
        return;

    sal_Int8 nTextDirection (GetTextDirection());

    const double nSavedM12 (rRenderState.AffineTransform.m12);

    if ( ! IsTextReferencePointLeft())
        rRenderState.AffineTransform.m02 += rSize.Width;

#ifdef SHOW_CHARACTER_BOXES
    for (sal_Int32 nIndex=0,nCount=maLines.size();
         nIndex<nCount;
         ++nIndex)
    {
        Line& rLine (maLines[nIndex]);
        rLine.ProvideLayoutedLine(msParagraphText, rpFont, nTextDirection);
    }
#endif

    for (sal_Int32 nIndex=0,nCount=maLines.size();
         nIndex<nCount;
         ++nIndex, rRenderState.AffineTransform.m12 += mnLineHeight)
    {
        Line& rLine (maLines[nIndex]);

        // Paint only visible lines.
        const double nLineTop = rLine.mnBaseLine - mnAscent - nTopOffset;
        if (nLineTop + mnLineHeight< nClipTop)
            continue;
        else if (nLineTop > nClipBottom)
            break;
        rLine.ProvideLayoutedLine(msParagraphText, rpFont, nTextDirection);

        rRenderState.AffineTransform.m12 = nSavedM12 + rLine.mnBaseLine;

        rxCanvas->drawTextLayout (
            rLine.mxLayoutedLine,
            rViewState,
            rRenderState);
    }
    rRenderState.AffineTransform.m12 = nSavedM12;

    if ( ! IsTextReferencePointLeft())
        rRenderState.AffineTransform.m02 -= rSize.Width;
}

void PresenterTextParagraph::Format (
    const double nY,
    const double nWidth,
    const PresenterTheme::SharedFontDescriptor& rpFont)
{
    // Make sure that the text view is in a valid and sane state.
    if ( ! mxBreakIterator.is() || ! mxScriptTypeDetector.is())
        return;
    if (nWidth<=0)
        return;
    if ( ! rpFont || ! rpFont->mxFont.is())
        return;

    sal_Int32 nPosition (0);

    mnWidth = nWidth;
    maLines.clear();
    mnLineHeight = 0;
    mnAscent = 0;
    mnDescent = 0;
    mnVerticalOffset = nY;
    maWordBoundaries.clear();
    maWordBoundaries.push_back(0);

    const rendering::FontMetrics aMetrics (rpFont->mxFont->getFontMetrics());
    mnAscent = aMetrics.Ascent;
    mnDescent = aMetrics.Descent;
    mnLineHeight = aMetrics.Ascent + aMetrics.Descent + aMetrics.ExternalLeading;
    nPosition = 0;
    i18n::Boundary aCurrentLine(0,0);
    while (true)
    {
        const i18n::Boundary aWordBoundary = mxBreakIterator->nextWord(
            msParagraphText,
            nPosition,
            lang::Locale(),
            i18n::WordType::ANYWORD_IGNOREWHITESPACES);
        AddWord(nWidth, aCurrentLine, aWordBoundary.startPos, rpFont);

        // Remember the new word boundary for caret travelling by words.
        // Prevent duplicates.
        if (aWordBoundary.startPos > maWordBoundaries.back())
            maWordBoundaries.push_back(aWordBoundary.startPos);

        if (aWordBoundary.endPos>aWordBoundary.startPos)
            AddWord(nWidth, aCurrentLine, aWordBoundary.endPos, rpFont);

        if (aWordBoundary.startPos<0 || aWordBoundary.endPos<0)
            break;
        if (nPosition >= aWordBoundary.endPos)
            break;
        nPosition = aWordBoundary.endPos;
    }

    if (aCurrentLine.endPos>aCurrentLine.startPos)
        AddLine(aCurrentLine);

}

sal_Int32 PresenterTextParagraph::GetWordBoundary(
    const sal_Int32 nLocalCharacterIndex,
    const sal_Int32 nDistance)
{
    OSL_ASSERT(nDistance==-1 || nDistance==+1);

    if (nLocalCharacterIndex < 0)
    {
        // The caller asked for the start or end position of the paragraph.
        if (nDistance < 0)
            return 0;
        else
            return GetCharacterCount();
    }

    sal_Int32 nIndex (0);
    for (sal_Int32 nCount (maWordBoundaries.size()); nIndex<nCount; ++nIndex)
    {
        if (maWordBoundaries[nIndex] >= nLocalCharacterIndex)
        {
            // When inside the word (not at its start or end) then
            // first move to the start or end before going the previous or
            // next word.
            if (maWordBoundaries[nIndex] > nLocalCharacterIndex)
                if (nDistance > 0)
                    --nIndex;
            break;
        }
    }

    nIndex += nDistance;

    if (nIndex < 0)
        return -1;
    else if (o3tl::make_unsigned(nIndex)>=maWordBoundaries.size())
        return -1;
    else
        return maWordBoundaries[nIndex];
}

sal_Int32 PresenterTextParagraph::GetCaretPosition() const
{
    if (mpCaret && mpCaret->GetParagraphIndex()==mnParagraphIndex)
        return mpCaret->GetCharacterIndex();
    else
        return -1;
}

void PresenterTextParagraph::SetCaretPosition (const sal_Int32 nPosition) const
{
    if (mpCaret && mpCaret->GetParagraphIndex()==mnParagraphIndex)
        return mpCaret->SetPosition(mnParagraphIndex, nPosition);
}

void PresenterTextParagraph::SetOrigin (const double nXOrigin, const double nYOrigin)
{
    mnXOrigin = nXOrigin;
    mnYOrigin = nYOrigin;
}

awt::Point PresenterTextParagraph::GetRelativeLocation() const
{
    return awt::Point(
        sal_Int32(mnXOrigin),
        sal_Int32(mnYOrigin + mnVerticalOffset));
}

awt::Size PresenterTextParagraph::GetSize() const
{
    return awt::Size(
        sal_Int32(mnWidth),
        sal_Int32(GetTotalTextHeight()));
}

void PresenterTextParagraph::AddWord (
    const double nWidth,
    i18n::Boundary& rCurrentLine,
    const sal_Int32 nWordBoundary,
    const PresenterTheme::SharedFontDescriptor& rpFont)
{
    sal_Int32 nLineStart (0);
    if ( ! maLines.empty())
        nLineStart = rCurrentLine.startPos;

    const OUString sLineCandidate (
        msParagraphText.copy(nLineStart, nWordBoundary-nLineStart));

    css::geometry::RealRectangle2D aLineBox (
        PresenterCanvasHelper::GetTextBoundingBox (
            rpFont->mxFont,
            sLineCandidate,
            mnWritingMode));
    const double nLineWidth (aLineBox.X2 - aLineBox.X1);

    if (nLineWidth >= nWidth)
    {
        // Add new line with a single word (so far).
        AddLine(rCurrentLine);
    }
    rCurrentLine.endPos = nWordBoundary;
}

void PresenterTextParagraph::AddLine (
    i18n::Boundary& rCurrentLine)
{
    Line aLine (rCurrentLine.startPos, rCurrentLine.endPos);

    // Find the start and end of the line with respect to cells.
    if (!maLines.empty())
    {
        aLine.mnLineStartCellIndex = maLines.back().mnLineEndCellIndex;
        aLine.mnBaseLine = maLines.back().mnBaseLine + mnLineHeight;
    }
    else
    {
        aLine.mnLineStartCellIndex = 0;
        aLine.mnBaseLine = mnVerticalOffset + mnAscent;
    }
    sal_Int32 nCellIndex (aLine.mnLineStartCellIndex);
    double nWidth (0);
    for ( ; nCellIndex<sal_Int32(maCells.size()); ++nCellIndex)
    {
        const Cell& rCell (maCells[nCellIndex]);
        if (rCell.mnCharacterIndex+rCell.mnCharacterCount > aLine.mnLineEndCharacterIndex)
            break;
        nWidth += rCell.mnCellWidth;
    }
    aLine.mnLineEndCellIndex = nCellIndex;
    aLine.mnWidth = nWidth;

    maLines.push_back(aLine);

    rCurrentLine.startPos = rCurrentLine.endPos;
}

double PresenterTextParagraph::GetTotalTextHeight() const
{
    return maLines.size() * mnLineHeight;
}

void PresenterTextParagraph::SetCharacterOffset (const sal_Int32 nCharacterOffset)
{
    mnCharacterOffset = nCharacterOffset;
}

sal_Int32 PresenterTextParagraph::GetCharacterCount() const
{
    return msParagraphText.getLength();
}

sal_Unicode PresenterTextParagraph::GetCharacter (
    const sal_Int32 nGlobalCharacterIndex) const
{
    if (nGlobalCharacterIndex<mnCharacterOffset
        || nGlobalCharacterIndex>=mnCharacterOffset+msParagraphText.getLength())
    {
        return sal_Unicode();
    }
    else
    {
        return msParagraphText[nGlobalCharacterIndex - mnCharacterOffset];
    }
}

const OUString& PresenterTextParagraph::GetText() const
{
    return msParagraphText;
}

TextSegment PresenterTextParagraph::GetTextSegment (
    const sal_Int32 nOffset,
    const sal_Int32 nIndex,
    const sal_Int16 nTextType) const
{
    switch(nTextType)
    {
        case AccessibleTextType::PARAGRAPH:
            return TextSegment(
                msParagraphText,
                mnCharacterOffset,
                mnCharacterOffset+msParagraphText.getLength());

        case AccessibleTextType::SENTENCE:
            if (mxBreakIterator.is())
            {
                const sal_Int32 nStart (mxBreakIterator->beginOfSentence(
                    msParagraphText, nIndex-mnCharacterOffset, lang::Locale()));
                const sal_Int32 nEnd (mxBreakIterator->endOfSentence(
                    msParagraphText, nIndex-mnCharacterOffset, lang::Locale()));
                if (nStart < nEnd)
                    return TextSegment(
                        msParagraphText.copy(nStart, nEnd-nStart),
                        nStart+mnCharacterOffset,
                        nEnd+mnCharacterOffset);
            }
            break;

        case AccessibleTextType::WORD:
            if (mxBreakIterator.is())
                return GetWordTextSegment(nOffset, nIndex);
            break;

        case AccessibleTextType::LINE:
        {
            auto iLine = std::find_if(maLines.begin(), maLines.end(),
                [nIndex](const Line& rLine) { return nIndex < rLine.mnLineEndCharacterIndex; });
            if (iLine != maLines.end())
            {
                return TextSegment(
                    msParagraphText.copy(
                        iLine->mnLineStartCharacterIndex,
                        iLine->mnLineEndCharacterIndex - iLine->mnLineStartCharacterIndex),
                    iLine->mnLineStartCharacterIndex,
                    iLine->mnLineEndCharacterIndex);
            }
        }
        break;

        // Handle GLYPH and ATTRIBUTE_RUN like CHARACTER because we can not
        // do better at the moment.
        case AccessibleTextType::CHARACTER:
        case AccessibleTextType::GLYPH:
        case AccessibleTextType::ATTRIBUTE_RUN:
        {
            const sal_Int32 nStartIndex = nIndex + nOffset;
            const sal_Int32 nEndIndex = nStartIndex < GetCharacterCount() ? nStartIndex + 1 : nStartIndex;
            return CreateTextSegment(nStartIndex, nEndIndex);
        }
    }

    return TextSegment(OUString(), 0,0);
}

TextSegment PresenterTextParagraph::GetWordTextSegment (
    const sal_Int32 nOffset,
    const sal_Int32 nIndex) const
{
    sal_Int32 nCurrentOffset (nOffset);
    sal_Int32 nCurrentIndex (nIndex);

    i18n::Boundary aWordBoundary;
    if (nCurrentOffset == 0)
        aWordBoundary = mxBreakIterator->getWordBoundary(
            msParagraphText,
            nIndex,
            lang::Locale(),
            i18n::WordType::ANYWORD_IGNOREWHITESPACES,
            true);
    else if (nCurrentOffset < 0)
    {
        while (nCurrentOffset<0 && nCurrentIndex>0)
        {
            aWordBoundary = mxBreakIterator->previousWord(
                msParagraphText,
                nCurrentIndex,
                lang::Locale(),
                i18n::WordType::ANYWORD_IGNOREWHITESPACES);
            nCurrentIndex = aWordBoundary.startPos;
            ++nCurrentOffset;
        }
    }
    else
    {
        while (nCurrentOffset>0 && nCurrentIndex<=GetCharacterCount())
        {
            aWordBoundary = mxBreakIterator->nextWord(
                msParagraphText,
                nCurrentIndex,
                lang::Locale(),
                i18n::WordType::ANYWORD_IGNOREWHITESPACES);
            nCurrentIndex = aWordBoundary.endPos;
            --nCurrentOffset;
        }
    }

    return CreateTextSegment(aWordBoundary.startPos, aWordBoundary.endPos);
}

TextSegment PresenterTextParagraph::CreateTextSegment (
    sal_Int32 nStartIndex,
    sal_Int32 nEndIndex) const
{
    if (nEndIndex <= nStartIndex)
        return TextSegment(
            OUString(),
            nStartIndex,
            nEndIndex);
    else
        return TextSegment(
            msParagraphText.copy(nStartIndex, nEndIndex-nStartIndex),
            nStartIndex,
            nEndIndex);
}

awt::Rectangle PresenterTextParagraph::GetCharacterBounds (
    sal_Int32 nGlobalCharacterIndex,
    const bool bCaretBox)
{
    // Find the line that contains the requested character and accumulate
    // the previous line heights.
    double nX (mnXOrigin);
    double nY (mnYOrigin + mnVerticalOffset + mnAscent);
    const sal_Int8 nTextDirection (GetTextDirection());
    for (sal_Int32 nLineIndex=0,nLineCount=maLines.size();
         nLineIndex<nLineCount;
         ++nLineIndex, nY+=mnLineHeight)
    {
        Line& rLine (maLines[nLineIndex]);
        // Skip lines before the indexed character.
        if (nGlobalCharacterIndex >= rLine.mnLineEndCharacterIndex)
            // When in the last line then allow the index past the last char.
            if (nLineIndex<nLineCount-1)
                continue;

        rLine.ProvideCellBoxes();

        const sal_Int32 nCellIndex (nGlobalCharacterIndex - rLine.mnLineStartCharacterIndex);

        // The cell bounding box is defined relative to the origin of
        // the current line.  Therefore we have to add the absolute
        // position of the line.
        geometry::RealRectangle2D rCellBox (rLine.maCellBoxes[
            ::std::min(nCellIndex, rLine.maCellBoxes.getLength()-1)]);

        double nLeft = nX + rCellBox.X1;
        double nRight = nX + rCellBox.X2;
        if (nTextDirection == rendering::TextDirection::WEAK_RIGHT_TO_LEFT)
        {
            const double nOldRight (nRight);
            nRight = rLine.mnWidth - nLeft;
            nLeft = rLine.mnWidth - nOldRight;
        }
        double nTop = nY - mnAscent;
        double nBottom;
        if (bCaretBox)
        {
            nBottom = nTop + mnLineHeight;
            if (nCellIndex >= rLine.maCellBoxes.getLength())
                nLeft = nRight-2;
            if (nLeft < nX)
                nLeft = nX;
            nRight = nLeft+2;
        }
        else
        {
            nBottom = nTop + mnAscent + mnDescent;
        }
        const sal_Int32 nX1 = sal_Int32(floor(nLeft));
        const sal_Int32 nY1 = sal_Int32(floor(nTop));
        const sal_Int32 nX2 = sal_Int32(ceil(nRight));
        const sal_Int32 nY2 = sal_Int32(ceil(nBottom));

        return awt::Rectangle(nX1,nY1,nX2-nX1+1,nY2-nY1+1);
    }

    // We are still here.  That means that the given index lies past the
    // last character in the paragraph.
    // Return an empty box that lies past the last character.  Better than nothing.
    return awt::Rectangle(sal_Int32(nX+0.5), sal_Int32(nY+0.5), 0, 0);
}

sal_Int8 PresenterTextParagraph::GetTextDirection() const
{
    // Find first portion that has a non-neutral text direction.
    sal_Int32 nPosition (0);
    sal_Int32 nTextLength (msParagraphText.getLength());
    while (nPosition < nTextLength)
    {
        const sal_Int16 nScriptDirection (
            mxScriptTypeDetector->getScriptDirection(
                msParagraphText, nPosition, i18n::ScriptDirection::NEUTRAL));
        switch (nScriptDirection)
        {
            case i18n::ScriptDirection::NEUTRAL:
                // continue looping.
                break;
            case i18n::ScriptDirection::LEFT_TO_RIGHT:
                return rendering::TextDirection::WEAK_LEFT_TO_RIGHT;

            case i18n::ScriptDirection::RIGHT_TO_LEFT:
                return rendering::TextDirection::WEAK_RIGHT_TO_LEFT;
        }

        nPosition = mxScriptTypeDetector->endOfScriptDirection(
            msParagraphText, nPosition, nScriptDirection);
    }

    // All text in paragraph is neutral.  Fall back on writing mode taken
    // from the XText (which may not be properly initialized.)
    sal_Int8 nTextDirection(rendering::TextDirection::WEAK_LEFT_TO_RIGHT);
    switch(mnWritingMode)
    {
        case text::WritingMode2::LR_TB:
            nTextDirection = rendering::TextDirection::WEAK_LEFT_TO_RIGHT;
            break;

        case text::WritingMode2::RL_TB:
            nTextDirection = rendering::TextDirection::WEAK_RIGHT_TO_LEFT;
            break;

        default:
        case text::WritingMode2::TB_RL:
        case text::WritingMode2::TB_LR:
            // Can not handle this.  Use default and hope for the best.
            break;
    }
    return nTextDirection;
}

bool PresenterTextParagraph::IsTextReferencePointLeft() const
{
    return mnWritingMode != text::WritingMode2::RL_TB;
}

void PresenterTextParagraph::SetupCellArray (
    const PresenterTheme::SharedFontDescriptor& rpFont)
{
    maCells.clear();

    if ( ! rpFont || ! rpFont->mxFont.is())
        return;

    sal_Int32 nPosition (0);
    sal_Int32 nIndex (0);
    const sal_Int32 nTextLength (msParagraphText.getLength());
    const sal_Int8 nTextDirection (GetTextDirection());
    while (nPosition < nTextLength)
    {
        const sal_Int32 nNewPosition (mxBreakIterator->nextCharacters(
            msParagraphText,
            nPosition,
            lang::Locale(),
            i18n::CharacterIteratorMode::SKIPCELL,
            1,
            nIndex));

        rendering::StringContext aContext (msParagraphText, nPosition, nNewPosition-nPosition);
        Reference<rendering::XTextLayout> xLayout (
            rpFont->mxFont->createTextLayout(aContext, nTextDirection, 0));
        css::geometry::RealRectangle2D aCharacterBox (xLayout->queryTextBounds());

        maCells.emplace_back(
            nPosition,
            nNewPosition-nPosition,
            aCharacterBox.X2-aCharacterBox.X1);

        nPosition = nNewPosition;
    }
}

//===== PresenterTextCaret ================================================----

PresenterTextCaret::PresenterTextCaret (
        uno::Reference<uno::XComponentContext> const& xContext,
    ::std::function<css::awt::Rectangle (const sal_Int32,const sal_Int32)> aCharacterBoundsAccess,
    ::std::function<void (const css::awt::Rectangle&)> aInvalidator)
    : m_xContext(xContext)
    , mnParagraphIndex(-1),
      mnCharacterIndex(-1),
      mnCaretBlinkTaskId(0),
      mbIsCaretVisible(false),
      maCharacterBoundsAccess(std::move(aCharacterBoundsAccess)),
      maInvalidator(std::move(aInvalidator))
{
}

PresenterTextCaret::~PresenterTextCaret()
{
    try
    {
        HideCaret();
    }
    catch (uno::Exception const&)
    {
        TOOLS_WARN_EXCEPTION("sdext.presenter""unexpected exception in ~PresenterTextCaret");
    }
}

void PresenterTextCaret::ShowCaret()
{
    if (mnCaretBlinkTaskId == 0)
    {
        mnCaretBlinkTaskId = PresenterTimer::ScheduleRepeatedTask (
            m_xContext,
            [this] (TimeValue const&) { return this->InvertCaret(); },
            CaretBlinkInterval,
            CaretBlinkInterval);
    }
    mbIsCaretVisible = true;
}

void PresenterTextCaret::HideCaret()
{
    if (mnCaretBlinkTaskId != 0)
    {
        PresenterTimer::CancelTask(mnCaretBlinkTaskId);
        mnCaretBlinkTaskId = 0;
    }
    mbIsCaretVisible = false;
    // Reset the caret position.
    mnParagraphIndex = -1;
    mnCharacterIndex = -1;
}


void PresenterTextCaret::SetPosition (
    const sal_Int32 nParagraphIndex,
    const sal_Int32 nCharacterIndex)
{
    if (mnParagraphIndex == nParagraphIndex
        && mnCharacterIndex == nCharacterIndex)
        return;

    if (mnParagraphIndex >= 0)
        maInvalidator(maCaretBounds);

    const sal_Int32 nOldParagraphIndex (mnParagraphIndex);
    const sal_Int32 nOldCharacterIndex (mnCharacterIndex);
    mnParagraphIndex = nParagraphIndex;
    mnCharacterIndex = nCharacterIndex;
    maCaretBounds = maCharacterBoundsAccess(mnParagraphIndex, mnCharacterIndex);
    if (mnParagraphIndex >= 0)
        ShowCaret();
    else
        HideCaret();

    if (mnParagraphIndex >= 0)
        maInvalidator(maCaretBounds);

    if (maBroadcaster)
        maBroadcaster(
            nOldParagraphIndex,
            nOldCharacterIndex,
            mnParagraphIndex,
            mnCharacterIndex);
}


void PresenterTextCaret::SetCaretMotionBroadcaster (
    const ::std::function<void (sal_Int32,sal_Int32,sal_Int32,sal_Int32)>& rBroadcaster)
{
    maBroadcaster = rBroadcaster;
}

const css::awt::Rectangle& PresenterTextCaret::GetBounds() const
{
    return maCaretBounds;
}

void PresenterTextCaret::InvertCaret()
{
    mbIsCaretVisible = !mbIsCaretVisible;
    if (mnParagraphIndex >= 0)
        maInvalidator(maCaretBounds);
}

//===== PresenterTextParagraph::Cell ==========================================

PresenterTextParagraph::Cell::Cell (
    const sal_Int32 nCharacterIndex,
    const sal_Int32 nCharacterCount,
    const double nCellWidth)
    : mnCharacterIndex(nCharacterIndex),
      mnCharacterCount(nCharacterCount),
      mnCellWidth(nCellWidth)
{
}

//===== PresenterTextParagraph::Line ==========================================

PresenterTextParagraph::Line::Line (
    const sal_Int32 nLineStartCharacterIndex,
    const sal_Int32 nLineEndCharacterIndex)
    : mnLineStartCharacterIndex(nLineStartCharacterIndex),
      mnLineEndCharacterIndex(nLineEndCharacterIndex),
      mnLineStartCellIndex(-1), mnLineEndCellIndex(-1),
      mnBaseLine(0), mnWidth(0)
{
}

void PresenterTextParagraph::Line::ProvideCellBoxes()
{
    if ( mnLineStartCharacterIndex < mnLineEndCharacterIndex && !maCellBoxes.hasElements() )
    {
        if (mxLayoutedLine.is())
            maCellBoxes = mxLayoutedLine->queryInkMeasures();
        else
        {
            OSL_ASSERT(mxLayoutedLine.is());
        }
    }
}

void PresenterTextParagraph::Line::ProvideLayoutedLine (
    const OUString& rsParagraphText,
    const PresenterTheme::SharedFontDescriptor& rpFont,
    const sal_Int8 nTextDirection)
{
    if ( ! mxLayoutedLine.is())
    {
        const rendering::StringContext aContext (
            rsParagraphText,
            mnLineStartCharacterIndex,
            mnLineEndCharacterIndex - mnLineStartCharacterIndex);

        mxLayoutedLine = rpFont->mxFont->createTextLayout(
            aContext,
            nTextDirection,
            0);
    }
}

// end of namespace ::sdext::presenter

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

Messung V0.5
C=94 H=96 G=94

¤ Dauer der Verarbeitung: 0.16 Sekunden  (vorverarbeitet)  ¤

*© 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.