vcl: allow for overriding the default PDF rendering resolution
[LibreOffice.git] / sdext / source / presenter / PresenterTextView.cxx
blob583e11d7fbe5a8faeacce9151e295499a10bb713
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 <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
48 namespace {
49 sal_Int32 Signum (const sal_Int32 nValue)
51 if (nValue < 0)
52 return -1;
53 else if (nValue > 0)
54 return +1;
55 else
56 return 0;
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)
68 : mxCanvas(rxCanvas),
69 mxBreakIterator(),
70 mxScriptTypeDetector(),
71 maLocation(0,0),
72 maSize(0,0),
73 mpFont(),
74 maParagraphs(),
75 mpCaret(new PresenterTextCaret(
76 rxContext,
77 [this] (sal_Int32 const nParagraphIndex, sal_Int32 const nCharacterIndex)
78 { return this->GetCaretBounds(nParagraphIndex, nCharacterIndex); },
79 rInvalidator)),
80 mnLeftOffset(0),
81 mnTopOffset(0),
82 mbIsFormatPending(false),
83 maTextChangeBroadcaster()
85 Reference<lang::XMultiComponentFactory> xFactory =
86 rxContext->getServiceManager();
87 if ( ! xFactory.is())
88 return;
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",
98 rxContext),
99 UNO_QUERY_THROW);
102 void PresenterTextView::SetText (const Reference<text::XText>& rxText)
104 maParagraphs.clear();
106 Reference<container::XEnumerationAccess> xParagraphAccess (rxText, UNO_QUERY);
107 if ( ! xParagraphAccess.is())
108 return;
110 Reference<container::XEnumeration> xParagraphs =
111 xParagraphAccess->createEnumeration();
112 if ( ! xParagraphs.is())
113 return;
115 if ( ! mpFont || ! mpFont->PrepareFont(mxCanvas))
116 return;
118 sal_Int32 nCharacterCount (0);
119 while (xParagraphs->hasMoreElements())
121 SharedPresenterTextParagraph pParagraph (new PresenterTextParagraph(
122 maParagraphs.size(),
123 mxBreakIterator,
124 mxScriptTypeDetector,
125 Reference<text::XTextRange>(xParagraphs->nextElement(), UNO_QUERY),
126 mpCaret));
127 pParagraph->SetupCellArray(mpFont);
128 pParagraph->SetCharacterOffset(nCharacterCount);
129 nCharacterCount += pParagraph->GetCharacterCount();
130 maParagraphs.push_back(pParagraph);
133 if (mpCaret)
134 mpCaret->HideCaret();
136 RequestFormat();
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)
159 maSize = rSize;
160 RequestFormat();
163 double PresenterTextView::GetTotalTextHeight()
165 if (mbIsFormatPending)
167 if ( ! mpFont->PrepareFont(mxCanvas))
168 return 0;
169 Format();
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)
180 mpFont = rpFont;
181 RequestFormat();
184 void PresenterTextView::SetOffset(
185 const double nLeft,
186 const double nTop)
188 mnLeftOffset = nLeft;
189 mnTopOffset = nTop;
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)
199 if ( ! mpCaret)
200 return;
202 // When the caret has not been visible yet then move it to the beginning
203 // of the text.
204 if (mpCaret->GetParagraphIndex() < 0)
206 mpCaret->SetPosition(0,0);
207 return;
210 sal_Int32 nParagraphIndex (mpCaret->GetParagraphIndex());
211 sal_Int32 nCharacterIndex (mpCaret->GetCharacterIndex());
212 switch (nTextType)
214 default:
215 case AccessibleTextType::CHARACTER:
216 nCharacterIndex += nDistance;
217 break;
219 case AccessibleTextType::WORD:
221 sal_Int32 nRemainingDistance (nDistance);
222 while (nRemainingDistance != 0)
224 SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
225 if (pParagraph)
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)
235 nParagraphIndex = 0;
236 nCharacterIndex = 0;
237 nRemainingDistance = 0;
239 else if (sal_uInt32(nParagraphIndex) >= maParagraphs.size())
241 nParagraphIndex = maParagraphs.size()-1;
242 pParagraph = GetParagraph(nParagraphIndex);
243 if (pParagraph)
244 nCharacterIndex = pParagraph->GetCharacterCount();
245 nRemainingDistance = 0;
247 else
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);
254 if (pParagraph)
256 if (nDistance<0)
257 nCharacterIndex = pParagraph->GetCharacterCount();
258 else
259 nCharacterIndex = 0;
263 else
264 nRemainingDistance -= nDelta;
266 else
267 break;
269 break;
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())
281 return;
282 if ( ! mpFont->PrepareFont(mxCanvas))
283 return;
285 if (mbIsFormatPending)
286 Format();
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)
305 return;
307 const awt::Rectangle aClipBox(
308 nClipLeft,
309 nClipTop,
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),
317 xClipPolygon);
319 rendering::RenderState aRenderState (
320 geometry::AffineMatrix2D(1,0,nX, 0,1,nY),
321 nullptr,
322 Sequence<double>(4),
323 rendering::CompositeOperation::SOURCE);
324 PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor);
326 for (const auto& rxParagraph : maParagraphs)
328 rxParagraph->Paint(
329 mxCanvas,
330 maSize,
331 mpFont,
332 aViewState,
333 aRenderState,
334 mnTopOffset,
335 nClipTop,
336 nClipBottom);
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;
346 ++nParagraphIndex)
348 const SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
349 if ( ! pParagraph)
350 continue;
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(
357 aBox,
358 mxCanvas->getDevice()),
359 aViewState,
360 aRenderState);
363 PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor);
364 #endif
366 if (mpCaret && mpCaret->IsVisible())
368 mxCanvas->fillPolyPolygon (
369 PresenterGeometryHelper::CreatePolygon(
370 mpCaret->GetBounds(),
371 mxCanvas->getDevice()),
372 aViewState,
373 aRenderState);
377 const SharedPresenterTextCaret& PresenterTextView::GetCaret() const
379 return mpCaret;
382 awt::Rectangle PresenterTextView::GetCaretBounds (
383 sal_Int32 nParagraphIndex,
384 const sal_Int32 nCharacterIndex) const
386 SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
388 if (pParagraph)
389 return pParagraph->GetCharacterBounds(nCharacterIndex, true);
390 else
391 return awt::Rectangle(0,0,0,0);
394 //----- private ---------------------------------------------------------------
396 void PresenterTextView::RequestFormat()
398 mbIsFormatPending = true;
401 void PresenterTextView::Format()
403 mbIsFormatPending = false;
405 double nY (0);
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();
428 else
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)
440 : msParagraphText(),
441 mnParagraphIndex(nParagraphIndex),
442 mpCaret(rpCaret),
443 mxBreakIterator(rxBreakIterator),
444 mxScriptTypeDetector(rxScriptTypeDetector),
445 maLines(),
446 mnVerticalOffset(0),
447 mnXOrigin(0),
448 mnYOrigin(0),
449 mnWidth(0),
450 mnAscent(0),
451 mnDescent(0),
452 mnLineHeight(-1),
453 mnWritingMode (text::WritingMode2::LR_TB),
454 mnCharacterOffset(0),
455 maCells()
457 if (!rxTextRange.is())
458 return;
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)
484 return;
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();
495 nIndex<nCount;
496 ++nIndex)
498 Line& rLine (maLines[nIndex]);
499 rLine.ProvideLayoutedLine(msParagraphText, rpFont, nTextDirection);
501 #endif
503 for (sal_Int32 nIndex=0,nCount=maLines.size();
504 nIndex<nCount;
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)
512 continue;
513 else if (nLineTop > nClipBottom)
514 break;
515 rLine.ProvideLayoutedLine(msParagraphText, rpFont, nTextDirection);
517 rRenderState.AffineTransform.m12 = nSavedM12 + rLine.mnBaseLine;
519 rxCanvas->drawTextLayout (
520 rLine.mxLayoutedLine,
521 rViewState,
522 rRenderState);
524 rRenderState.AffineTransform.m12 = nSavedM12;
526 if ( ! IsTextReferencePointLeft())
527 rRenderState.AffineTransform.m02 -= rSize.Width;
530 void PresenterTextParagraph::Format (
531 const double nY,
532 const double nWidth,
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())
537 return;
538 if (nWidth<=0)
539 return;
540 if ( ! rpFont || ! rpFont->mxFont.is())
541 return;
543 sal_Int32 nPosition (0);
545 mnWidth = nWidth;
546 maLines.clear();
547 mnLineHeight = 0;
548 mnAscent = 0;
549 mnDescent = 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;
558 nPosition = 0;
559 i18n::Boundary aCurrentLine(0,0);
560 while (true)
562 const i18n::Boundary aWordBoundary = mxBreakIterator->nextWord(
563 msParagraphText,
564 nPosition,
565 lang::Locale(),
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)
578 break;
579 if (nPosition >= aWordBoundary.endPos)
580 break;
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.
598 if (nDistance < 0)
599 return 0;
600 else
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
611 // next word.
612 if (maWordBoundaries[nIndex] > nLocalCharacterIndex)
613 if (nDistance > 0)
614 --nIndex;
615 break;
619 nIndex += nDistance;
621 if (nIndex < 0)
622 return -1;
623 else if (sal_uInt32(nIndex)>=maWordBoundaries.size())
624 return -1;
625 else
626 return maWordBoundaries[nIndex];
629 sal_Int32 PresenterTextParagraph::GetCaretPosition() const
631 if (mpCaret && mpCaret->GetParagraphIndex()==mnParagraphIndex)
632 return mpCaret->GetCharacterIndex();
633 else
634 return -1;
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
651 return awt::Point(
652 sal_Int32(mnXOrigin),
653 sal_Int32(mnYOrigin + mnVerticalOffset));
656 awt::Size PresenterTextParagraph::GetSize() const
658 return awt::Size(
659 sal_Int32(mnWidth),
660 sal_Int32(GetTotalTextHeight()));
663 void PresenterTextParagraph::AddWord (
664 const double nWidth,
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 (
678 rpFont->mxFont,
679 sLineCandidate,
680 mnWritingMode));
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;
702 else
704 aLine.mnLineStartCellIndex = 0;
705 aLine.mnBaseLine = mnVerticalOffset + mnAscent;
707 sal_Int32 nCellIndex (aLine.mnLineStartCellIndex);
708 double nWidth (0);
709 for ( ; nCellIndex<sal_Int32(maCells.size()); ++nCellIndex)
711 const Cell& rCell (maCells[nCellIndex]);
712 if (rCell.mnCharacterIndex+rCell.mnCharacterCount > aLine.mnLineEndCharacterIndex)
713 break;
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();
747 else
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
763 switch(nTextType)
765 case AccessibleTextType::PARAGRAPH:
766 return TextSegment(
767 msParagraphText,
768 mnCharacterOffset,
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()));
778 if (nStart < nEnd)
779 return TextSegment(
780 msParagraphText.copy(nStart, nEnd-nStart),
781 nStart+mnCharacterOffset,
782 nEnd+mnCharacterOffset);
784 break;
786 case AccessibleTextType::WORD:
787 if (mxBreakIterator.is())
788 return GetWordTextSegment(nOffset, nIndex);
789 break;
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())
797 return TextSegment(
798 msParagraphText.copy(
799 iLine->mnLineStartCharacterIndex,
800 iLine->mnLineEndCharacterIndex - iLine->mnLineStartCharacterIndex),
801 iLine->mnLineStartCharacterIndex,
802 iLine->mnLineEndCharacterIndex);
805 break;
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(
828 msParagraphText,
829 nIndex,
830 lang::Locale(),
831 i18n::WordType::ANYWORD_IGNOREWHITESPACES,
832 true);
833 else if (nCurrentOffset < 0)
835 while (nCurrentOffset<0 && nCurrentIndex>0)
837 aWordBoundary = mxBreakIterator->previousWord(
838 msParagraphText,
839 nCurrentIndex,
840 lang::Locale(),
841 i18n::WordType::ANYWORD_IGNOREWHITESPACES);
842 nCurrentIndex = aWordBoundary.startPos;
843 ++nCurrentOffset;
846 else
848 while (nCurrentOffset>0 && nCurrentIndex<=GetCharacterCount())
850 aWordBoundary = mxBreakIterator->nextWord(
851 msParagraphText,
852 nCurrentIndex,
853 lang::Locale(),
854 i18n::WordType::ANYWORD_IGNOREWHITESPACES);
855 nCurrentIndex = aWordBoundary.endPos;
856 --nCurrentOffset;
860 return CreateTextSegment(aWordBoundary.startPos, aWordBoundary.endPos);
863 TextSegment PresenterTextParagraph::CreateTextSegment (
864 sal_Int32 nStartIndex,
865 sal_Int32 nEndIndex) const
867 if (nEndIndex <= nStartIndex)
868 return TextSegment(
869 OUString(),
870 nStartIndex,
871 nEndIndex);
872 else
873 return TextSegment(
874 msParagraphText.copy(nStartIndex, nEndIndex-nStartIndex),
875 nStartIndex,
876 nEndIndex);
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)
897 continue;
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;
918 double nBottom;
919 if (bCaretBox)
921 nBottom = nTop + mnLineHeight;
922 if (nCellIndex >= rLine.maCellBoxes.getLength())
923 nLeft = nRight-2;
924 if (nLeft < nX)
925 nLeft = nX;
926 nRight = nLeft+2;
928 else
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:
959 // continue looping.
960 break;
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;
979 break;
981 case text::WritingMode2::RL_TB:
982 nTextDirection = rendering::TextDirection::WEAK_RIGHT_TO_LEFT;
983 break;
985 default:
986 case text::WritingMode2::TB_RL:
987 case text::WritingMode2::TB_LR:
988 // Can not handle this. Use default and hope for the best.
989 break;
991 return nTextDirection;
994 bool PresenterTextParagraph::IsTextReferencePointLeft() const
996 return mnWritingMode != text::WritingMode2::RL_TB;
999 void PresenterTextParagraph::SetupCellArray (
1000 const PresenterTheme::SharedFontDescriptor& rpFont)
1002 maCells.clear();
1004 if ( ! rpFont || ! rpFont->mxFont.is())
1005 return;
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(
1014 msParagraphText,
1015 nPosition,
1016 lang::Locale(),
1017 i18n::CharacterIteratorMode::SKIPCELL,
1019 nIndex));
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(
1027 nPosition,
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),
1048 maBroadcaster(),
1049 maCaretBounds()
1053 PresenterTextCaret::~PresenterTextCaret()
1057 HideCaret();
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 (
1070 m_xContext,
1071 [this] (TimeValue const&) { return this->InvertCaret(); },
1072 CaretBlinkInterval,
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)
1098 return;
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)
1109 ShowCaret();
1110 else
1111 HideCaret();
1113 if (mnParagraphIndex >= 0)
1114 maInvalidator(maCaretBounds);
1116 if (maBroadcaster)
1117 maBroadcaster(
1118 nOldParagraphIndex,
1119 nOldCharacterIndex,
1120 mnParagraphIndex,
1121 mnCharacterIndex);
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),
1163 mxLayoutedLine(),
1164 mnBaseLine(0), mnWidth(0),
1165 maCellBoxes()
1169 void PresenterTextParagraph::Line::ProvideCellBoxes()
1171 if ( mnLineStartCharacterIndex < mnLineEndCharacterIndex && !maCellBoxes.hasElements() )
1173 if (mxLayoutedLine.is())
1174 maCellBoxes = mxLayoutedLine->queryInkMeasures();
1175 else
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 (
1190 rsParagraphText,
1191 mnLineStartCharacterIndex,
1192 mnLineEndCharacterIndex - mnLineStartCharacterIndex);
1194 mxLayoutedLine = rpFont->mxFont->createTextLayout(
1195 aContext,
1196 nTextDirection,
1201 } } // end of namespace ::sdext::presenter
1203 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */