Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sd / source / console / PresenterTextView.cxx
blob08d981fe6ac149908b42b8f0a30dd78682525952
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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"
25 #include <algorithm>
26 #include <cmath>
27 #include <numeric>
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>
39 #include <utility>
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
50 namespace {
51 sal_Int32 Signum (const sal_Int32 nValue)
53 if (nValue < 0)
54 return -1;
55 else if (nValue > 0)
56 return +1;
57 else
58 return 0;
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)
70 : mxCanvas(rxCanvas),
71 maLocation(0,0),
72 maSize(0,0),
73 mpCaret(std::make_shared<PresenterTextCaret>(
74 rxContext,
75 [this] (sal_Int32 const nParagraphIndex, sal_Int32 const nCharacterIndex)
76 { return this->GetCaretBounds(nParagraphIndex, nCharacterIndex); },
77 rInvalidator)),
78 mnLeftOffset(0),
79 mnTopOffset(0),
80 mbIsFormatPending(false)
82 Reference<lang::XMultiComponentFactory> xFactory =
83 rxContext->getServiceManager();
84 if ( ! xFactory.is())
85 return;
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",
95 rxContext),
96 UNO_QUERY_THROW);
99 void PresenterTextView::SetText (const Reference<text::XText>& rxText)
101 maParagraphs.clear();
103 Reference<container::XEnumerationAccess> xParagraphAccess (rxText, UNO_QUERY);
104 if ( ! xParagraphAccess.is())
105 return;
107 Reference<container::XEnumeration> xParagraphs =
108 xParagraphAccess->createEnumeration();
109 if ( ! xParagraphs.is())
110 return;
112 if ( ! mpFont || ! mpFont->PrepareFont(mxCanvas))
113 return;
115 sal_Int32 nCharacterCount (0);
116 while (xParagraphs->hasMoreElements())
118 SharedPresenterTextParagraph pParagraph = std::make_shared<PresenterTextParagraph>(
119 maParagraphs.size(),
120 mxBreakIterator,
121 mxScriptTypeDetector,
122 Reference<text::XTextRange>(xParagraphs->nextElement(), UNO_QUERY),
123 mpCaret);
124 pParagraph->SetupCellArray(mpFont);
125 pParagraph->SetCharacterOffset(nCharacterCount);
126 nCharacterCount += pParagraph->GetCharacterCount();
127 maParagraphs.push_back(pParagraph);
130 if (mpCaret)
131 mpCaret->HideCaret();
133 RequestFormat();
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)
156 maSize = rSize;
157 RequestFormat();
160 double PresenterTextView::GetTotalTextHeight()
162 if (mbIsFormatPending)
164 if ( ! mpFont->PrepareFont(mxCanvas))
165 return 0;
166 Format();
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)
177 mpFont = rpFont;
178 RequestFormat();
181 void PresenterTextView::SetOffset(
182 const double nLeft,
183 const double nTop)
185 mnLeftOffset = nLeft;
186 mnTopOffset = nTop;
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)
196 if ( ! mpCaret)
197 return;
199 // When the caret has not been visible yet then move it to the beginning
200 // of the text.
201 if (mpCaret->GetParagraphIndex() < 0)
203 mpCaret->SetPosition(0,0);
204 return;
207 sal_Int32 nParagraphIndex (mpCaret->GetParagraphIndex());
208 sal_Int32 nCharacterIndex (mpCaret->GetCharacterIndex());
209 switch (nTextType)
211 default:
212 case AccessibleTextType::CHARACTER:
213 nCharacterIndex += nDistance;
214 break;
216 case AccessibleTextType::WORD:
218 sal_Int32 nRemainingDistance (nDistance);
219 while (nRemainingDistance != 0)
221 SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
222 if (pParagraph)
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)
232 nParagraphIndex = 0;
233 nCharacterIndex = 0;
234 nRemainingDistance = 0;
236 else if (o3tl::make_unsigned(nParagraphIndex) >= maParagraphs.size())
238 nParagraphIndex = maParagraphs.size()-1;
239 pParagraph = GetParagraph(nParagraphIndex);
240 if (pParagraph)
241 nCharacterIndex = pParagraph->GetCharacterCount();
242 nRemainingDistance = 0;
244 else
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);
251 if (pParagraph)
253 if (nDistance<0)
254 nCharacterIndex = pParagraph->GetCharacterCount();
255 else
256 nCharacterIndex = 0;
260 else
261 nRemainingDistance -= nDelta;
263 else
264 break;
266 break;
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())
278 return;
279 if ( ! mpFont->PrepareFont(mxCanvas))
280 return;
282 if (mbIsFormatPending)
283 Format();
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)
302 return;
304 const awt::Rectangle aClipBox(
305 nClipLeft,
306 nClipTop,
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),
314 xClipPolygon);
316 rendering::RenderState aRenderState (
317 geometry::AffineMatrix2D(1,0,nX, 0,1,nY),
318 nullptr,
319 Sequence<double>(4),
320 rendering::CompositeOperation::SOURCE);
321 PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor);
323 for (const auto& rxParagraph : maParagraphs)
325 rxParagraph->Paint(
326 mxCanvas,
327 maSize,
328 mpFont,
329 aViewState,
330 aRenderState,
331 mnTopOffset,
332 nClipTop,
333 nClipBottom);
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;
343 ++nParagraphIndex)
345 const SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
346 if ( ! pParagraph)
347 continue;
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(
354 aBox,
355 mxCanvas->getDevice()),
356 aViewState,
357 aRenderState);
360 PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor);
361 #endif
363 if (mpCaret && mpCaret->IsVisible())
365 mxCanvas->fillPolyPolygon (
366 PresenterGeometryHelper::CreatePolygon(
367 mpCaret->GetBounds(),
368 mxCanvas->getDevice()),
369 aViewState,
370 aRenderState);
374 const SharedPresenterTextCaret& PresenterTextView::GetCaret() const
376 return mpCaret;
379 awt::Rectangle PresenterTextView::GetCaretBounds (
380 sal_Int32 nParagraphIndex,
381 const sal_Int32 nCharacterIndex) const
383 SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
385 if (pParagraph)
386 return pParagraph->GetCharacterBounds(nCharacterIndex, true);
387 else
388 return awt::Rectangle(0,0,0,0);
391 //----- private ---------------------------------------------------------------
393 void PresenterTextView::RequestFormat()
395 mbIsFormatPending = true;
398 void PresenterTextView::Format()
400 mbIsFormatPending = false;
402 double nY (0);
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();
425 else
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),
441 mnVerticalOffset(0),
442 mnXOrigin(0),
443 mnYOrigin(0),
444 mnWidth(0),
445 mnAscent(0),
446 mnDescent(0),
447 mnLineHeight(-1),
448 mnWritingMode (text::WritingMode2::LR_TB),
449 mnCharacterOffset(0)
451 if (!rxTextRange.is())
452 return;
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)
478 return;
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();
489 nIndex<nCount;
490 ++nIndex)
492 Line& rLine (maLines[nIndex]);
493 rLine.ProvideLayoutedLine(msParagraphText, rpFont, nTextDirection);
495 #endif
497 for (sal_Int32 nIndex=0,nCount=maLines.size();
498 nIndex<nCount;
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)
506 continue;
507 else if (nLineTop > nClipBottom)
508 break;
509 rLine.ProvideLayoutedLine(msParagraphText, rpFont, nTextDirection);
511 rRenderState.AffineTransform.m12 = nSavedM12 + rLine.mnBaseLine;
513 rxCanvas->drawTextLayout (
514 rLine.mxLayoutedLine,
515 rViewState,
516 rRenderState);
518 rRenderState.AffineTransform.m12 = nSavedM12;
520 if ( ! IsTextReferencePointLeft())
521 rRenderState.AffineTransform.m02 -= rSize.Width;
524 void PresenterTextParagraph::Format (
525 const double nY,
526 const double nWidth,
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())
531 return;
532 if (nWidth<=0)
533 return;
534 if ( ! rpFont || ! rpFont->mxFont.is())
535 return;
537 sal_Int32 nPosition (0);
539 mnWidth = nWidth;
540 maLines.clear();
541 mnLineHeight = 0;
542 mnAscent = 0;
543 mnDescent = 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;
552 nPosition = 0;
553 i18n::Boundary aCurrentLine(0,0);
554 while (true)
556 const i18n::Boundary aWordBoundary = mxBreakIterator->nextWord(
557 msParagraphText,
558 nPosition,
559 lang::Locale(),
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)
572 break;
573 if (nPosition >= aWordBoundary.endPos)
574 break;
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.
592 if (nDistance < 0)
593 return 0;
594 else
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
605 // next word.
606 if (maWordBoundaries[nIndex] > nLocalCharacterIndex)
607 if (nDistance > 0)
608 --nIndex;
609 break;
613 nIndex += nDistance;
615 if (nIndex < 0)
616 return -1;
617 else if (o3tl::make_unsigned(nIndex)>=maWordBoundaries.size())
618 return -1;
619 else
620 return maWordBoundaries[nIndex];
623 sal_Int32 PresenterTextParagraph::GetCaretPosition() const
625 if (mpCaret && mpCaret->GetParagraphIndex()==mnParagraphIndex)
626 return mpCaret->GetCharacterIndex();
627 else
628 return -1;
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
645 return awt::Point(
646 sal_Int32(mnXOrigin),
647 sal_Int32(mnYOrigin + mnVerticalOffset));
650 awt::Size PresenterTextParagraph::GetSize() const
652 return awt::Size(
653 sal_Int32(mnWidth),
654 sal_Int32(GetTotalTextHeight()));
657 void PresenterTextParagraph::AddWord (
658 const double nWidth,
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 (
672 rpFont->mxFont,
673 sLineCandidate,
674 mnWritingMode));
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;
696 else
698 aLine.mnLineStartCellIndex = 0;
699 aLine.mnBaseLine = mnVerticalOffset + mnAscent;
701 sal_Int32 nCellIndex (aLine.mnLineStartCellIndex);
702 double nWidth (0);
703 for ( ; nCellIndex<sal_Int32(maCells.size()); ++nCellIndex)
705 const Cell& rCell (maCells[nCellIndex]);
706 if (rCell.mnCharacterIndex+rCell.mnCharacterCount > aLine.mnLineEndCharacterIndex)
707 break;
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();
741 else
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
757 switch(nTextType)
759 case AccessibleTextType::PARAGRAPH:
760 return TextSegment(
761 msParagraphText,
762 mnCharacterOffset,
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()));
772 if (nStart < nEnd)
773 return TextSegment(
774 msParagraphText.copy(nStart, nEnd-nStart),
775 nStart+mnCharacterOffset,
776 nEnd+mnCharacterOffset);
778 break;
780 case AccessibleTextType::WORD:
781 if (mxBreakIterator.is())
782 return GetWordTextSegment(nOffset, nIndex);
783 break;
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())
791 return TextSegment(
792 msParagraphText.copy(
793 iLine->mnLineStartCharacterIndex,
794 iLine->mnLineEndCharacterIndex - iLine->mnLineStartCharacterIndex),
795 iLine->mnLineStartCharacterIndex,
796 iLine->mnLineEndCharacterIndex);
799 break;
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(
822 msParagraphText,
823 nIndex,
824 lang::Locale(),
825 i18n::WordType::ANYWORD_IGNOREWHITESPACES,
826 true);
827 else if (nCurrentOffset < 0)
829 while (nCurrentOffset<0 && nCurrentIndex>0)
831 aWordBoundary = mxBreakIterator->previousWord(
832 msParagraphText,
833 nCurrentIndex,
834 lang::Locale(),
835 i18n::WordType::ANYWORD_IGNOREWHITESPACES);
836 nCurrentIndex = aWordBoundary.startPos;
837 ++nCurrentOffset;
840 else
842 while (nCurrentOffset>0 && nCurrentIndex<=GetCharacterCount())
844 aWordBoundary = mxBreakIterator->nextWord(
845 msParagraphText,
846 nCurrentIndex,
847 lang::Locale(),
848 i18n::WordType::ANYWORD_IGNOREWHITESPACES);
849 nCurrentIndex = aWordBoundary.endPos;
850 --nCurrentOffset;
854 return CreateTextSegment(aWordBoundary.startPos, aWordBoundary.endPos);
857 TextSegment PresenterTextParagraph::CreateTextSegment (
858 sal_Int32 nStartIndex,
859 sal_Int32 nEndIndex) const
861 if (nEndIndex <= nStartIndex)
862 return TextSegment(
863 OUString(),
864 nStartIndex,
865 nEndIndex);
866 else
867 return TextSegment(
868 msParagraphText.copy(nStartIndex, nEndIndex-nStartIndex),
869 nStartIndex,
870 nEndIndex);
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)
891 continue;
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;
912 double nBottom;
913 if (bCaretBox)
915 nBottom = nTop + mnLineHeight;
916 if (nCellIndex >= rLine.maCellBoxes.getLength())
917 nLeft = nRight-2;
918 if (nLeft < nX)
919 nLeft = nX;
920 nRight = nLeft+2;
922 else
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:
953 // continue looping.
954 break;
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;
973 break;
975 case text::WritingMode2::RL_TB:
976 nTextDirection = rendering::TextDirection::WEAK_RIGHT_TO_LEFT;
977 break;
979 default:
980 case text::WritingMode2::TB_RL:
981 case text::WritingMode2::TB_LR:
982 // Can not handle this. Use default and hope for the best.
983 break;
985 return nTextDirection;
988 bool PresenterTextParagraph::IsTextReferencePointLeft() const
990 return mnWritingMode != text::WritingMode2::RL_TB;
993 void PresenterTextParagraph::SetupCellArray (
994 const PresenterTheme::SharedFontDescriptor& rpFont)
996 maCells.clear();
998 if ( ! rpFont || ! rpFont->mxFont.is())
999 return;
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(
1008 msParagraphText,
1009 nPosition,
1010 lang::Locale(),
1011 i18n::CharacterIteratorMode::SKIPCELL,
1013 nIndex));
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(
1021 nPosition,
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()
1049 HideCaret();
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 (
1062 m_xContext,
1063 [this] (TimeValue const&) { return this->InvertCaret(); },
1064 CaretBlinkInterval,
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)
1090 return;
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)
1101 ShowCaret();
1102 else
1103 HideCaret();
1105 if (mnParagraphIndex >= 0)
1106 maInvalidator(maCaretBounds);
1108 if (maBroadcaster)
1109 maBroadcaster(
1110 nOldParagraphIndex,
1111 nOldCharacterIndex,
1112 mnParagraphIndex,
1113 mnCharacterIndex);
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();
1165 else
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 (
1180 rsParagraphText,
1181 mnLineStartCharacterIndex,
1182 mnLineEndCharacterIndex - mnLineStartCharacterIndex);
1184 mxLayoutedLine = rpFont->mxFont->createTextLayout(
1185 aContext,
1186 nTextDirection,
1191 } // end of namespace ::sdext::presenter
1193 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */