1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "PresenterTextView.hxx"
21 #include "PresenterCanvasHelper.hxx"
22 #include "PresenterGeometryHelper.hxx"
23 #include "PresenterTimer.hxx"
29 #include <com/sun/star/accessibility/AccessibleTextType.hpp>
30 #include <com/sun/star/container/XEnumerationAccess.hpp>
31 #include <com/sun/star/i18n/BreakIterator.hpp>
32 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
33 #include <com/sun/star/i18n/ScriptDirection.hpp>
34 #include <com/sun/star/i18n/WordType.hpp>
35 #include <com/sun/star/rendering/CompositeOperation.hpp>
36 #include <com/sun/star/rendering/TextDirection.hpp>
37 #include <com/sun/star/text/WritingMode2.hpp>
38 #include <tools/diagnose_ex.h>
40 using namespace ::com::sun::star
;
41 using namespace ::com::sun::star::accessibility
;
42 using namespace ::com::sun::star::uno
;
44 const static sal_Int64 CaretBlinkInterval
= 500 * 1000 * 1000;
46 //#define SHOW_CHARACTER_BOXES
49 sal_Int32
Signum (const sal_Int32 nValue
)
60 namespace sdext
{ namespace presenter
{
62 //===== PresenterTextView =====================================================
64 PresenterTextView::PresenterTextView (
65 const Reference
<XComponentContext
>& rxContext
,
66 const Reference
<rendering::XCanvas
>& rxCanvas
,
67 const ::std::function
<void (const css::awt::Rectangle
&)>& rInvalidator
)
70 mxScriptTypeDetector(),
75 mpCaret(new PresenterTextCaret(
77 [this] (sal_Int32
const nParagraphIndex
, sal_Int32
const nCharacterIndex
)
78 { return this->GetCaretBounds(nParagraphIndex
, nCharacterIndex
); },
82 mbIsFormatPending(false),
83 maTextChangeBroadcaster()
85 Reference
<lang::XMultiComponentFactory
> xFactory
=
86 rxContext
->getServiceManager();
90 // Create the break iterator that we use to break text into lines.
91 mxBreakIterator
= i18n::BreakIterator::create(rxContext
);
93 // Create the script type detector that is used to split paragraphs into
94 // portions of the same text direction.
95 mxScriptTypeDetector
.set(
96 xFactory
->createInstanceWithContext(
97 "com.sun.star.i18n.ScriptTypeDetector",
102 void PresenterTextView::SetText (const Reference
<text::XText
>& rxText
)
104 maParagraphs
.clear();
106 Reference
<container::XEnumerationAccess
> xParagraphAccess (rxText
, UNO_QUERY
);
107 if ( ! xParagraphAccess
.is())
110 Reference
<container::XEnumeration
> xParagraphs
=
111 xParagraphAccess
->createEnumeration();
112 if ( ! xParagraphs
.is())
115 if ( ! mpFont
|| ! mpFont
->PrepareFont(mxCanvas
))
118 sal_Int32
nCharacterCount (0);
119 while (xParagraphs
->hasMoreElements())
121 SharedPresenterTextParagraph
pParagraph (new PresenterTextParagraph(
124 mxScriptTypeDetector
,
125 Reference
<text::XTextRange
>(xParagraphs
->nextElement(), UNO_QUERY
),
127 pParagraph
->SetupCellArray(mpFont
);
128 pParagraph
->SetCharacterOffset(nCharacterCount
);
129 nCharacterCount
+= pParagraph
->GetCharacterCount();
130 maParagraphs
.push_back(pParagraph
);
134 mpCaret
->HideCaret();
139 void PresenterTextView::SetTextChangeBroadcaster (
140 const ::std::function
<void ()>& rBroadcaster
)
142 maTextChangeBroadcaster
= rBroadcaster
;
145 void PresenterTextView::SetLocation (const css::geometry::RealPoint2D
& rLocation
)
147 maLocation
= rLocation
;
149 for (auto& rxParagraph
: maParagraphs
)
151 rxParagraph
->SetOrigin(
152 maLocation
.X
- mnLeftOffset
,
153 maLocation
.Y
- mnTopOffset
);
157 void PresenterTextView::SetSize (const css::geometry::RealSize2D
& rSize
)
163 double PresenterTextView::GetTotalTextHeight()
165 if (mbIsFormatPending
)
167 if ( ! mpFont
->PrepareFont(mxCanvas
))
172 return std::accumulate(maParagraphs
.begin(), maParagraphs
.end(), double(0),
173 [](const double& nTotalHeight
, const SharedPresenterTextParagraph
& rxParagraph
) {
174 return nTotalHeight
+ rxParagraph
->GetTotalTextHeight();
178 void PresenterTextView::SetFont (const PresenterTheme::SharedFontDescriptor
& rpFont
)
184 void PresenterTextView::SetOffset(
188 mnLeftOffset
= nLeft
;
191 // Trigger an update of the text origin stored at the individual paragraphs.
192 SetLocation(maLocation
);
195 void PresenterTextView::MoveCaret (
196 const sal_Int32 nDistance
,
197 const sal_Int16 nTextType
)
202 // When the caret has not been visible yet then move it to the beginning
204 if (mpCaret
->GetParagraphIndex() < 0)
206 mpCaret
->SetPosition(0,0);
210 sal_Int32
nParagraphIndex (mpCaret
->GetParagraphIndex());
211 sal_Int32
nCharacterIndex (mpCaret
->GetCharacterIndex());
215 case AccessibleTextType::CHARACTER
:
216 nCharacterIndex
+= nDistance
;
219 case AccessibleTextType::WORD
:
221 sal_Int32
nRemainingDistance (nDistance
);
222 while (nRemainingDistance
!= 0)
224 SharedPresenterTextParagraph
pParagraph (GetParagraph(nParagraphIndex
));
227 const sal_Int32
nDelta (Signum(nDistance
));
228 nCharacterIndex
= pParagraph
->GetWordBoundary(nCharacterIndex
, nDelta
);
229 if (nCharacterIndex
< 0)
231 // Go to previous or next paragraph.
232 nParagraphIndex
+= nDelta
;
233 if (nParagraphIndex
< 0)
237 nRemainingDistance
= 0;
239 else if (sal_uInt32(nParagraphIndex
) >= maParagraphs
.size())
241 nParagraphIndex
= maParagraphs
.size()-1;
242 pParagraph
= GetParagraph(nParagraphIndex
);
244 nCharacterIndex
= pParagraph
->GetCharacterCount();
245 nRemainingDistance
= 0;
249 nRemainingDistance
-= nDelta
;
251 // Move caret one character to the end of
252 // the previous or the start of the next paragraph.
253 pParagraph
= GetParagraph(nParagraphIndex
);
257 nCharacterIndex
= pParagraph
->GetCharacterCount();
264 nRemainingDistance
-= nDelta
;
273 // Move the caret to the new position.
274 mpCaret
->SetPosition(nParagraphIndex
, nCharacterIndex
);
277 void PresenterTextView::Paint (
278 const css::awt::Rectangle
& rUpdateBox
)
280 if ( ! mxCanvas
.is())
282 if ( ! mpFont
->PrepareFont(mxCanvas
))
285 if (mbIsFormatPending
)
288 // Setup the clipping rectangle. Horizontally we make it a little
289 // larger to allow characters (and the caret) to stick out of their
290 // bounding boxes. This can happen on some characters (like the
291 // uppercase J) for typographical reasons.
292 const sal_Int32
nAdditionalLeftBorder (10);
293 const sal_Int32
nAdditionalRightBorder (5);
294 double nX (maLocation
.X
- mnLeftOffset
);
295 double nY (maLocation
.Y
- mnTopOffset
);
296 const sal_Int32
nClipLeft (::std::max(
297 PresenterGeometryHelper::Round(maLocation
.X
)-nAdditionalLeftBorder
, rUpdateBox
.X
));
298 const sal_Int32
nClipTop (::std::max(
299 PresenterGeometryHelper::Round(maLocation
.Y
), rUpdateBox
.Y
));
300 const sal_Int32
nClipRight (::std::min(
301 PresenterGeometryHelper::Round(maLocation
.X
+maSize
.Width
)+nAdditionalRightBorder
, rUpdateBox
.X
+rUpdateBox
.Width
));
302 const sal_Int32
nClipBottom (::std::min(
303 PresenterGeometryHelper::Round(maLocation
.Y
+maSize
.Height
), rUpdateBox
.Y
+rUpdateBox
.Height
));
304 if (nClipLeft
>=nClipRight
|| nClipTop
>=nClipBottom
)
307 const awt::Rectangle
aClipBox(
310 nClipRight
- nClipLeft
,
311 nClipBottom
- nClipTop
);
312 Reference
<rendering::XPolyPolygon2D
> xClipPolygon (
313 PresenterGeometryHelper::CreatePolygon(aClipBox
, mxCanvas
->getDevice()));
315 const rendering::ViewState
aViewState(
316 geometry::AffineMatrix2D(1,0,0, 0,1,0),
319 rendering::RenderState
aRenderState (
320 geometry::AffineMatrix2D(1,0,nX
, 0,1,nY
),
323 rendering::CompositeOperation::SOURCE
);
324 PresenterCanvasHelper::SetDeviceColor(aRenderState
, mpFont
->mnColor
);
326 for (const auto& rxParagraph
: maParagraphs
)
339 aRenderState
.AffineTransform
.m02
= 0;
340 aRenderState
.AffineTransform
.m12
= 0;
342 #ifdef SHOW_CHARACTER_BOXES
343 PresenterCanvasHelper::SetDeviceColor(aRenderState
, 0x00808080);
344 for (sal_Int32
nParagraphIndex(0), nParagraphCount(GetParagraphCount());
345 nParagraphIndex
<nParagraphCount
;
348 const SharedPresenterTextParagraph
pParagraph (GetParagraph(nParagraphIndex
));
351 for (sal_Int32
nCharacterIndex(0),nCharacterCount(pParagraph
->GetCharacterCount());
352 nCharacterIndex
<nCharacterCount
; ++nCharacterIndex
)
354 const awt::Rectangle
aBox (pParagraph
->GetCharacterBounds(nCharacterIndex
, false));
355 mxCanvas
->drawPolyPolygon (
356 PresenterGeometryHelper::CreatePolygon(
358 mxCanvas
->getDevice()),
363 PresenterCanvasHelper::SetDeviceColor(aRenderState
, mpFont
->mnColor
);
366 if (mpCaret
&& mpCaret
->IsVisible())
368 mxCanvas
->fillPolyPolygon (
369 PresenterGeometryHelper::CreatePolygon(
370 mpCaret
->GetBounds(),
371 mxCanvas
->getDevice()),
377 const SharedPresenterTextCaret
& PresenterTextView::GetCaret() const
382 awt::Rectangle
PresenterTextView::GetCaretBounds (
383 sal_Int32 nParagraphIndex
,
384 const sal_Int32 nCharacterIndex
) const
386 SharedPresenterTextParagraph
pParagraph (GetParagraph(nParagraphIndex
));
389 return pParagraph
->GetCharacterBounds(nCharacterIndex
, true);
391 return awt::Rectangle(0,0,0,0);
394 //----- private ---------------------------------------------------------------
396 void PresenterTextView::RequestFormat()
398 mbIsFormatPending
= true;
401 void PresenterTextView::Format()
403 mbIsFormatPending
= false;
406 for (const auto& rxParagraph
: maParagraphs
)
408 rxParagraph
->Format(nY
, maSize
.Width
, mpFont
);
409 nY
+= rxParagraph
->GetTotalTextHeight();
412 if (maTextChangeBroadcaster
)
413 maTextChangeBroadcaster();
416 sal_Int32
PresenterTextView::GetParagraphCount() const
418 return maParagraphs
.size();
421 SharedPresenterTextParagraph
PresenterTextView::GetParagraph (
422 const sal_Int32 nParagraphIndex
) const
424 if (nParagraphIndex
< 0)
425 return SharedPresenterTextParagraph();
426 else if (nParagraphIndex
>=sal_Int32(maParagraphs
.size()))
427 return SharedPresenterTextParagraph();
429 return maParagraphs
[nParagraphIndex
];
432 //===== PresenterTextParagraph ================================================
434 PresenterTextParagraph::PresenterTextParagraph (
435 const sal_Int32 nParagraphIndex
,
436 const Reference
<i18n::XBreakIterator
>& rxBreakIterator
,
437 const Reference
<i18n::XScriptTypeDetector
>& rxScriptTypeDetector
,
438 const Reference
<text::XTextRange
>& rxTextRange
,
439 const SharedPresenterTextCaret
& rpCaret
)
441 mnParagraphIndex(nParagraphIndex
),
443 mxBreakIterator(rxBreakIterator
),
444 mxScriptTypeDetector(rxScriptTypeDetector
),
453 mnWritingMode (text::WritingMode2::LR_TB
),
454 mnCharacterOffset(0),
457 if (!rxTextRange
.is())
460 Reference
<beans::XPropertySet
> xProperties (rxTextRange
, UNO_QUERY
);
463 xProperties
->getPropertyValue("WritingMode") >>= mnWritingMode
;
465 catch(beans::UnknownPropertyException
&)
467 // Ignore the exception. Use the default value.
470 msParagraphText
= rxTextRange
->getString();
473 void PresenterTextParagraph::Paint (
474 const Reference
<rendering::XCanvas
>& rxCanvas
,
475 const geometry::RealSize2D
& rSize
,
476 const PresenterTheme::SharedFontDescriptor
& rpFont
,
477 const rendering::ViewState
& rViewState
,
478 rendering::RenderState
& rRenderState
,
479 const double nTopOffset
,
480 const double nClipTop
,
481 const double nClipBottom
)
483 if (mnLineHeight
<= 0)
486 sal_Int8
nTextDirection (GetTextDirection());
488 const double nSavedM12 (rRenderState
.AffineTransform
.m12
);
490 if ( ! IsTextReferencePointLeft())
491 rRenderState
.AffineTransform
.m02
+= rSize
.Width
;
493 #ifdef SHOW_CHARACTER_BOXES
494 for (sal_Int32 nIndex
=0,nCount
=maLines
.size();
498 Line
& rLine (maLines
[nIndex
]);
499 rLine
.ProvideLayoutedLine(msParagraphText
, rpFont
, nTextDirection
);
503 for (sal_Int32 nIndex
=0,nCount
=maLines
.size();
505 ++nIndex
, rRenderState
.AffineTransform
.m12
+= mnLineHeight
)
507 Line
& rLine (maLines
[nIndex
]);
509 // Paint only visible lines.
510 const double nLineTop
= rLine
.mnBaseLine
- mnAscent
- nTopOffset
;
511 if (nLineTop
+ mnLineHeight
< nClipTop
)
513 else if (nLineTop
> nClipBottom
)
515 rLine
.ProvideLayoutedLine(msParagraphText
, rpFont
, nTextDirection
);
517 rRenderState
.AffineTransform
.m12
= nSavedM12
+ rLine
.mnBaseLine
;
519 rxCanvas
->drawTextLayout (
520 rLine
.mxLayoutedLine
,
524 rRenderState
.AffineTransform
.m12
= nSavedM12
;
526 if ( ! IsTextReferencePointLeft())
527 rRenderState
.AffineTransform
.m02
-= rSize
.Width
;
530 void PresenterTextParagraph::Format (
533 const PresenterTheme::SharedFontDescriptor
& rpFont
)
535 // Make sure that the text view is in a valid and sane state.
536 if ( ! mxBreakIterator
.is() || ! mxScriptTypeDetector
.is())
540 if ( ! rpFont
|| ! rpFont
->mxFont
.is())
543 sal_Int32
nPosition (0);
550 mnVerticalOffset
= nY
;
551 maWordBoundaries
.clear();
552 maWordBoundaries
.push_back(0);
554 const rendering::FontMetrics
aMetrics (rpFont
->mxFont
->getFontMetrics());
555 mnAscent
= aMetrics
.Ascent
;
556 mnDescent
= aMetrics
.Descent
;
557 mnLineHeight
= aMetrics
.Ascent
+ aMetrics
.Descent
+ aMetrics
.ExternalLeading
;
559 i18n::Boundary
aCurrentLine(0,0);
562 const i18n::Boundary aWordBoundary
= mxBreakIterator
->nextWord(
566 i18n::WordType::ANYWORD_IGNOREWHITESPACES
);
567 AddWord(nWidth
, aCurrentLine
, aWordBoundary
.startPos
, rpFont
);
569 // Remember the new word boundary for caret travelling by words.
570 // Prevent duplicates.
571 if (aWordBoundary
.startPos
> maWordBoundaries
.back())
572 maWordBoundaries
.push_back(aWordBoundary
.startPos
);
574 if (aWordBoundary
.endPos
>aWordBoundary
.startPos
)
575 AddWord(nWidth
, aCurrentLine
, aWordBoundary
.endPos
, rpFont
);
577 if (aWordBoundary
.startPos
<0 || aWordBoundary
.endPos
<0)
579 if (nPosition
>= aWordBoundary
.endPos
)
581 nPosition
= aWordBoundary
.endPos
;
584 if (aCurrentLine
.endPos
>aCurrentLine
.startPos
)
585 AddLine(aCurrentLine
);
589 sal_Int32
PresenterTextParagraph::GetWordBoundary(
590 const sal_Int32 nLocalCharacterIndex
,
591 const sal_Int32 nDistance
)
593 OSL_ASSERT(nDistance
==-1 || nDistance
==+1);
595 if (nLocalCharacterIndex
< 0)
597 // The caller asked for the start or end position of the paragraph.
601 return GetCharacterCount();
604 sal_Int32
nIndex (0);
605 for (sal_Int32
nCount (maWordBoundaries
.size()); nIndex
<nCount
; ++nIndex
)
607 if (maWordBoundaries
[nIndex
] >= nLocalCharacterIndex
)
609 // When inside the word (not at its start or end) then
610 // first move to the start or end before going the previous or
612 if (maWordBoundaries
[nIndex
] > nLocalCharacterIndex
)
623 else if (sal_uInt32(nIndex
)>=maWordBoundaries
.size())
626 return maWordBoundaries
[nIndex
];
629 sal_Int32
PresenterTextParagraph::GetCaretPosition() const
631 if (mpCaret
&& mpCaret
->GetParagraphIndex()==mnParagraphIndex
)
632 return mpCaret
->GetCharacterIndex();
637 void PresenterTextParagraph::SetCaretPosition (const sal_Int32 nPosition
) const
639 if (mpCaret
&& mpCaret
->GetParagraphIndex()==mnParagraphIndex
)
640 return mpCaret
->SetPosition(mnParagraphIndex
, nPosition
);
643 void PresenterTextParagraph::SetOrigin (const double nXOrigin
, const double nYOrigin
)
645 mnXOrigin
= nXOrigin
;
646 mnYOrigin
= nYOrigin
;
649 awt::Point
PresenterTextParagraph::GetRelativeLocation() const
652 sal_Int32(mnXOrigin
),
653 sal_Int32(mnYOrigin
+ mnVerticalOffset
));
656 awt::Size
PresenterTextParagraph::GetSize() const
660 sal_Int32(GetTotalTextHeight()));
663 void PresenterTextParagraph::AddWord (
665 i18n::Boundary
& rCurrentLine
,
666 const sal_Int32 nWordBoundary
,
667 const PresenterTheme::SharedFontDescriptor
& rpFont
)
669 sal_Int32
nLineStart (0);
670 if ( ! maLines
.empty())
671 nLineStart
= rCurrentLine
.startPos
;
673 const OUString
sLineCandidate (
674 msParagraphText
.copy(nLineStart
, nWordBoundary
-nLineStart
));
676 css::geometry::RealRectangle2D
aLineBox (
677 PresenterCanvasHelper::GetTextBoundingBox (
681 const double nLineWidth (aLineBox
.X2
- aLineBox
.X1
);
683 if (nLineWidth
>= nWidth
)
685 // Add new line with a single word (so far).
686 AddLine(rCurrentLine
);
688 rCurrentLine
.endPos
= nWordBoundary
;
691 void PresenterTextParagraph::AddLine (
692 i18n::Boundary
& rCurrentLine
)
694 Line
aLine (rCurrentLine
.startPos
, rCurrentLine
.endPos
);
696 // Find the start and end of the line with respect to cells.
697 if (!maLines
.empty())
699 aLine
.mnLineStartCellIndex
= maLines
.back().mnLineEndCellIndex
;
700 aLine
.mnBaseLine
= maLines
.back().mnBaseLine
+ mnLineHeight
;
704 aLine
.mnLineStartCellIndex
= 0;
705 aLine
.mnBaseLine
= mnVerticalOffset
+ mnAscent
;
707 sal_Int32
nCellIndex (aLine
.mnLineStartCellIndex
);
709 for ( ; nCellIndex
<sal_Int32(maCells
.size()); ++nCellIndex
)
711 const Cell
& rCell (maCells
[nCellIndex
]);
712 if (rCell
.mnCharacterIndex
+rCell
.mnCharacterCount
> aLine
.mnLineEndCharacterIndex
)
714 nWidth
+= rCell
.mnCellWidth
;
716 aLine
.mnLineEndCellIndex
= nCellIndex
;
717 aLine
.mnWidth
= nWidth
;
719 maLines
.push_back(aLine
);
721 rCurrentLine
.startPos
= rCurrentLine
.endPos
;
724 double PresenterTextParagraph::GetTotalTextHeight() const
726 return maLines
.size() * mnLineHeight
;
729 void PresenterTextParagraph::SetCharacterOffset (const sal_Int32 nCharacterOffset
)
731 mnCharacterOffset
= nCharacterOffset
;
734 sal_Int32
PresenterTextParagraph::GetCharacterCount() const
736 return msParagraphText
.getLength();
739 sal_Unicode
PresenterTextParagraph::GetCharacter (
740 const sal_Int32 nGlobalCharacterIndex
) const
742 if (nGlobalCharacterIndex
<mnCharacterOffset
743 || nGlobalCharacterIndex
>=mnCharacterOffset
+msParagraphText
.getLength())
745 return sal_Unicode();
749 return msParagraphText
[nGlobalCharacterIndex
- mnCharacterOffset
];
753 const OUString
& PresenterTextParagraph::GetText() const
755 return msParagraphText
;
758 TextSegment
PresenterTextParagraph::GetTextSegment (
759 const sal_Int32 nOffset
,
760 const sal_Int32 nIndex
,
761 const sal_Int16 nTextType
) const
765 case AccessibleTextType::PARAGRAPH
:
769 mnCharacterOffset
+msParagraphText
.getLength());
771 case AccessibleTextType::SENTENCE
:
772 if (mxBreakIterator
.is())
774 const sal_Int32
nStart (mxBreakIterator
->beginOfSentence(
775 msParagraphText
, nIndex
-mnCharacterOffset
, lang::Locale()));
776 const sal_Int32
nEnd (mxBreakIterator
->endOfSentence(
777 msParagraphText
, nIndex
-mnCharacterOffset
, lang::Locale()));
780 msParagraphText
.copy(nStart
, nEnd
-nStart
),
781 nStart
+mnCharacterOffset
,
782 nEnd
+mnCharacterOffset
);
786 case AccessibleTextType::WORD
:
787 if (mxBreakIterator
.is())
788 return GetWordTextSegment(nOffset
, nIndex
);
791 case AccessibleTextType::LINE
:
793 auto iLine
= std::find_if(maLines
.begin(), maLines
.end(),
794 [nIndex
](const Line
& rLine
) { return nIndex
< rLine
.mnLineEndCharacterIndex
; });
795 if (iLine
!= maLines
.end())
798 msParagraphText
.copy(
799 iLine
->mnLineStartCharacterIndex
,
800 iLine
->mnLineEndCharacterIndex
- iLine
->mnLineStartCharacterIndex
),
801 iLine
->mnLineStartCharacterIndex
,
802 iLine
->mnLineEndCharacterIndex
);
807 // Handle GLYPH and ATTRIBUTE_RUN like CHARACTER because we can not
808 // do better at the moment.
809 case AccessibleTextType::CHARACTER
:
810 case AccessibleTextType::GLYPH
:
811 case AccessibleTextType::ATTRIBUTE_RUN
:
812 return CreateTextSegment(nIndex
+nOffset
, nIndex
+nOffset
+1);
815 return TextSegment(OUString(), 0,0);
818 TextSegment
PresenterTextParagraph::GetWordTextSegment (
819 const sal_Int32 nOffset
,
820 const sal_Int32 nIndex
) const
822 sal_Int32
nCurrentOffset (nOffset
);
823 sal_Int32
nCurrentIndex (nIndex
);
825 i18n::Boundary aWordBoundary
;
826 if (nCurrentOffset
== 0)
827 aWordBoundary
= mxBreakIterator
->getWordBoundary(
831 i18n::WordType::ANYWORD_IGNOREWHITESPACES
,
833 else if (nCurrentOffset
< 0)
835 while (nCurrentOffset
<0 && nCurrentIndex
>0)
837 aWordBoundary
= mxBreakIterator
->previousWord(
841 i18n::WordType::ANYWORD_IGNOREWHITESPACES
);
842 nCurrentIndex
= aWordBoundary
.startPos
;
848 while (nCurrentOffset
>0 && nCurrentIndex
<=GetCharacterCount())
850 aWordBoundary
= mxBreakIterator
->nextWord(
854 i18n::WordType::ANYWORD_IGNOREWHITESPACES
);
855 nCurrentIndex
= aWordBoundary
.endPos
;
860 return CreateTextSegment(aWordBoundary
.startPos
, aWordBoundary
.endPos
);
863 TextSegment
PresenterTextParagraph::CreateTextSegment (
864 sal_Int32 nStartIndex
,
865 sal_Int32 nEndIndex
) const
867 if (nEndIndex
<= nStartIndex
)
874 msParagraphText
.copy(nStartIndex
, nEndIndex
-nStartIndex
),
879 awt::Rectangle
PresenterTextParagraph::GetCharacterBounds (
880 sal_Int32 nGlobalCharacterIndex
,
881 const bool bCaretBox
)
883 // Find the line that contains the requested character and accumulate
884 // the previous line heights.
885 double nX (mnXOrigin
);
886 double nY (mnYOrigin
+ mnVerticalOffset
+ mnAscent
);
887 const sal_Int8
nTextDirection (GetTextDirection());
888 for (sal_Int32 nLineIndex
=0,nLineCount
=maLines
.size();
889 nLineIndex
<nLineCount
;
890 ++nLineIndex
, nY
+=mnLineHeight
)
892 Line
& rLine (maLines
[nLineIndex
]);
893 // Skip lines before the indexed character.
894 if (nGlobalCharacterIndex
>= rLine
.mnLineEndCharacterIndex
)
895 // When in the last line then allow the index past the last char.
896 if (nLineIndex
<nLineCount
-1)
899 rLine
.ProvideCellBoxes();
901 const sal_Int32
nCellIndex (nGlobalCharacterIndex
- rLine
.mnLineStartCharacterIndex
);
903 // The cell bounding box is defined relative to the origin of
904 // the current line. Therefore we have to add the absolute
905 // position of the line.
906 geometry::RealRectangle2D
rCellBox (rLine
.maCellBoxes
[
907 ::std::min(nCellIndex
, rLine
.maCellBoxes
.getLength()-1)]);
909 double nLeft
= nX
+ rCellBox
.X1
;
910 double nRight
= nX
+ rCellBox
.X2
;
911 if (nTextDirection
== rendering::TextDirection::WEAK_RIGHT_TO_LEFT
)
913 const double nOldRight (nRight
);
914 nRight
= rLine
.mnWidth
- nLeft
;
915 nLeft
= rLine
.mnWidth
- nOldRight
;
917 double nTop
= nY
- mnAscent
;
921 nBottom
= nTop
+ mnLineHeight
;
922 if (nCellIndex
>= rLine
.maCellBoxes
.getLength())
930 nBottom
= nTop
+ mnAscent
+ mnDescent
;
932 const sal_Int32 nX1
= sal_Int32(floor(nLeft
));
933 const sal_Int32 nY1
= sal_Int32(floor(nTop
));
934 const sal_Int32 nX2
= sal_Int32(ceil(nRight
));
935 const sal_Int32 nY2
= sal_Int32(ceil(nBottom
));
937 return awt::Rectangle(nX1
,nY1
,nX2
-nX1
+1,nY2
-nY1
+1);
940 // We are still here. That means that the given index lies past the
941 // last character in the paragraph.
942 // Return an empty box that lies past the last character. Better than nothing.
943 return awt::Rectangle(sal_Int32(nX
+0.5), sal_Int32(nY
+0.5), 0, 0);
946 sal_Int8
PresenterTextParagraph::GetTextDirection() const
948 // Find first portion that has a non-neutral text direction.
949 sal_Int32
nPosition (0);
950 sal_Int32
nTextLength (msParagraphText
.getLength());
951 while (nPosition
< nTextLength
)
953 const sal_Int16
nScriptDirection (
954 mxScriptTypeDetector
->getScriptDirection(
955 msParagraphText
, nPosition
, i18n::ScriptDirection::NEUTRAL
));
956 switch (nScriptDirection
)
958 case i18n::ScriptDirection::NEUTRAL
:
961 case i18n::ScriptDirection::LEFT_TO_RIGHT
:
962 return rendering::TextDirection::WEAK_LEFT_TO_RIGHT
;
964 case i18n::ScriptDirection::RIGHT_TO_LEFT
:
965 return rendering::TextDirection::WEAK_RIGHT_TO_LEFT
;
968 nPosition
= mxScriptTypeDetector
->endOfScriptDirection(
969 msParagraphText
, nPosition
, nScriptDirection
);
972 // All text in paragraph is neutral. Fall back on writing mode taken
973 // from the XText (which may not be properly initialized.)
974 sal_Int8
nTextDirection(rendering::TextDirection::WEAK_LEFT_TO_RIGHT
);
975 switch(mnWritingMode
)
977 case text::WritingMode2::LR_TB
:
978 nTextDirection
= rendering::TextDirection::WEAK_LEFT_TO_RIGHT
;
981 case text::WritingMode2::RL_TB
:
982 nTextDirection
= rendering::TextDirection::WEAK_RIGHT_TO_LEFT
;
986 case text::WritingMode2::TB_RL
:
987 case text::WritingMode2::TB_LR
:
988 // Can not handle this. Use default and hope for the best.
991 return nTextDirection
;
994 bool PresenterTextParagraph::IsTextReferencePointLeft() const
996 return mnWritingMode
!= text::WritingMode2::RL_TB
;
999 void PresenterTextParagraph::SetupCellArray (
1000 const PresenterTheme::SharedFontDescriptor
& rpFont
)
1004 if ( ! rpFont
|| ! rpFont
->mxFont
.is())
1007 sal_Int32
nPosition (0);
1008 sal_Int32
nIndex (0);
1009 const sal_Int32
nTextLength (msParagraphText
.getLength());
1010 const sal_Int8
nTextDirection (GetTextDirection());
1011 while (nPosition
< nTextLength
)
1013 const sal_Int32
nNewPosition (mxBreakIterator
->nextCharacters(
1017 i18n::CharacterIteratorMode::SKIPCELL
,
1021 rendering::StringContext
aContext (msParagraphText
, nPosition
, nNewPosition
-nPosition
);
1022 Reference
<rendering::XTextLayout
> xLayout (
1023 rpFont
->mxFont
->createTextLayout(aContext
, nTextDirection
, 0));
1024 css::geometry::RealRectangle2D
aCharacterBox (xLayout
->queryTextBounds());
1026 maCells
.emplace_back(
1028 nNewPosition
-nPosition
,
1029 aCharacterBox
.X2
-aCharacterBox
.X1
);
1031 nPosition
= nNewPosition
;
1035 //===== PresenterTextCaret ================================================----
1037 PresenterTextCaret::PresenterTextCaret (
1038 uno::Reference
<uno::XComponentContext
> const& xContext
,
1039 const ::std::function
<css::awt::Rectangle (const sal_Int32
,const sal_Int32
)>& rCharacterBoundsAccess
,
1040 const ::std::function
<void (const css::awt::Rectangle
&)>& rInvalidator
)
1041 : m_xContext(xContext
)
1042 , mnParagraphIndex(-1),
1043 mnCharacterIndex(-1),
1044 mnCaretBlinkTaskId(0),
1045 mbIsCaretVisible(false),
1046 maCharacterBoundsAccess(rCharacterBoundsAccess
),
1047 maInvalidator(rInvalidator
),
1053 PresenterTextCaret::~PresenterTextCaret()
1059 catch (uno::Exception
const&)
1061 TOOLS_WARN_EXCEPTION("sdext.presenter", "unexpected exception in ~PresenterTextCaret");
1065 void PresenterTextCaret::ShowCaret()
1067 if (mnCaretBlinkTaskId
== 0)
1069 mnCaretBlinkTaskId
= PresenterTimer::ScheduleRepeatedTask (
1071 [this] (TimeValue
const&) { return this->InvertCaret(); },
1073 CaretBlinkInterval
);
1075 mbIsCaretVisible
= true;
1078 void PresenterTextCaret::HideCaret()
1080 if (mnCaretBlinkTaskId
!= 0)
1082 PresenterTimer::CancelTask(mnCaretBlinkTaskId
);
1083 mnCaretBlinkTaskId
= 0;
1085 mbIsCaretVisible
= false;
1086 // Reset the caret position.
1087 mnParagraphIndex
= -1;
1088 mnCharacterIndex
= -1;
1092 void PresenterTextCaret::SetPosition (
1093 const sal_Int32 nParagraphIndex
,
1094 const sal_Int32 nCharacterIndex
)
1096 if (mnParagraphIndex
== nParagraphIndex
1097 && mnCharacterIndex
== nCharacterIndex
)
1100 if (mnParagraphIndex
>= 0)
1101 maInvalidator(maCaretBounds
);
1103 const sal_Int32
nOldParagraphIndex (mnParagraphIndex
);
1104 const sal_Int32
nOldCharacterIndex (mnCharacterIndex
);
1105 mnParagraphIndex
= nParagraphIndex
;
1106 mnCharacterIndex
= nCharacterIndex
;
1107 maCaretBounds
= maCharacterBoundsAccess(mnParagraphIndex
, mnCharacterIndex
);
1108 if (mnParagraphIndex
>= 0)
1113 if (mnParagraphIndex
>= 0)
1114 maInvalidator(maCaretBounds
);
1125 void PresenterTextCaret::SetCaretMotionBroadcaster (
1126 const ::std::function
<void (sal_Int32
,sal_Int32
,sal_Int32
,sal_Int32
)>& rBroadcaster
)
1128 maBroadcaster
= rBroadcaster
;
1131 const css::awt::Rectangle
& PresenterTextCaret::GetBounds() const
1133 return maCaretBounds
;
1136 void PresenterTextCaret::InvertCaret()
1138 mbIsCaretVisible
= !mbIsCaretVisible
;
1139 if (mnParagraphIndex
>= 0)
1140 maInvalidator(maCaretBounds
);
1143 //===== PresenterTextParagraph::Cell ==========================================
1145 PresenterTextParagraph::Cell::Cell (
1146 const sal_Int32 nCharacterIndex
,
1147 const sal_Int32 nCharacterCount
,
1148 const double nCellWidth
)
1149 : mnCharacterIndex(nCharacterIndex
),
1150 mnCharacterCount(nCharacterCount
),
1151 mnCellWidth(nCellWidth
)
1155 //===== PresenterTextParagraph::Line ==========================================
1157 PresenterTextParagraph::Line::Line (
1158 const sal_Int32 nLineStartCharacterIndex
,
1159 const sal_Int32 nLineEndCharacterIndex
)
1160 : mnLineStartCharacterIndex(nLineStartCharacterIndex
),
1161 mnLineEndCharacterIndex(nLineEndCharacterIndex
),
1162 mnLineStartCellIndex(-1), mnLineEndCellIndex(-1),
1164 mnBaseLine(0), mnWidth(0),
1169 void PresenterTextParagraph::Line::ProvideCellBoxes()
1171 if ( mnLineStartCharacterIndex
< mnLineEndCharacterIndex
&& !maCellBoxes
.hasElements() )
1173 if (mxLayoutedLine
.is())
1174 maCellBoxes
= mxLayoutedLine
->queryInkMeasures();
1177 OSL_ASSERT(mxLayoutedLine
.is());
1182 void PresenterTextParagraph::Line::ProvideLayoutedLine (
1183 const OUString
& rsParagraphText
,
1184 const PresenterTheme::SharedFontDescriptor
& rpFont
,
1185 const sal_Int8 nTextDirection
)
1187 if ( ! mxLayoutedLine
.is())
1189 const rendering::StringContext
aContext (
1191 mnLineStartCharacterIndex
,
1192 mnLineEndCharacterIndex
- mnLineStartCharacterIndex
);
1194 mxLayoutedLine
= rpFont
->mxFont
->createTextLayout(
1201 } } // end of namespace ::sdext::presenter
1203 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */