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 <o3tl/safeint.hxx>
40 #include <comphelper/diagnose_ex.hxx>
42 using namespace ::com::sun::star
;
43 using namespace ::com::sun::star::accessibility
;
44 using namespace ::com::sun::star::uno
;
46 const sal_Int64 CaretBlinkInterval
= 500 * 1000 * 1000;
48 //#define SHOW_CHARACTER_BOXES
51 sal_Int32
Signum (const sal_Int32 nValue
)
62 namespace sdext::presenter
{
64 //===== PresenterTextView =====================================================
66 PresenterTextView::PresenterTextView (
67 const Reference
<XComponentContext
>& rxContext
,
68 const Reference
<rendering::XCanvas
>& rxCanvas
,
69 const ::std::function
<void (const css::awt::Rectangle
&)>& rInvalidator
)
73 mpCaret(std::make_shared
<PresenterTextCaret
>(
75 [this] (sal_Int32
const nParagraphIndex
, sal_Int32
const nCharacterIndex
)
76 { return this->GetCaretBounds(nParagraphIndex
, nCharacterIndex
); },
80 mbIsFormatPending(false)
82 Reference
<lang::XMultiComponentFactory
> xFactory
=
83 rxContext
->getServiceManager();
87 // Create the break iterator that we use to break text into lines.
88 mxBreakIterator
= i18n::BreakIterator::create(rxContext
);
90 // Create the script type detector that is used to split paragraphs into
91 // portions of the same text direction.
92 mxScriptTypeDetector
.set(
93 xFactory
->createInstanceWithContext(
94 "com.sun.star.i18n.ScriptTypeDetector",
99 void PresenterTextView::SetText (const Reference
<text::XText
>& rxText
)
101 maParagraphs
.clear();
103 Reference
<container::XEnumerationAccess
> xParagraphAccess (rxText
, UNO_QUERY
);
104 if ( ! xParagraphAccess
.is())
107 Reference
<container::XEnumeration
> xParagraphs
=
108 xParagraphAccess
->createEnumeration();
109 if ( ! xParagraphs
.is())
112 if ( ! mpFont
|| ! mpFont
->PrepareFont(mxCanvas
))
115 sal_Int32
nCharacterCount (0);
116 while (xParagraphs
->hasMoreElements())
118 SharedPresenterTextParagraph pParagraph
= std::make_shared
<PresenterTextParagraph
>(
121 mxScriptTypeDetector
,
122 Reference
<text::XTextRange
>(xParagraphs
->nextElement(), UNO_QUERY
),
124 pParagraph
->SetupCellArray(mpFont
);
125 pParagraph
->SetCharacterOffset(nCharacterCount
);
126 nCharacterCount
+= pParagraph
->GetCharacterCount();
127 maParagraphs
.push_back(pParagraph
);
131 mpCaret
->HideCaret();
136 void PresenterTextView::SetTextChangeBroadcaster (
137 const ::std::function
<void ()>& rBroadcaster
)
139 maTextChangeBroadcaster
= rBroadcaster
;
142 void PresenterTextView::SetLocation (const css::geometry::RealPoint2D
& rLocation
)
144 maLocation
= rLocation
;
146 for (auto& rxParagraph
: maParagraphs
)
148 rxParagraph
->SetOrigin(
149 maLocation
.X
- mnLeftOffset
,
150 maLocation
.Y
- mnTopOffset
);
154 void PresenterTextView::SetSize (const css::geometry::RealSize2D
& rSize
)
160 double PresenterTextView::GetTotalTextHeight()
162 if (mbIsFormatPending
)
164 if ( ! mpFont
->PrepareFont(mxCanvas
))
169 return std::accumulate(maParagraphs
.begin(), maParagraphs
.end(), double(0),
170 [](const double& nTotalHeight
, const SharedPresenterTextParagraph
& rxParagraph
) {
171 return nTotalHeight
+ rxParagraph
->GetTotalTextHeight();
175 void PresenterTextView::SetFont (const PresenterTheme::SharedFontDescriptor
& rpFont
)
181 void PresenterTextView::SetOffset(
185 mnLeftOffset
= nLeft
;
188 // Trigger an update of the text origin stored at the individual paragraphs.
189 SetLocation(maLocation
);
192 void PresenterTextView::MoveCaret (
193 const sal_Int32 nDistance
,
194 const sal_Int16 nTextType
)
199 // When the caret has not been visible yet then move it to the beginning
201 if (mpCaret
->GetParagraphIndex() < 0)
203 mpCaret
->SetPosition(0,0);
207 sal_Int32
nParagraphIndex (mpCaret
->GetParagraphIndex());
208 sal_Int32
nCharacterIndex (mpCaret
->GetCharacterIndex());
212 case AccessibleTextType::CHARACTER
:
213 nCharacterIndex
+= nDistance
;
216 case AccessibleTextType::WORD
:
218 sal_Int32
nRemainingDistance (nDistance
);
219 while (nRemainingDistance
!= 0)
221 SharedPresenterTextParagraph
pParagraph (GetParagraph(nParagraphIndex
));
224 const sal_Int32
nDelta (Signum(nDistance
));
225 nCharacterIndex
= pParagraph
->GetWordBoundary(nCharacterIndex
, nDelta
);
226 if (nCharacterIndex
< 0)
228 // Go to previous or next paragraph.
229 nParagraphIndex
+= nDelta
;
230 if (nParagraphIndex
< 0)
234 nRemainingDistance
= 0;
236 else if (o3tl::make_unsigned(nParagraphIndex
) >= maParagraphs
.size())
238 nParagraphIndex
= maParagraphs
.size()-1;
239 pParagraph
= GetParagraph(nParagraphIndex
);
241 nCharacterIndex
= pParagraph
->GetCharacterCount();
242 nRemainingDistance
= 0;
246 nRemainingDistance
-= nDelta
;
248 // Move caret one character to the end of
249 // the previous or the start of the next paragraph.
250 pParagraph
= GetParagraph(nParagraphIndex
);
254 nCharacterIndex
= pParagraph
->GetCharacterCount();
261 nRemainingDistance
-= nDelta
;
270 // Move the caret to the new position.
271 mpCaret
->SetPosition(nParagraphIndex
, nCharacterIndex
);
274 void PresenterTextView::Paint (
275 const css::awt::Rectangle
& rUpdateBox
)
277 if ( ! mxCanvas
.is())
279 if ( ! mpFont
->PrepareFont(mxCanvas
))
282 if (mbIsFormatPending
)
285 // Setup the clipping rectangle. Horizontally we make it a little
286 // larger to allow characters (and the caret) to stick out of their
287 // bounding boxes. This can happen on some characters (like the
288 // uppercase J) for typographical reasons.
289 const sal_Int32
nAdditionalLeftBorder (10);
290 const sal_Int32
nAdditionalRightBorder (5);
291 double nX (maLocation
.X
- mnLeftOffset
);
292 double nY (maLocation
.Y
- mnTopOffset
);
293 const sal_Int32
nClipLeft (::std::max(
294 PresenterGeometryHelper::Round(maLocation
.X
)-nAdditionalLeftBorder
, rUpdateBox
.X
));
295 const sal_Int32
nClipTop (::std::max(
296 PresenterGeometryHelper::Round(maLocation
.Y
), rUpdateBox
.Y
));
297 const sal_Int32
nClipRight (::std::min(
298 PresenterGeometryHelper::Round(maLocation
.X
+maSize
.Width
)+nAdditionalRightBorder
, rUpdateBox
.X
+rUpdateBox
.Width
));
299 const sal_Int32
nClipBottom (::std::min(
300 PresenterGeometryHelper::Round(maLocation
.Y
+maSize
.Height
), rUpdateBox
.Y
+rUpdateBox
.Height
));
301 if (nClipLeft
>=nClipRight
|| nClipTop
>=nClipBottom
)
304 const awt::Rectangle
aClipBox(
307 nClipRight
- nClipLeft
,
308 nClipBottom
- nClipTop
);
309 Reference
<rendering::XPolyPolygon2D
> xClipPolygon (
310 PresenterGeometryHelper::CreatePolygon(aClipBox
, mxCanvas
->getDevice()));
312 const rendering::ViewState
aViewState(
313 geometry::AffineMatrix2D(1,0,0, 0,1,0),
316 rendering::RenderState
aRenderState (
317 geometry::AffineMatrix2D(1,0,nX
, 0,1,nY
),
320 rendering::CompositeOperation::SOURCE
);
321 PresenterCanvasHelper::SetDeviceColor(aRenderState
, mpFont
->mnColor
);
323 for (const auto& rxParagraph
: maParagraphs
)
336 aRenderState
.AffineTransform
.m02
= 0;
337 aRenderState
.AffineTransform
.m12
= 0;
339 #ifdef SHOW_CHARACTER_BOXES
340 PresenterCanvasHelper::SetDeviceColor(aRenderState
, 0x00808080);
341 for (sal_Int32
nParagraphIndex(0), nParagraphCount(GetParagraphCount());
342 nParagraphIndex
<nParagraphCount
;
345 const SharedPresenterTextParagraph
pParagraph (GetParagraph(nParagraphIndex
));
348 for (sal_Int32
nCharacterIndex(0),nCharacterCount(pParagraph
->GetCharacterCount());
349 nCharacterIndex
<nCharacterCount
; ++nCharacterIndex
)
351 const awt::Rectangle
aBox (pParagraph
->GetCharacterBounds(nCharacterIndex
, false));
352 mxCanvas
->drawPolyPolygon (
353 PresenterGeometryHelper::CreatePolygon(
355 mxCanvas
->getDevice()),
360 PresenterCanvasHelper::SetDeviceColor(aRenderState
, mpFont
->mnColor
);
363 if (mpCaret
&& mpCaret
->IsVisible())
365 mxCanvas
->fillPolyPolygon (
366 PresenterGeometryHelper::CreatePolygon(
367 mpCaret
->GetBounds(),
368 mxCanvas
->getDevice()),
374 const SharedPresenterTextCaret
& PresenterTextView::GetCaret() const
379 awt::Rectangle
PresenterTextView::GetCaretBounds (
380 sal_Int32 nParagraphIndex
,
381 const sal_Int32 nCharacterIndex
) const
383 SharedPresenterTextParagraph
pParagraph (GetParagraph(nParagraphIndex
));
386 return pParagraph
->GetCharacterBounds(nCharacterIndex
, true);
388 return awt::Rectangle(0,0,0,0);
391 //----- private ---------------------------------------------------------------
393 void PresenterTextView::RequestFormat()
395 mbIsFormatPending
= true;
398 void PresenterTextView::Format()
400 mbIsFormatPending
= false;
403 for (const auto& rxParagraph
: maParagraphs
)
405 rxParagraph
->Format(nY
, maSize
.Width
, mpFont
);
406 nY
+= rxParagraph
->GetTotalTextHeight();
409 if (maTextChangeBroadcaster
)
410 maTextChangeBroadcaster();
413 sal_Int32
PresenterTextView::GetParagraphCount() const
415 return maParagraphs
.size();
418 SharedPresenterTextParagraph
PresenterTextView::GetParagraph (
419 const sal_Int32 nParagraphIndex
) const
421 if (nParagraphIndex
< 0)
422 return SharedPresenterTextParagraph();
423 else if (o3tl::make_unsigned(nParagraphIndex
)>=maParagraphs
.size())
424 return SharedPresenterTextParagraph();
426 return maParagraphs
[nParagraphIndex
];
429 //===== PresenterTextParagraph ================================================
431 PresenterTextParagraph::PresenterTextParagraph (
432 const sal_Int32 nParagraphIndex
,
433 const Reference
<i18n::XBreakIterator
>& rxBreakIterator
,
434 const Reference
<i18n::XScriptTypeDetector
>& rxScriptTypeDetector
,
435 const Reference
<text::XTextRange
>& rxTextRange
,
436 SharedPresenterTextCaret xCaret
)
437 : mnParagraphIndex(nParagraphIndex
),
438 mpCaret(std::move(xCaret
)),
439 mxBreakIterator(rxBreakIterator
),
440 mxScriptTypeDetector(rxScriptTypeDetector
),
448 mnWritingMode (text::WritingMode2::LR_TB
),
451 if (!rxTextRange
.is())
454 Reference
<beans::XPropertySet
> xProperties (rxTextRange
, UNO_QUERY
);
457 xProperties
->getPropertyValue("WritingMode") >>= mnWritingMode
;
459 catch(beans::UnknownPropertyException
&)
461 // Ignore the exception. Use the default value.
464 msParagraphText
= rxTextRange
->getString();
467 void PresenterTextParagraph::Paint (
468 const Reference
<rendering::XCanvas
>& rxCanvas
,
469 const geometry::RealSize2D
& rSize
,
470 const PresenterTheme::SharedFontDescriptor
& rpFont
,
471 const rendering::ViewState
& rViewState
,
472 rendering::RenderState
& rRenderState
,
473 const double nTopOffset
,
474 const double nClipTop
,
475 const double nClipBottom
)
477 if (mnLineHeight
<= 0)
480 sal_Int8
nTextDirection (GetTextDirection());
482 const double nSavedM12 (rRenderState
.AffineTransform
.m12
);
484 if ( ! IsTextReferencePointLeft())
485 rRenderState
.AffineTransform
.m02
+= rSize
.Width
;
487 #ifdef SHOW_CHARACTER_BOXES
488 for (sal_Int32 nIndex
=0,nCount
=maLines
.size();
492 Line
& rLine (maLines
[nIndex
]);
493 rLine
.ProvideLayoutedLine(msParagraphText
, rpFont
, nTextDirection
);
497 for (sal_Int32 nIndex
=0,nCount
=maLines
.size();
499 ++nIndex
, rRenderState
.AffineTransform
.m12
+= mnLineHeight
)
501 Line
& rLine (maLines
[nIndex
]);
503 // Paint only visible lines.
504 const double nLineTop
= rLine
.mnBaseLine
- mnAscent
- nTopOffset
;
505 if (nLineTop
+ mnLineHeight
< nClipTop
)
507 else if (nLineTop
> nClipBottom
)
509 rLine
.ProvideLayoutedLine(msParagraphText
, rpFont
, nTextDirection
);
511 rRenderState
.AffineTransform
.m12
= nSavedM12
+ rLine
.mnBaseLine
;
513 rxCanvas
->drawTextLayout (
514 rLine
.mxLayoutedLine
,
518 rRenderState
.AffineTransform
.m12
= nSavedM12
;
520 if ( ! IsTextReferencePointLeft())
521 rRenderState
.AffineTransform
.m02
-= rSize
.Width
;
524 void PresenterTextParagraph::Format (
527 const PresenterTheme::SharedFontDescriptor
& rpFont
)
529 // Make sure that the text view is in a valid and sane state.
530 if ( ! mxBreakIterator
.is() || ! mxScriptTypeDetector
.is())
534 if ( ! rpFont
|| ! rpFont
->mxFont
.is())
537 sal_Int32
nPosition (0);
544 mnVerticalOffset
= nY
;
545 maWordBoundaries
.clear();
546 maWordBoundaries
.push_back(0);
548 const rendering::FontMetrics
aMetrics (rpFont
->mxFont
->getFontMetrics());
549 mnAscent
= aMetrics
.Ascent
;
550 mnDescent
= aMetrics
.Descent
;
551 mnLineHeight
= aMetrics
.Ascent
+ aMetrics
.Descent
+ aMetrics
.ExternalLeading
;
553 i18n::Boundary
aCurrentLine(0,0);
556 const i18n::Boundary aWordBoundary
= mxBreakIterator
->nextWord(
560 i18n::WordType::ANYWORD_IGNOREWHITESPACES
);
561 AddWord(nWidth
, aCurrentLine
, aWordBoundary
.startPos
, rpFont
);
563 // Remember the new word boundary for caret travelling by words.
564 // Prevent duplicates.
565 if (aWordBoundary
.startPos
> maWordBoundaries
.back())
566 maWordBoundaries
.push_back(aWordBoundary
.startPos
);
568 if (aWordBoundary
.endPos
>aWordBoundary
.startPos
)
569 AddWord(nWidth
, aCurrentLine
, aWordBoundary
.endPos
, rpFont
);
571 if (aWordBoundary
.startPos
<0 || aWordBoundary
.endPos
<0)
573 if (nPosition
>= aWordBoundary
.endPos
)
575 nPosition
= aWordBoundary
.endPos
;
578 if (aCurrentLine
.endPos
>aCurrentLine
.startPos
)
579 AddLine(aCurrentLine
);
583 sal_Int32
PresenterTextParagraph::GetWordBoundary(
584 const sal_Int32 nLocalCharacterIndex
,
585 const sal_Int32 nDistance
)
587 OSL_ASSERT(nDistance
==-1 || nDistance
==+1);
589 if (nLocalCharacterIndex
< 0)
591 // The caller asked for the start or end position of the paragraph.
595 return GetCharacterCount();
598 sal_Int32
nIndex (0);
599 for (sal_Int32
nCount (maWordBoundaries
.size()); nIndex
<nCount
; ++nIndex
)
601 if (maWordBoundaries
[nIndex
] >= nLocalCharacterIndex
)
603 // When inside the word (not at its start or end) then
604 // first move to the start or end before going the previous or
606 if (maWordBoundaries
[nIndex
] > nLocalCharacterIndex
)
617 else if (o3tl::make_unsigned(nIndex
)>=maWordBoundaries
.size())
620 return maWordBoundaries
[nIndex
];
623 sal_Int32
PresenterTextParagraph::GetCaretPosition() const
625 if (mpCaret
&& mpCaret
->GetParagraphIndex()==mnParagraphIndex
)
626 return mpCaret
->GetCharacterIndex();
631 void PresenterTextParagraph::SetCaretPosition (const sal_Int32 nPosition
) const
633 if (mpCaret
&& mpCaret
->GetParagraphIndex()==mnParagraphIndex
)
634 return mpCaret
->SetPosition(mnParagraphIndex
, nPosition
);
637 void PresenterTextParagraph::SetOrigin (const double nXOrigin
, const double nYOrigin
)
639 mnXOrigin
= nXOrigin
;
640 mnYOrigin
= nYOrigin
;
643 awt::Point
PresenterTextParagraph::GetRelativeLocation() const
646 sal_Int32(mnXOrigin
),
647 sal_Int32(mnYOrigin
+ mnVerticalOffset
));
650 awt::Size
PresenterTextParagraph::GetSize() const
654 sal_Int32(GetTotalTextHeight()));
657 void PresenterTextParagraph::AddWord (
659 i18n::Boundary
& rCurrentLine
,
660 const sal_Int32 nWordBoundary
,
661 const PresenterTheme::SharedFontDescriptor
& rpFont
)
663 sal_Int32
nLineStart (0);
664 if ( ! maLines
.empty())
665 nLineStart
= rCurrentLine
.startPos
;
667 const OUString
sLineCandidate (
668 msParagraphText
.copy(nLineStart
, nWordBoundary
-nLineStart
));
670 css::geometry::RealRectangle2D
aLineBox (
671 PresenterCanvasHelper::GetTextBoundingBox (
675 const double nLineWidth (aLineBox
.X2
- aLineBox
.X1
);
677 if (nLineWidth
>= nWidth
)
679 // Add new line with a single word (so far).
680 AddLine(rCurrentLine
);
682 rCurrentLine
.endPos
= nWordBoundary
;
685 void PresenterTextParagraph::AddLine (
686 i18n::Boundary
& rCurrentLine
)
688 Line
aLine (rCurrentLine
.startPos
, rCurrentLine
.endPos
);
690 // Find the start and end of the line with respect to cells.
691 if (!maLines
.empty())
693 aLine
.mnLineStartCellIndex
= maLines
.back().mnLineEndCellIndex
;
694 aLine
.mnBaseLine
= maLines
.back().mnBaseLine
+ mnLineHeight
;
698 aLine
.mnLineStartCellIndex
= 0;
699 aLine
.mnBaseLine
= mnVerticalOffset
+ mnAscent
;
701 sal_Int32
nCellIndex (aLine
.mnLineStartCellIndex
);
703 for ( ; nCellIndex
<sal_Int32(maCells
.size()); ++nCellIndex
)
705 const Cell
& rCell (maCells
[nCellIndex
]);
706 if (rCell
.mnCharacterIndex
+rCell
.mnCharacterCount
> aLine
.mnLineEndCharacterIndex
)
708 nWidth
+= rCell
.mnCellWidth
;
710 aLine
.mnLineEndCellIndex
= nCellIndex
;
711 aLine
.mnWidth
= nWidth
;
713 maLines
.push_back(aLine
);
715 rCurrentLine
.startPos
= rCurrentLine
.endPos
;
718 double PresenterTextParagraph::GetTotalTextHeight() const
720 return maLines
.size() * mnLineHeight
;
723 void PresenterTextParagraph::SetCharacterOffset (const sal_Int32 nCharacterOffset
)
725 mnCharacterOffset
= nCharacterOffset
;
728 sal_Int32
PresenterTextParagraph::GetCharacterCount() const
730 return msParagraphText
.getLength();
733 sal_Unicode
PresenterTextParagraph::GetCharacter (
734 const sal_Int32 nGlobalCharacterIndex
) const
736 if (nGlobalCharacterIndex
<mnCharacterOffset
737 || nGlobalCharacterIndex
>=mnCharacterOffset
+msParagraphText
.getLength())
739 return sal_Unicode();
743 return msParagraphText
[nGlobalCharacterIndex
- mnCharacterOffset
];
747 const OUString
& PresenterTextParagraph::GetText() const
749 return msParagraphText
;
752 TextSegment
PresenterTextParagraph::GetTextSegment (
753 const sal_Int32 nOffset
,
754 const sal_Int32 nIndex
,
755 const sal_Int16 nTextType
) const
759 case AccessibleTextType::PARAGRAPH
:
763 mnCharacterOffset
+msParagraphText
.getLength());
765 case AccessibleTextType::SENTENCE
:
766 if (mxBreakIterator
.is())
768 const sal_Int32
nStart (mxBreakIterator
->beginOfSentence(
769 msParagraphText
, nIndex
-mnCharacterOffset
, lang::Locale()));
770 const sal_Int32
nEnd (mxBreakIterator
->endOfSentence(
771 msParagraphText
, nIndex
-mnCharacterOffset
, lang::Locale()));
774 msParagraphText
.copy(nStart
, nEnd
-nStart
),
775 nStart
+mnCharacterOffset
,
776 nEnd
+mnCharacterOffset
);
780 case AccessibleTextType::WORD
:
781 if (mxBreakIterator
.is())
782 return GetWordTextSegment(nOffset
, nIndex
);
785 case AccessibleTextType::LINE
:
787 auto iLine
= std::find_if(maLines
.begin(), maLines
.end(),
788 [nIndex
](const Line
& rLine
) { return nIndex
< rLine
.mnLineEndCharacterIndex
; });
789 if (iLine
!= maLines
.end())
792 msParagraphText
.copy(
793 iLine
->mnLineStartCharacterIndex
,
794 iLine
->mnLineEndCharacterIndex
- iLine
->mnLineStartCharacterIndex
),
795 iLine
->mnLineStartCharacterIndex
,
796 iLine
->mnLineEndCharacterIndex
);
801 // Handle GLYPH and ATTRIBUTE_RUN like CHARACTER because we can not
802 // do better at the moment.
803 case AccessibleTextType::CHARACTER
:
804 case AccessibleTextType::GLYPH
:
805 case AccessibleTextType::ATTRIBUTE_RUN
:
806 return CreateTextSegment(nIndex
+nOffset
, nIndex
+nOffset
+1);
809 return TextSegment(OUString(), 0,0);
812 TextSegment
PresenterTextParagraph::GetWordTextSegment (
813 const sal_Int32 nOffset
,
814 const sal_Int32 nIndex
) const
816 sal_Int32
nCurrentOffset (nOffset
);
817 sal_Int32
nCurrentIndex (nIndex
);
819 i18n::Boundary aWordBoundary
;
820 if (nCurrentOffset
== 0)
821 aWordBoundary
= mxBreakIterator
->getWordBoundary(
825 i18n::WordType::ANYWORD_IGNOREWHITESPACES
,
827 else if (nCurrentOffset
< 0)
829 while (nCurrentOffset
<0 && nCurrentIndex
>0)
831 aWordBoundary
= mxBreakIterator
->previousWord(
835 i18n::WordType::ANYWORD_IGNOREWHITESPACES
);
836 nCurrentIndex
= aWordBoundary
.startPos
;
842 while (nCurrentOffset
>0 && nCurrentIndex
<=GetCharacterCount())
844 aWordBoundary
= mxBreakIterator
->nextWord(
848 i18n::WordType::ANYWORD_IGNOREWHITESPACES
);
849 nCurrentIndex
= aWordBoundary
.endPos
;
854 return CreateTextSegment(aWordBoundary
.startPos
, aWordBoundary
.endPos
);
857 TextSegment
PresenterTextParagraph::CreateTextSegment (
858 sal_Int32 nStartIndex
,
859 sal_Int32 nEndIndex
) const
861 if (nEndIndex
<= nStartIndex
)
868 msParagraphText
.copy(nStartIndex
, nEndIndex
-nStartIndex
),
873 awt::Rectangle
PresenterTextParagraph::GetCharacterBounds (
874 sal_Int32 nGlobalCharacterIndex
,
875 const bool bCaretBox
)
877 // Find the line that contains the requested character and accumulate
878 // the previous line heights.
879 double nX (mnXOrigin
);
880 double nY (mnYOrigin
+ mnVerticalOffset
+ mnAscent
);
881 const sal_Int8
nTextDirection (GetTextDirection());
882 for (sal_Int32 nLineIndex
=0,nLineCount
=maLines
.size();
883 nLineIndex
<nLineCount
;
884 ++nLineIndex
, nY
+=mnLineHeight
)
886 Line
& rLine (maLines
[nLineIndex
]);
887 // Skip lines before the indexed character.
888 if (nGlobalCharacterIndex
>= rLine
.mnLineEndCharacterIndex
)
889 // When in the last line then allow the index past the last char.
890 if (nLineIndex
<nLineCount
-1)
893 rLine
.ProvideCellBoxes();
895 const sal_Int32
nCellIndex (nGlobalCharacterIndex
- rLine
.mnLineStartCharacterIndex
);
897 // The cell bounding box is defined relative to the origin of
898 // the current line. Therefore we have to add the absolute
899 // position of the line.
900 geometry::RealRectangle2D
rCellBox (rLine
.maCellBoxes
[
901 ::std::min(nCellIndex
, rLine
.maCellBoxes
.getLength()-1)]);
903 double nLeft
= nX
+ rCellBox
.X1
;
904 double nRight
= nX
+ rCellBox
.X2
;
905 if (nTextDirection
== rendering::TextDirection::WEAK_RIGHT_TO_LEFT
)
907 const double nOldRight (nRight
);
908 nRight
= rLine
.mnWidth
- nLeft
;
909 nLeft
= rLine
.mnWidth
- nOldRight
;
911 double nTop
= nY
- mnAscent
;
915 nBottom
= nTop
+ mnLineHeight
;
916 if (nCellIndex
>= rLine
.maCellBoxes
.getLength())
924 nBottom
= nTop
+ mnAscent
+ mnDescent
;
926 const sal_Int32 nX1
= sal_Int32(floor(nLeft
));
927 const sal_Int32 nY1
= sal_Int32(floor(nTop
));
928 const sal_Int32 nX2
= sal_Int32(ceil(nRight
));
929 const sal_Int32 nY2
= sal_Int32(ceil(nBottom
));
931 return awt::Rectangle(nX1
,nY1
,nX2
-nX1
+1,nY2
-nY1
+1);
934 // We are still here. That means that the given index lies past the
935 // last character in the paragraph.
936 // Return an empty box that lies past the last character. Better than nothing.
937 return awt::Rectangle(sal_Int32(nX
+0.5), sal_Int32(nY
+0.5), 0, 0);
940 sal_Int8
PresenterTextParagraph::GetTextDirection() const
942 // Find first portion that has a non-neutral text direction.
943 sal_Int32
nPosition (0);
944 sal_Int32
nTextLength (msParagraphText
.getLength());
945 while (nPosition
< nTextLength
)
947 const sal_Int16
nScriptDirection (
948 mxScriptTypeDetector
->getScriptDirection(
949 msParagraphText
, nPosition
, i18n::ScriptDirection::NEUTRAL
));
950 switch (nScriptDirection
)
952 case i18n::ScriptDirection::NEUTRAL
:
955 case i18n::ScriptDirection::LEFT_TO_RIGHT
:
956 return rendering::TextDirection::WEAK_LEFT_TO_RIGHT
;
958 case i18n::ScriptDirection::RIGHT_TO_LEFT
:
959 return rendering::TextDirection::WEAK_RIGHT_TO_LEFT
;
962 nPosition
= mxScriptTypeDetector
->endOfScriptDirection(
963 msParagraphText
, nPosition
, nScriptDirection
);
966 // All text in paragraph is neutral. Fall back on writing mode taken
967 // from the XText (which may not be properly initialized.)
968 sal_Int8
nTextDirection(rendering::TextDirection::WEAK_LEFT_TO_RIGHT
);
969 switch(mnWritingMode
)
971 case text::WritingMode2::LR_TB
:
972 nTextDirection
= rendering::TextDirection::WEAK_LEFT_TO_RIGHT
;
975 case text::WritingMode2::RL_TB
:
976 nTextDirection
= rendering::TextDirection::WEAK_RIGHT_TO_LEFT
;
980 case text::WritingMode2::TB_RL
:
981 case text::WritingMode2::TB_LR
:
982 // Can not handle this. Use default and hope for the best.
985 return nTextDirection
;
988 bool PresenterTextParagraph::IsTextReferencePointLeft() const
990 return mnWritingMode
!= text::WritingMode2::RL_TB
;
993 void PresenterTextParagraph::SetupCellArray (
994 const PresenterTheme::SharedFontDescriptor
& rpFont
)
998 if ( ! rpFont
|| ! rpFont
->mxFont
.is())
1001 sal_Int32
nPosition (0);
1002 sal_Int32
nIndex (0);
1003 const sal_Int32
nTextLength (msParagraphText
.getLength());
1004 const sal_Int8
nTextDirection (GetTextDirection());
1005 while (nPosition
< nTextLength
)
1007 const sal_Int32
nNewPosition (mxBreakIterator
->nextCharacters(
1011 i18n::CharacterIteratorMode::SKIPCELL
,
1015 rendering::StringContext
aContext (msParagraphText
, nPosition
, nNewPosition
-nPosition
);
1016 Reference
<rendering::XTextLayout
> xLayout (
1017 rpFont
->mxFont
->createTextLayout(aContext
, nTextDirection
, 0));
1018 css::geometry::RealRectangle2D
aCharacterBox (xLayout
->queryTextBounds());
1020 maCells
.emplace_back(
1022 nNewPosition
-nPosition
,
1023 aCharacterBox
.X2
-aCharacterBox
.X1
);
1025 nPosition
= nNewPosition
;
1029 //===== PresenterTextCaret ================================================----
1031 PresenterTextCaret::PresenterTextCaret (
1032 uno::Reference
<uno::XComponentContext
> const& xContext
,
1033 ::std::function
<css::awt::Rectangle (const sal_Int32
,const sal_Int32
)> aCharacterBoundsAccess
,
1034 ::std::function
<void (const css::awt::Rectangle
&)> aInvalidator
)
1035 : m_xContext(xContext
)
1036 , mnParagraphIndex(-1),
1037 mnCharacterIndex(-1),
1038 mnCaretBlinkTaskId(0),
1039 mbIsCaretVisible(false),
1040 maCharacterBoundsAccess(std::move(aCharacterBoundsAccess
)),
1041 maInvalidator(std::move(aInvalidator
))
1045 PresenterTextCaret::~PresenterTextCaret()
1051 catch (uno::Exception
const&)
1053 TOOLS_WARN_EXCEPTION("sdext.presenter", "unexpected exception in ~PresenterTextCaret");
1057 void PresenterTextCaret::ShowCaret()
1059 if (mnCaretBlinkTaskId
== 0)
1061 mnCaretBlinkTaskId
= PresenterTimer::ScheduleRepeatedTask (
1063 [this] (TimeValue
const&) { return this->InvertCaret(); },
1065 CaretBlinkInterval
);
1067 mbIsCaretVisible
= true;
1070 void PresenterTextCaret::HideCaret()
1072 if (mnCaretBlinkTaskId
!= 0)
1074 PresenterTimer::CancelTask(mnCaretBlinkTaskId
);
1075 mnCaretBlinkTaskId
= 0;
1077 mbIsCaretVisible
= false;
1078 // Reset the caret position.
1079 mnParagraphIndex
= -1;
1080 mnCharacterIndex
= -1;
1084 void PresenterTextCaret::SetPosition (
1085 const sal_Int32 nParagraphIndex
,
1086 const sal_Int32 nCharacterIndex
)
1088 if (mnParagraphIndex
== nParagraphIndex
1089 && mnCharacterIndex
== nCharacterIndex
)
1092 if (mnParagraphIndex
>= 0)
1093 maInvalidator(maCaretBounds
);
1095 const sal_Int32
nOldParagraphIndex (mnParagraphIndex
);
1096 const sal_Int32
nOldCharacterIndex (mnCharacterIndex
);
1097 mnParagraphIndex
= nParagraphIndex
;
1098 mnCharacterIndex
= nCharacterIndex
;
1099 maCaretBounds
= maCharacterBoundsAccess(mnParagraphIndex
, mnCharacterIndex
);
1100 if (mnParagraphIndex
>= 0)
1105 if (mnParagraphIndex
>= 0)
1106 maInvalidator(maCaretBounds
);
1117 void PresenterTextCaret::SetCaretMotionBroadcaster (
1118 const ::std::function
<void (sal_Int32
,sal_Int32
,sal_Int32
,sal_Int32
)>& rBroadcaster
)
1120 maBroadcaster
= rBroadcaster
;
1123 const css::awt::Rectangle
& PresenterTextCaret::GetBounds() const
1125 return maCaretBounds
;
1128 void PresenterTextCaret::InvertCaret()
1130 mbIsCaretVisible
= !mbIsCaretVisible
;
1131 if (mnParagraphIndex
>= 0)
1132 maInvalidator(maCaretBounds
);
1135 //===== PresenterTextParagraph::Cell ==========================================
1137 PresenterTextParagraph::Cell::Cell (
1138 const sal_Int32 nCharacterIndex
,
1139 const sal_Int32 nCharacterCount
,
1140 const double nCellWidth
)
1141 : mnCharacterIndex(nCharacterIndex
),
1142 mnCharacterCount(nCharacterCount
),
1143 mnCellWidth(nCellWidth
)
1147 //===== PresenterTextParagraph::Line ==========================================
1149 PresenterTextParagraph::Line::Line (
1150 const sal_Int32 nLineStartCharacterIndex
,
1151 const sal_Int32 nLineEndCharacterIndex
)
1152 : mnLineStartCharacterIndex(nLineStartCharacterIndex
),
1153 mnLineEndCharacterIndex(nLineEndCharacterIndex
),
1154 mnLineStartCellIndex(-1), mnLineEndCellIndex(-1),
1155 mnBaseLine(0), mnWidth(0)
1159 void PresenterTextParagraph::Line::ProvideCellBoxes()
1161 if ( mnLineStartCharacterIndex
< mnLineEndCharacterIndex
&& !maCellBoxes
.hasElements() )
1163 if (mxLayoutedLine
.is())
1164 maCellBoxes
= mxLayoutedLine
->queryInkMeasures();
1167 OSL_ASSERT(mxLayoutedLine
.is());
1172 void PresenterTextParagraph::Line::ProvideLayoutedLine (
1173 const OUString
& rsParagraphText
,
1174 const PresenterTheme::SharedFontDescriptor
& rpFont
,
1175 const sal_Int8 nTextDirection
)
1177 if ( ! mxLayoutedLine
.is())
1179 const rendering::StringContext
aContext (
1181 mnLineStartCharacterIndex
,
1182 mnLineEndCharacterIndex
- mnLineStartCharacterIndex
);
1184 mxLayoutedLine
= rpFont
->mxFont
->createTextLayout(
1191 } // end of namespace ::sdext::presenter
1193 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */