blob: b700340beab2a28ebf5eabbe4e3f58d81c6ec135 [file] [log] [blame]
/**************************************************************
*
* 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the 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