diff options
Diffstat (limited to 'sdext/source/presenter/PresenterTextView.cxx')
-rw-r--r-- | sdext/source/presenter/PresenterTextView.cxx | 1599 |
1 files changed, 1599 insertions, 0 deletions
diff --git a/sdext/source/presenter/PresenterTextView.cxx b/sdext/source/presenter/PresenterTextView.cxx new file mode 100644 index 000000000000..2bb334b376f3 --- /dev/null +++ b/sdext/source/presenter/PresenterTextView.cxx @@ -0,0 +1,1599 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2008 by Sun Microsystems, Inc. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * $RCSfile: PresenterNotesView.hxx,v $ + * + * $Revision: 1.5 $ + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +#include "precompiled_sdext.hxx" + +#include "PresenterTextView.hxx" +#include "PresenterCanvasHelper.hxx" +#include "PresenterGeometryHelper.hxx" +#include "PresenterTimer.hxx" + +#include <cmath> + +#include <com/sun/star/accessibility/AccessibleTextType.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/i18n/CharType.hpp> +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <com/sun/star/i18n/CTLScriptType.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 <boost/bind.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; + +#define A2S(pString) (::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM(pString))) + +const static sal_Int64 CaretBlinkIntervall = 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 { namespace presenter { + + +//===== PresenterTextView ===================================================== + +PresenterTextView::PresenterTextView ( + const Reference<XComponentContext>& rxContext, + const Reference<rendering::XCanvas>& rxCanvas, + const ::boost::function<void(const ::css::awt::Rectangle&)>& rInvalidator) + : mxCanvas(rxCanvas), + mbDoOuput(true), + mxBreakIterator(), + mxScriptTypeDetector(), + maLocation(0,0), + maSize(0,0), + mpFont(), + maParagraphs(), + mpCaret(new PresenterTextCaret( + ::boost::bind(&PresenterTextView::GetCaretBounds, this, _1, _2), + rInvalidator)), + mnLeftOffset(0), + mnTopOffset(0), + maInvalidator(rInvalidator), + mbIsFormatPending(false), + mnCharacterCount(-1), + maTextChangeBroadcaster() +{ + Reference<lang::XMultiComponentFactory> xFactory ( + rxContext->getServiceManager(), UNO_QUERY); + if ( ! xFactory.is()) + return; + + // Create the break iterator that we use to break text into lines. + mxBreakIterator = Reference<i18n::XBreakIterator>( + xFactory->createInstanceWithContext( + A2S("com.sun.star.i18n.BreakIterator"), + rxContext), + UNO_QUERY_THROW); + + // Create the script type detector that is used to split paragraphs into + // portions of the same text direction. + mxScriptTypeDetector = Reference<i18n::XScriptTypeDetector>( + xFactory->createInstanceWithContext( + A2S("com.sun.star.i18n.ScriptTypeDetector"), + rxContext), + UNO_QUERY_THROW); +} + + + + +PresenterTextView::PresenterTextView ( + const Reference<XComponentContext>& rxContext, + const Reference<rendering::XCanvas>& rxCanvas) + : mxCanvas(rxCanvas), + mbDoOuput(false), + mxBreakIterator(), + mxScriptTypeDetector(), + maLocation(0,0), + maSize(0,0), + mpFont(), + maParagraphs(), + mpCaret(new PresenterTextCaret( + ::boost::bind(&PresenterTextView::GetCaretBounds, this, _1, _2), + ::boost::function<void(const css::awt::Rectangle&)>())), + mnLeftOffset(0), + mnTopOffset(0), + maInvalidator(), + mbIsFormatPending(false), + mnCharacterCount(-1), + maTextChangeBroadcaster() +{ + Reference<lang::XMultiComponentFactory> xFactory ( + rxContext->getServiceManager(), UNO_QUERY); + if ( ! xFactory.is()) + return; + + // Create the break iterator that we use to break text into lines. + mxBreakIterator = Reference<i18n::XBreakIterator>( + xFactory->createInstanceWithContext( + A2S("com.sun.star.i18n.BreakIterator"), + rxContext), + UNO_QUERY_THROW); + + // Create the script type detector that is used to split paragraphs into + // portions of the same text direction. + mxScriptTypeDetector = Reference<i18n::XScriptTypeDetector>( + xFactory->createInstanceWithContext( + A2S("com.sun.star.i18n.ScriptTypeDetector"), + rxContext), + UNO_QUERY_THROW); +} + + + + +void PresenterTextView::SetText (const Reference<text::XText>& rxText) +{ + maParagraphs.clear(); + mnCharacterCount = -1; + + Reference<container::XEnumerationAccess> xParagraphAccess (rxText, UNO_QUERY); + if ( ! xParagraphAccess.is()) + return; + + Reference<container::XEnumeration> xParagraphs ( + xParagraphAccess->createEnumeration() , UNO_QUERY); + if ( ! xParagraphs.is()) + return; + + if ( ! mpFont || ! mpFont->PrepareFont(mxCanvas)) + return; + + sal_Int32 nCharacterCount (0); + while (xParagraphs->hasMoreElements()) + { + SharedPresenterTextParagraph pParagraph (new 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::SetText (const ::rtl::OUString& rsText) +{ + maParagraphs.clear(); + mnCharacterCount = -1; + + if ( ! mpFont || ! mpFont->PrepareFont(mxCanvas)) + return; + + sal_Int32 nCharacterCount (0); + + SharedPresenterTextParagraph pParagraph (new PresenterTextParagraph( + 0, + mxBreakIterator, + mxScriptTypeDetector, + rsText, + mpCaret)); + pParagraph->SetupCellArray(mpFont); + pParagraph->SetCharacterOffset(nCharacterCount); + nCharacterCount += pParagraph->GetCharacterCount(); + maParagraphs.push_back(pParagraph); + + if (mpCaret) + mpCaret->HideCaret(); + + RequestFormat(); +} + + + + +void PresenterTextView::SetTextChangeBroadcaster ( + const ::boost::function<void(void)>& rBroadcaster) +{ + maTextChangeBroadcaster = rBroadcaster; +} + + + + +void PresenterTextView::SetLocation (const css::geometry::RealPoint2D& rLocation) +{ + maLocation = rLocation; + + for (::std::vector<SharedPresenterTextParagraph>::iterator + iParagraph(maParagraphs.begin()), + iEnd(maParagraphs.end()); + iParagraph!=iEnd; + ++iParagraph) + { + (*iParagraph)->SetOrigin( + maLocation.X - mnLeftOffset, + maLocation.Y - mnTopOffset); + } +} + + + + +void PresenterTextView::SetSize (const css::geometry::RealSize2D& rSize) +{ + maSize = rSize; + RequestFormat(); +} + + + + +double PresenterTextView::GetTotalTextHeight (void) +{ + double nTotalHeight (0); + + if (mbIsFormatPending) + { + if ( ! mpFont->PrepareFont(mxCanvas)) + return 0; + Format(); + } + + for (::std::vector<SharedPresenterTextParagraph>::iterator + iParagraph(maParagraphs.begin()), + iEnd(maParagraphs.end()); + iParagraph!=iEnd; + ++iParagraph) + { + nTotalHeight += (*iParagraph)->GetTotalTextHeight(); + } + + return nTotalHeight; +} + + + + +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 (sal_uInt32(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 ( ! mbDoOuput) + return; + 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), + NULL, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor); + + for (::std::vector<SharedPresenterTextParagraph>::const_iterator + iParagraph(maParagraphs.begin()), + iEnd(maParagraphs.end()); + iParagraph!=iEnd; + ++iParagraph) + { + (*iParagraph)->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); + } +} + + + + +SharedPresenterTextCaret PresenterTextView::GetCaret (void) const +{ + return mpCaret; +} + + + + +sal_Int32 PresenterTextView::GetCharacterOffset (const sal_Int32 nParagraphIndex) const +{ + sal_Int32 nCharacterOffset (0); + for (sal_Int32 nIndex=0; nIndex<nParagraphIndex; ++nIndex) + nCharacterOffset += maParagraphs[nIndex]->GetCharacterCount(); + return nCharacterOffset; +} + + + + +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 (void) +{ + mbIsFormatPending = true; +} + + + + +void PresenterTextView::Format (void) +{ + mbIsFormatPending = false; + + double nY (0); + for (::std::vector<SharedPresenterTextParagraph>::const_iterator + iParagraph(maParagraphs.begin()), + iEnd(maParagraphs.end()); + iParagraph!=iEnd; + ++iParagraph) + { + (*iParagraph)->Format(nY, maSize.Width, mpFont); + nY += (*iParagraph)->GetTotalTextHeight(); + } + + if (maTextChangeBroadcaster) + maTextChangeBroadcaster(); +} + + + + +sal_Int32 PresenterTextView::GetParagraphCount (void) const +{ + return maParagraphs.size(); +} + + + + +SharedPresenterTextParagraph PresenterTextView::GetParagraph ( + const sal_Int32 nParagraphIndex) const +{ + if (nParagraphIndex < 0) + return SharedPresenterTextParagraph(); + else if (nParagraphIndex>=sal_Int32(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, + const SharedPresenterTextCaret& rpCaret) + : msParagraphText(), + mnParagraphIndex(nParagraphIndex), + mpCaret(rpCaret), + mxBreakIterator(rxBreakIterator), + mxScriptTypeDetector(rxScriptTypeDetector), + maLines(), + mnVerticalOffset(0), + mnXOrigin(0), + mnYOrigin(0), + mnWidth(0), + mnAscent(0), + mnDescent(0), + mnLineHeight(-1), + meAdjust(style::ParagraphAdjust_LEFT), + mnWritingMode (text::WritingMode2::LR_TB), + mnCharacterOffset(0), + maCells() +{ + if (rxTextRange.is()) + { + Reference<beans::XPropertySet> xProperties (rxTextRange, UNO_QUERY); + lang::Locale aLocale; + try + { + xProperties->getPropertyValue(A2S("CharLocale")) >>= aLocale; + } + catch(beans::UnknownPropertyException&) + { + // Ignore the exception. Use the default value. + } + try + { + xProperties->getPropertyValue(A2S("ParaAdjust")) >>= meAdjust; + } + catch(beans::UnknownPropertyException&) + { + // Ignore the exception. Use the default value. + } + try + { + xProperties->getPropertyValue(A2S("WritingMode")) >>= mnWritingMode; + } + catch(beans::UnknownPropertyException&) + { + // Ignore the exception. Use the default value. + } + + msParagraphText = rxTextRange->getString(); + } +} + + + + +PresenterTextParagraph::PresenterTextParagraph ( + const sal_Int32 nParagraphIndex, + const Reference<i18n::XBreakIterator>& rxBreakIterator, + const Reference<i18n::XScriptTypeDetector>& rxScriptTypeDetector, + const ::rtl::OUString& rsText, + const SharedPresenterTextCaret& rpCaret) + : msParagraphText(rsText), + mnParagraphIndex(nParagraphIndex), + mpCaret(rpCaret), + mxBreakIterator(rxBreakIterator), + mxScriptTypeDetector(rxScriptTypeDetector), + maLines(), + mnVerticalOffset(0), + mnXOrigin(0), + mnYOrigin(0), + mnWidth(0), + mnAscent(0), + mnDescent(0), + mnLineHeight(-1), + meAdjust(style::ParagraphAdjust_LEFT), + mnWritingMode (text::WritingMode2::LR_TB), + mnCharacterOffset(0), + maCells() +{ +} + + + + +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 (sal_uInt32(nIndex)>=maWordBoundaries.size()) + return -1; + else + return maWordBoundaries[nIndex]; +} + + + + +sal_Int32 PresenterTextParagraph::GetCaretPosition (void) 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 (void) const +{ + return awt::Point( + sal_Int32(mnXOrigin), + sal_Int32(mnYOrigin + mnVerticalOffset)); +} + + + + +awt::Size PresenterTextParagraph::GetSize (void) +{ + 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); + sal_Int32 nLineEnd (0); + if ( ! maLines.empty()) + { + nLineStart = rCurrentLine.startPos; + nLineEnd = rCurrentLine.endPos; + } + + const ::rtl::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.size() > 0) + { + 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; +} + + + + +sal_Int32 PresenterTextParagraph::GetParagraphIndex (void) const +{ + return mnParagraphIndex; +} + + + + +double PresenterTextParagraph::GetTotalTextHeight (void) +{ + return maLines.size() * mnLineHeight; +} + + + + +sal_Int32 PresenterTextParagraph::GetCharacterOffset (void) const +{ + return mnCharacterOffset; +} + + + + +void PresenterTextParagraph::SetCharacterOffset (const sal_Int32 nCharacterOffset) +{ + mnCharacterOffset = nCharacterOffset; +} + + + + +sal_Int32 PresenterTextParagraph::GetCharacterCount (void) 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.getStr()[nGlobalCharacterIndex - mnCharacterOffset]; + } +} + + + + +::rtl::OUString PresenterTextParagraph::GetText (void) 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: + { + for (::std::vector<Line>::const_iterator + iLine(maLines.begin()), + iEnd(maLines.end()); + iLine!=iEnd; + ++iLine) + { + if (nIndex < iLine->mnLineEndCharacterIndex) + { + 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: + return CreateTextSegment(nIndex+nOffset, nIndex+nOffset+1); + } + + return TextSegment(::rtl::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, + sal_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( + ::rtl::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. + sal_Int32 nFirstCharacterIndex (0); + sal_Int32 nEndCharacterIndex (0); + double nX (mnXOrigin); + double nY (mnYOrigin + mnVerticalOffset + mnAscent); + const sal_Int8 nTextDirection (GetTextDirection()); + for (sal_Int32 nLineIndex=0,nLineCount=maLines.size(); + nLineIndex<nLineCount; + ++nLineIndex, nFirstCharacterIndex=nEndCharacterIndex, 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 + rCellBox.Y1); + double nBottom (nY + rCellBox.Y2); + if (bCaretBox) + { + nTop = nTop - rCellBox.Y1 - mnAscent; + nBottom = nTop + mnLineHeight; + if (nCellIndex >= rLine.maCellBoxes.getLength()) + nLeft = nRight-2; + if (nLeft < nX) + nLeft = nX; + nRight = nLeft+2; + } + else + { + nTop = nTop - rCellBox.Y1 - mnAscent; + 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_Int32 PresenterTextParagraph::GetIndexAtPoint (const awt::Point& rPoint) const +{ + (void)rPoint; + return -1; +} + + + + +sal_Int8 PresenterTextParagraph::GetTextDirection (void) 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 (void) 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.push_back(Cell( + nPosition, + nNewPosition-nPosition, + aCharacterBox.X2-aCharacterBox.X1)); + + nPosition = nNewPosition; + } +} + + + + +//===== PresenterTextCaret ================================================---- + +PresenterTextCaret::PresenterTextCaret ( + const ::boost::function<css::awt::Rectangle(const sal_Int32,const sal_Int32)>& rCharacterBoundsAccess, + const ::boost::function<void(const css::awt::Rectangle&)>& rInvalidator) + : mnParagraphIndex(-1), + mnCharacterIndex(-1), + mnCaretBlinkTaskId(0), + mbIsCaretVisible(false), + maCharacterBoundsAccess(rCharacterBoundsAccess), + maInvalidator(rInvalidator), + maBroadcaster(), + maCaretBounds() +{ +} + + + + +PresenterTextCaret::~PresenterTextCaret (void) +{ + HideCaret(); +} + + + + +void PresenterTextCaret::ShowCaret (void) +{ + if (mnCaretBlinkTaskId == 0) + { + mnCaretBlinkTaskId = PresenterTimer::ScheduleRepeatedTask ( + ::boost::bind(&PresenterTextCaret::InvertCaret, this), + CaretBlinkIntervall, + CaretBlinkIntervall); + } + mbIsCaretVisible = true; +} + + + + +void PresenterTextCaret::HideCaret (void) +{ + if (mnCaretBlinkTaskId != 0) + { + PresenterTimer::CancelTask(mnCaretBlinkTaskId); + mnCaretBlinkTaskId = 0; + } + mbIsCaretVisible = false; + // Reset the caret position. + mnParagraphIndex = -1; + mnCharacterIndex = -1; +} + + + + +sal_Int32 PresenterTextCaret::GetParagraphIndex (void) const +{ + return mnParagraphIndex; +} + + + + +sal_Int32 PresenterTextCaret::GetCharacterIndex (void) const +{ + return mnCharacterIndex; +} + + + + +void PresenterTextCaret::SetPosition ( + const sal_Int32 nParagraphIndex, + const sal_Int32 nCharacterIndex) +{ + if (mnParagraphIndex != nParagraphIndex + || mnCharacterIndex != nCharacterIndex) + { + 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); + + } +} + + + + +bool PresenterTextCaret::IsVisible (void) const +{ + return mbIsCaretVisible; +} + + + + +void PresenterTextCaret::SetCaretMotionBroadcaster ( + const ::boost::function<void(sal_Int32,sal_Int32,sal_Int32,sal_Int32)>& rBroadcaster) +{ + maBroadcaster = rBroadcaster; +} + + + + +css::awt::Rectangle PresenterTextCaret::GetBounds (void) const +{ + return maCaretBounds; +} + + + + +void PresenterTextCaret::InvertCaret (void) +{ + 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), + mxLayoutedLine(), + mnBaseLine(0), mnWidth(0), + maCellBoxes() +{ +} + + + + +sal_Int32 PresenterTextParagraph::Line::GetLength (void) const +{ + return mnLineEndCharacterIndex-mnLineStartCharacterIndex; +} + + + + +void PresenterTextParagraph::Line::ProvideCellBoxes (void) +{ + if ( ! IsEmpty() && maCellBoxes.getLength()==0) + { + if (mxLayoutedLine.is()) + maCellBoxes = mxLayoutedLine->queryInkMeasures(); + else + { + OSL_ASSERT(mxLayoutedLine.is()); + } + } +} + + + + +void PresenterTextParagraph::Line::ProvideLayoutedLine ( + const ::rtl::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); + } +} + + + + +bool PresenterTextParagraph::Line::IsEmpty (void) const +{ + return mnLineStartCharacterIndex >= mnLineEndCharacterIndex; +} + + + + +} } // end of namespace ::sdext::presenter |