LibreOffice Module sdext (master) 1
PresenterTextView.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
20#include "PresenterTextView.hxx"
23#include "PresenterTimer.hxx"
24
25#include <algorithm>
26#include <cmath>
27#include <numeric>
28
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 <tools/diagnose_ex.h>
41
42using namespace ::com::sun::star;
43using namespace ::com::sun::star::accessibility;
44using namespace ::com::sun::star::uno;
45
46const sal_Int64 CaretBlinkInterval = 500 * 1000 * 1000;
47
48//#define SHOW_CHARACTER_BOXES
49
50namespace {
51 sal_Int32 Signum (const sal_Int32 nValue)
52 {
53 if (nValue < 0)
54 return -1;
55 else if (nValue > 0)
56 return +1;
57 else
58 return 0;
59 }
60}
61
62namespace sdext::presenter {
63
64//===== PresenterTextView =====================================================
65
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)
81{
82 Reference<lang::XMultiComponentFactory> xFactory =
83 rxContext->getServiceManager();
84 if ( ! xFactory.is())
85 return;
86
87 // Create the break iterator that we use to break text into lines.
88 mxBreakIterator = i18n::BreakIterator::create(rxContext);
89
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);
97}
98
99void PresenterTextView::SetText (const Reference<text::XText>& rxText)
100{
101 maParagraphs.clear();
102
103 Reference<container::XEnumerationAccess> xParagraphAccess (rxText, UNO_QUERY);
104 if ( ! xParagraphAccess.is())
105 return;
106
107 Reference<container::XEnumeration> xParagraphs =
108 xParagraphAccess->createEnumeration();
109 if ( ! xParagraphs.is())
110 return;
111
112 if ( ! mpFont || ! mpFont->PrepareFont(mxCanvas))
113 return;
114
115 sal_Int32 nCharacterCount (0);
116 while (xParagraphs->hasMoreElements())
117 {
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);
128 }
129
130 if (mpCaret)
131 mpCaret->HideCaret();
132
133 RequestFormat();
134}
135
136void PresenterTextView::SetTextChangeBroadcaster (
137 const ::std::function<void ()>& rBroadcaster)
138{
139 maTextChangeBroadcaster = rBroadcaster;
140}
141
142void PresenterTextView::SetLocation (const css::geometry::RealPoint2D& rLocation)
143{
144 maLocation = rLocation;
145
146 for (auto& rxParagraph : maParagraphs)
147 {
148 rxParagraph->SetOrigin(
149 maLocation.X - mnLeftOffset,
150 maLocation.Y - mnTopOffset);
151 }
152}
153
154void PresenterTextView::SetSize (const css::geometry::RealSize2D& rSize)
155{
156 maSize = rSize;
157 RequestFormat();
158}
159
160double PresenterTextView::GetTotalTextHeight()
161{
162 if (mbIsFormatPending)
163 {
164 if ( ! mpFont->PrepareFont(mxCanvas))
165 return 0;
166 Format();
167 }
168
169 return std::accumulate(maParagraphs.begin(), maParagraphs.end(), double(0),
170 [](const double& nTotalHeight, const SharedPresenterTextParagraph& rxParagraph) {
171 return nTotalHeight + rxParagraph->GetTotalTextHeight();
172 });
173}
174
175void PresenterTextView::SetFont (const PresenterTheme::SharedFontDescriptor& rpFont)
176{
177 mpFont = rpFont;
178 RequestFormat();
179}
180
181void PresenterTextView::SetOffset(
182 const double nLeft,
183 const double nTop)
184{
185 mnLeftOffset = nLeft;
186 mnTopOffset = nTop;
187
188 // Trigger an update of the text origin stored at the individual paragraphs.
189 SetLocation(maLocation);
190}
191
192void PresenterTextView::MoveCaret (
193 const sal_Int32 nDistance,
194 const sal_Int16 nTextType)
195{
196 if ( ! mpCaret)
197 return;
198
199 // When the caret has not been visible yet then move it to the beginning
200 // of the text.
201 if (mpCaret->GetParagraphIndex() < 0)
202 {
203 mpCaret->SetPosition(0,0);
204 return;
205 }
206
207 sal_Int32 nParagraphIndex (mpCaret->GetParagraphIndex());
208 sal_Int32 nCharacterIndex (mpCaret->GetCharacterIndex());
209 switch (nTextType)
210 {
211 default:
212 case AccessibleTextType::CHARACTER:
213 nCharacterIndex += nDistance;
214 break;
215
216 case AccessibleTextType::WORD:
217 {
218 sal_Int32 nRemainingDistance (nDistance);
219 while (nRemainingDistance != 0)
220 {
221 SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
222 if (pParagraph)
223 {
224 const sal_Int32 nDelta (Signum(nDistance));
225 nCharacterIndex = pParagraph->GetWordBoundary(nCharacterIndex, nDelta);
226 if (nCharacterIndex < 0)
227 {
228 // Go to previous or next paragraph.
229 nParagraphIndex += nDelta;
230 if (nParagraphIndex < 0)
231 {
232 nParagraphIndex = 0;
233 nCharacterIndex = 0;
234 nRemainingDistance = 0;
235 }
236 else if (o3tl::make_unsigned(nParagraphIndex) >= maParagraphs.size())
237 {
238 nParagraphIndex = maParagraphs.size()-1;
239 pParagraph = GetParagraph(nParagraphIndex);
240 if (pParagraph)
241 nCharacterIndex = pParagraph->GetCharacterCount();
242 nRemainingDistance = 0;
243 }
244 else
245 {
246 nRemainingDistance -= nDelta;
247
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)
252 {
253 if (nDistance<0)
254 nCharacterIndex = pParagraph->GetCharacterCount();
255 else
256 nCharacterIndex = 0;
257 }
258 }
259 }
260 else
261 nRemainingDistance -= nDelta;
262 }
263 else
264 break;
265 }
266 break;
267 }
268 }
269
270 // Move the caret to the new position.
271 mpCaret->SetPosition(nParagraphIndex, nCharacterIndex);
272}
273
274void PresenterTextView::Paint (
275 const css::awt::Rectangle& rUpdateBox)
276{
277 if ( ! mxCanvas.is())
278 return;
279 if ( ! mpFont->PrepareFont(mxCanvas))
280 return;
281
282 if (mbIsFormatPending)
283 Format();
284
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;
303
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()));
311
312 const rendering::ViewState aViewState(
313 geometry::AffineMatrix2D(1,0,0, 0,1,0),
314 xClipPolygon);
315
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);
322
323 for (const auto& rxParagraph : maParagraphs)
324 {
325 rxParagraph->Paint(
326 mxCanvas,
327 maSize,
328 mpFont,
329 aViewState,
330 aRenderState,
331 mnTopOffset,
332 nClipTop,
333 nClipBottom);
334 }
335
336 aRenderState.AffineTransform.m02 = 0;
337 aRenderState.AffineTransform.m12 = 0;
338
339#ifdef SHOW_CHARACTER_BOXES
340 PresenterCanvasHelper::SetDeviceColor(aRenderState, 0x00808080);
341 for (sal_Int32 nParagraphIndex(0), nParagraphCount(GetParagraphCount());
342 nParagraphIndex<nParagraphCount;
343 ++nParagraphIndex)
344 {
345 const SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
346 if ( ! pParagraph)
347 continue;
348 for (sal_Int32 nCharacterIndex(0),nCharacterCount(pParagraph->GetCharacterCount());
349 nCharacterIndex<nCharacterCount; ++nCharacterIndex)
350 {
351 const awt::Rectangle aBox (pParagraph->GetCharacterBounds(nCharacterIndex, false));
352 mxCanvas->drawPolyPolygon (
353 PresenterGeometryHelper::CreatePolygon(
354 aBox,
355 mxCanvas->getDevice()),
356 aViewState,
357 aRenderState);
358 }
359 }
360 PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor);
361#endif
362
363 if (mpCaret && mpCaret->IsVisible())
364 {
365 mxCanvas->fillPolyPolygon (
366 PresenterGeometryHelper::CreatePolygon(
367 mpCaret->GetBounds(),
368 mxCanvas->getDevice()),
369 aViewState,
370 aRenderState);
371 }
372}
373
374const SharedPresenterTextCaret& PresenterTextView::GetCaret() const
375{
376 return mpCaret;
377}
378
379awt::Rectangle PresenterTextView::GetCaretBounds (
380 sal_Int32 nParagraphIndex,
381 const sal_Int32 nCharacterIndex) const
382{
383 SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
384
385 if (pParagraph)
386 return pParagraph->GetCharacterBounds(nCharacterIndex, true);
387 else
388 return awt::Rectangle(0,0,0,0);
389}
390
391//----- private ---------------------------------------------------------------
392
393void PresenterTextView::RequestFormat()
394{
395 mbIsFormatPending = true;
396}
397
398void PresenterTextView::Format()
399{
400 mbIsFormatPending = false;
401
402 double nY (0);
403 for (const auto& rxParagraph : maParagraphs)
404 {
405 rxParagraph->Format(nY, maSize.Width, mpFont);
406 nY += rxParagraph->GetTotalTextHeight();
407 }
408
409 if (maTextChangeBroadcaster)
410 maTextChangeBroadcaster();
411}
412
413sal_Int32 PresenterTextView::GetParagraphCount() const
414{
415 return maParagraphs.size();
416}
417
418SharedPresenterTextParagraph PresenterTextView::GetParagraph (
419 const sal_Int32 nParagraphIndex) const
420{
421 if (nParagraphIndex < 0)
423 else if (o3tl::make_unsigned(nParagraphIndex)>=maParagraphs.size())
425 else
426 return maParagraphs[nParagraphIndex];
427}
428
429//===== PresenterTextParagraph ================================================
430
431PresenterTextParagraph::PresenterTextParagraph (
432 const sal_Int32 nParagraphIndex,
433 const Reference<i18n::XBreakIterator>& rxBreakIterator,
434 const Reference<i18n::XScriptTypeDetector>& rxScriptTypeDetector,
435 const Reference<text::XTextRange>& rxTextRange,
437 : mnParagraphIndex(nParagraphIndex),
438 mpCaret(std::move(xCaret)),
439 mxBreakIterator(rxBreakIterator),
440 mxScriptTypeDetector(rxScriptTypeDetector),
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)
450{
451 if (!rxTextRange.is())
452 return;
453
454 Reference<beans::XPropertySet> xProperties (rxTextRange, UNO_QUERY);
455 try
456 {
457 xProperties->getPropertyValue("WritingMode") >>= mnWritingMode;
458 }
459 catch(beans::UnknownPropertyException&)
460 {
461 // Ignore the exception. Use the default value.
462 }
463
464 msParagraphText = rxTextRange->getString();
465}
466
468 const Reference<rendering::XCanvas>& rxCanvas,
469 const geometry::RealSize2D& rSize,
471 const rendering::ViewState& rViewState,
472 rendering::RenderState& rRenderState,
473 const double nTopOffset,
474 const double nClipTop,
475 const double nClipBottom)
476{
477 if (mnLineHeight <= 0)
478 return;
479
480 sal_Int8 nTextDirection (GetTextDirection());
481
482 const double nSavedM12 (rRenderState.AffineTransform.m12);
483
485 rRenderState.AffineTransform.m02 += rSize.Width;
486
487#ifdef SHOW_CHARACTER_BOXES
488 for (sal_Int32 nIndex=0,nCount=maLines.size();
490 ++nIndex)
491 {
492 Line& rLine (maLines[nIndex]);
493 rLine.ProvideLayoutedLine(msParagraphText, rpFont, nTextDirection);
494 }
495#endif
496
497 for (sal_Int32 nIndex=0,nCount=maLines.size();
499 ++nIndex, rRenderState.AffineTransform.m12 += mnLineHeight)
500 {
501 Line& rLine (maLines[nIndex]);
502
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);
510
511 rRenderState.AffineTransform.m12 = nSavedM12 + rLine.mnBaseLine;
512
513 rxCanvas->drawTextLayout (
514 rLine.mxLayoutedLine,
515 rViewState,
516 rRenderState);
517 }
518 rRenderState.AffineTransform.m12 = nSavedM12;
519
521 rRenderState.AffineTransform.m02 -= rSize.Width;
522}
523
525 const double nY,
526 const double nWidth,
528{
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;
536
537 sal_Int32 nPosition (0);
538
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);
547
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)
555 {
556 const i18n::Boundary aWordBoundary = mxBreakIterator->nextWord(
558 nPosition,
559 lang::Locale(),
560 i18n::WordType::ANYWORD_IGNOREWHITESPACES);
561 AddWord(nWidth, aCurrentLine, aWordBoundary.startPos, rpFont);
562
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);
567
568 if (aWordBoundary.endPos>aWordBoundary.startPos)
569 AddWord(nWidth, aCurrentLine, aWordBoundary.endPos, rpFont);
570
571 if (aWordBoundary.startPos<0 || aWordBoundary.endPos<0)
572 break;
573 if (nPosition >= aWordBoundary.endPos)
574 break;
575 nPosition = aWordBoundary.endPos;
576 }
577
578 if (aCurrentLine.endPos>aCurrentLine.startPos)
579 AddLine(aCurrentLine);
580
581}
582
584 const sal_Int32 nLocalCharacterIndex,
585 const sal_Int32 nDistance)
586{
587 OSL_ASSERT(nDistance==-1 || nDistance==+1);
588
589 if (nLocalCharacterIndex < 0)
590 {
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();
596 }
597
598 sal_Int32 nIndex (0);
599 for (sal_Int32 nCount (maWordBoundaries.size()); nIndex<nCount; ++nIndex)
600 {
601 if (maWordBoundaries[nIndex] >= nLocalCharacterIndex)
602 {
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;
610 }
611 }
612
613 nIndex += nDistance;
614
615 if (nIndex < 0)
616 return -1;
618 return -1;
619 else
620 return maWordBoundaries[nIndex];
621}
622
624{
625 if (mpCaret && mpCaret->GetParagraphIndex()==mnParagraphIndex)
626 return mpCaret->GetCharacterIndex();
627 else
628 return -1;
629}
630
631void PresenterTextParagraph::SetCaretPosition (const sal_Int32 nPosition) const
632{
633 if (mpCaret && mpCaret->GetParagraphIndex()==mnParagraphIndex)
634 return mpCaret->SetPosition(mnParagraphIndex, nPosition);
635}
636
637void PresenterTextParagraph::SetOrigin (const double nXOrigin, const double nYOrigin)
638{
639 mnXOrigin = nXOrigin;
640 mnYOrigin = nYOrigin;
641}
642
644{
645 return awt::Point(
646 sal_Int32(mnXOrigin),
647 sal_Int32(mnYOrigin + mnVerticalOffset));
648}
649
651{
652 return awt::Size(
653 sal_Int32(mnWidth),
654 sal_Int32(GetTotalTextHeight()));
655}
656
658 const double nWidth,
659 i18n::Boundary& rCurrentLine,
660 const sal_Int32 nWordBoundary,
662{
663 sal_Int32 nLineStart (0);
664 if ( ! maLines.empty())
665 nLineStart = rCurrentLine.startPos;
666
667 const OUString sLineCandidate (
668 msParagraphText.copy(nLineStart, nWordBoundary-nLineStart));
669
670 css::geometry::RealRectangle2D aLineBox (
672 rpFont->mxFont,
673 sLineCandidate,
675 const double nLineWidth (aLineBox.X2 - aLineBox.X1);
676
677 if (nLineWidth >= nWidth)
678 {
679 // Add new line with a single word (so far).
680 AddLine(rCurrentLine);
681 }
682 rCurrentLine.endPos = nWordBoundary;
683}
684
686 i18n::Boundary& rCurrentLine)
687{
688 Line aLine (rCurrentLine.startPos, rCurrentLine.endPos);
689
690 // Find the start and end of the line with respect to cells.
691 if (!maLines.empty())
692 {
693 aLine.mnLineStartCellIndex = maLines.back().mnLineEndCellIndex;
694 aLine.mnBaseLine = maLines.back().mnBaseLine + mnLineHeight;
695 }
696 else
697 {
698 aLine.mnLineStartCellIndex = 0;
700 }
701 sal_Int32 nCellIndex (aLine.mnLineStartCellIndex);
702 double nWidth (0);
703 for ( ; nCellIndex<sal_Int32(maCells.size()); ++nCellIndex)
704 {
705 const Cell& rCell (maCells[nCellIndex]);
707 break;
708 nWidth += rCell.mnCellWidth;
709 }
710 aLine.mnLineEndCellIndex = nCellIndex;
711 aLine.mnWidth = nWidth;
712
713 maLines.push_back(aLine);
714
715 rCurrentLine.startPos = rCurrentLine.endPos;
716}
717
719{
720 return maLines.size() * mnLineHeight;
721}
722
723void PresenterTextParagraph::SetCharacterOffset (const sal_Int32 nCharacterOffset)
724{
725 mnCharacterOffset = nCharacterOffset;
726}
727
729{
730 return msParagraphText.getLength();
731}
732
734 const sal_Int32 nGlobalCharacterIndex) const
735{
736 if (nGlobalCharacterIndex<mnCharacterOffset
737 || nGlobalCharacterIndex>=mnCharacterOffset+msParagraphText.getLength())
738 {
739 return sal_Unicode();
740 }
741 else
742 {
743 return msParagraphText[nGlobalCharacterIndex - mnCharacterOffset];
744 }
745}
746
747const OUString& PresenterTextParagraph::GetText() const
748{
749 return msParagraphText;
750}
751
753 const sal_Int32 nOffset,
754 const sal_Int32 nIndex,
755 const sal_Int16 nTextType) const
756{
757 switch(nTextType)
758 {
759 case AccessibleTextType::PARAGRAPH:
760 return TextSegment(
764
765 case AccessibleTextType::SENTENCE:
766 if (mxBreakIterator.is())
767 {
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);
777 }
778 break;
779
780 case AccessibleTextType::WORD:
781 if (mxBreakIterator.is())
782 return GetWordTextSegment(nOffset, nIndex);
783 break;
784
785 case AccessibleTextType::LINE:
786 {
787 auto iLine = std::find_if(maLines.begin(), maLines.end(),
788 [nIndex](const Line& rLine) { return nIndex < rLine.mnLineEndCharacterIndex; });
789 if (iLine != maLines.end())
790 {
791 return TextSegment(
792 msParagraphText.copy(
793 iLine->mnLineStartCharacterIndex,
794 iLine->mnLineEndCharacterIndex - iLine->mnLineStartCharacterIndex),
795 iLine->mnLineStartCharacterIndex,
796 iLine->mnLineEndCharacterIndex);
797 }
798 }
799 break;
800
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);
807 }
808
809 return TextSegment(OUString(), 0,0);
810}
811
813 const sal_Int32 nOffset,
814 const sal_Int32 nIndex) const
815{
816 sal_Int32 nCurrentOffset (nOffset);
817 sal_Int32 nCurrentIndex (nIndex);
818
819 i18n::Boundary aWordBoundary;
820 if (nCurrentOffset == 0)
821 aWordBoundary = mxBreakIterator->getWordBoundary(
823 nIndex,
824 lang::Locale(),
825 i18n::WordType::ANYWORD_IGNOREWHITESPACES,
826 true);
827 else if (nCurrentOffset < 0)
828 {
829 while (nCurrentOffset<0 && nCurrentIndex>0)
830 {
831 aWordBoundary = mxBreakIterator->previousWord(
833 nCurrentIndex,
834 lang::Locale(),
835 i18n::WordType::ANYWORD_IGNOREWHITESPACES);
836 nCurrentIndex = aWordBoundary.startPos;
837 ++nCurrentOffset;
838 }
839 }
840 else
841 {
842 while (nCurrentOffset>0 && nCurrentIndex<=GetCharacterCount())
843 {
844 aWordBoundary = mxBreakIterator->nextWord(
846 nCurrentIndex,
847 lang::Locale(),
848 i18n::WordType::ANYWORD_IGNOREWHITESPACES);
849 nCurrentIndex = aWordBoundary.endPos;
850 --nCurrentOffset;
851 }
852 }
853
854 return CreateTextSegment(aWordBoundary.startPos, aWordBoundary.endPos);
855}
856
858 sal_Int32 nStartIndex,
859 sal_Int32 nEndIndex) const
860{
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);
871}
872
874 sal_Int32 nGlobalCharacterIndex,
875 const bool bCaretBox)
876{
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)
885 {
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;
892
893 rLine.ProvideCellBoxes();
894
895 const sal_Int32 nCellIndex (nGlobalCharacterIndex - rLine.mnLineStartCharacterIndex);
896
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)]);
902
903 double nLeft = nX + rCellBox.X1;
904 double nRight = nX + rCellBox.X2;
905 if (nTextDirection == rendering::TextDirection::WEAK_RIGHT_TO_LEFT)
906 {
907 const double nOldRight (nRight);
908 nRight = rLine.mnWidth - nLeft;
909 nLeft = rLine.mnWidth - nOldRight;
910 }
911 double nTop = nY - mnAscent;
912 double nBottom;
913 if (bCaretBox)
914 {
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;
921 }
922 else
923 {
924 nBottom = nTop + mnAscent + mnDescent;
925 }
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));
930
931 return awt::Rectangle(nX1,nY1,nX2-nX1+1,nY2-nY1+1);
932 }
933
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);
938}
939
941{
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)
946 {
947 const sal_Int16 nScriptDirection (
948 mxScriptTypeDetector->getScriptDirection(
949 msParagraphText, nPosition, i18n::ScriptDirection::NEUTRAL));
950 switch (nScriptDirection)
951 {
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;
957
958 case i18n::ScriptDirection::RIGHT_TO_LEFT:
959 return rendering::TextDirection::WEAK_RIGHT_TO_LEFT;
960 }
961
962 nPosition = mxScriptTypeDetector->endOfScriptDirection(
963 msParagraphText, nPosition, nScriptDirection);
964 }
965
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)
970 {
971 case text::WritingMode2::LR_TB:
972 nTextDirection = rendering::TextDirection::WEAK_LEFT_TO_RIGHT;
973 break;
974
975 case text::WritingMode2::RL_TB:
976 nTextDirection = rendering::TextDirection::WEAK_RIGHT_TO_LEFT;
977 break;
978
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;
984 }
985 return nTextDirection;
986}
987
989{
990 return mnWritingMode != text::WritingMode2::RL_TB;
991}
992
995{
996 maCells.clear();
997
998 if ( ! rpFont || ! rpFont->mxFont.is())
999 return;
1000
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)
1006 {
1007 const sal_Int32 nNewPosition (mxBreakIterator->nextCharacters(
1009 nPosition,
1010 lang::Locale(),
1011 i18n::CharacterIteratorMode::SKIPCELL,
1012 1,
1013 nIndex));
1014
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());
1019
1020 maCells.emplace_back(
1021 nPosition,
1022 nNewPosition-nPosition,
1023 aCharacterBox.X2-aCharacterBox.X1);
1024
1025 nPosition = nNewPosition;
1026 }
1027}
1028
1029//===== PresenterTextCaret ================================================----
1030
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))
1042{
1043}
1044
1046{
1047 try
1048 {
1049 HideCaret();
1050 }
1051 catch (uno::Exception const&)
1052 {
1053 TOOLS_WARN_EXCEPTION("sdext.presenter", "unexpected exception in ~PresenterTextCaret");
1054 }
1055}
1056
1058{
1059 if (mnCaretBlinkTaskId == 0)
1060 {
1062 m_xContext,
1063 [this] (TimeValue const&) { return this->InvertCaret(); },
1066 }
1067 mbIsCaretVisible = true;
1068}
1069
1071{
1072 if (mnCaretBlinkTaskId != 0)
1073 {
1076 }
1077 mbIsCaretVisible = false;
1078 // Reset the caret position.
1079 mnParagraphIndex = -1;
1080 mnCharacterIndex = -1;
1081}
1082
1083
1085 const sal_Int32 nParagraphIndex,
1086 const sal_Int32 nCharacterIndex)
1087{
1088 if (mnParagraphIndex == nParagraphIndex
1089 && mnCharacterIndex == nCharacterIndex)
1090 return;
1091
1092 if (mnParagraphIndex >= 0)
1094
1095 const sal_Int32 nOldParagraphIndex (mnParagraphIndex);
1096 const sal_Int32 nOldCharacterIndex (mnCharacterIndex);
1097 mnParagraphIndex = nParagraphIndex;
1098 mnCharacterIndex = nCharacterIndex;
1100 if (mnParagraphIndex >= 0)
1101 ShowCaret();
1102 else
1103 HideCaret();
1104
1105 if (mnParagraphIndex >= 0)
1107
1108 if (maBroadcaster)
1110 nOldParagraphIndex,
1111 nOldCharacterIndex,
1114}
1115
1116
1118 const ::std::function<void (sal_Int32,sal_Int32,sal_Int32,sal_Int32)>& rBroadcaster)
1119{
1120 maBroadcaster = rBroadcaster;
1121}
1122
1123const css::awt::Rectangle& PresenterTextCaret::GetBounds() const
1124{
1125 return maCaretBounds;
1126}
1127
1129{
1131 if (mnParagraphIndex >= 0)
1133}
1134
1135//===== PresenterTextParagraph::Cell ==========================================
1136
1138 const sal_Int32 nCharacterIndex,
1139 const sal_Int32 nCharacterCount,
1140 const double nCellWidth)
1141 : mnCharacterIndex(nCharacterIndex),
1142 mnCharacterCount(nCharacterCount),
1143 mnCellWidth(nCellWidth)
1144{
1145}
1146
1147//===== PresenterTextParagraph::Line ==========================================
1148
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)
1156{
1157}
1158
1160{
1161 if ( mnLineStartCharacterIndex < mnLineEndCharacterIndex && !maCellBoxes.hasElements() )
1162 {
1163 if (mxLayoutedLine.is())
1164 maCellBoxes = mxLayoutedLine->queryInkMeasures();
1165 else
1166 {
1167 OSL_ASSERT(mxLayoutedLine.is());
1168 }
1169 }
1170}
1171
1173 const OUString& rsParagraphText,
1175 const sal_Int8 nTextDirection)
1176{
1177 if ( ! mxLayoutedLine.is())
1178 {
1179 const rendering::StringContext aContext (
1180 rsParagraphText,
1181 mnLineStartCharacterIndex,
1182 mnLineEndCharacterIndex - mnLineStartCharacterIndex);
1183
1184 mxLayoutedLine = rpFont->mxFont->createTextLayout(
1185 aContext,
1186 nTextDirection,
1187 0);
1188 }
1189}
1190
1191} // end of namespace ::sdext::presenter
1192
1193/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
sal_Int32 nLineWidth
geometry::RealSize2D maSize
double mnVerticalOffset
PresenterTheme::SharedFontDescriptor mpFont
const sal_Int64 CaretBlinkInterval
Reference< rendering::XCanvas > mxCanvas
awt::Point maLocation
static css::geometry::RealRectangle2D GetTextBoundingBox(const css::uno::Reference< css::rendering::XCanvasFont > &rxFont, const OUString &rsText, const sal_Int8=css::rendering::TextDirection::WEAK_LEFT_TO_RIGHT)
const css::awt::Rectangle & GetBounds() const
css::uno::Reference< css::uno::XComponentContext > const & m_xContext
void SetCaretMotionBroadcaster(const ::std::function< void(sal_Int32, sal_Int32, sal_Int32, sal_Int32)> &rBroadcaster)
Set a (possibly empty) functor that broadcasts changes of the caret position.
const ::std::function< css::awt::Rectangle(const sal_Int32, const sal_Int32)> maCharacterBoundsAccess
void SetPosition(const sal_Int32 nParagraphIndex, const sal_Int32 nCharacterIndex)
PresenterTextCaret(css::uno::Reference< css::uno::XComponentContext > const &xContext, ::std::function< css::awt::Rectangle(const sal_Int32, const sal_Int32)> aCharacterBoundsAccess, ::std::function< void(const css::awt::Rectangle &)> aInvalidator)
::std::function< void(sal_Int32, sal_Int32, sal_Int32, sal_Int32)> maBroadcaster
const ::std::function< void(const css::awt::Rectangle &)> maInvalidator
A portion of a string that encodes one unicode cell.
Cell(const sal_Int32 nCharacterIndex, const sal_Int32 nCharacterCount, const double nCellWidth)
Line(const sal_Int32 nLineStartCharacterIndex, const sal_Int32 nLineEndCharacterIndex)
css::uno::Sequence< css::geometry::RealRectangle2D > maCellBoxes
css::uno::Reference< css::rendering::XTextLayout > mxLayoutedLine
void ProvideLayoutedLine(const OUString &rsParagraphText, const PresenterTheme::SharedFontDescriptor &rpFont, const sal_Int8 nTextDirection)
sal_Unicode GetCharacter(const sal_Int32 nGlobalCharacterIndex) const
void SetCaretPosition(const sal_Int32 nPosition) const
css::accessibility::TextSegment GetTextSegment(const sal_Int32 nOffset, const sal_Int32 nGlobalCharacterIndex, const sal_Int16 nTextType) const
css::uno::Reference< css::i18n::XScriptTypeDetector > mxScriptTypeDetector
void AddLine(css::i18n::Boundary &rCurrentLine)
void SetupCellArray(const PresenterTheme::SharedFontDescriptor &rpFont)
sal_Int32 mnCharacterOffset
The index of the first character in this paragraph with respect to the whole text.
sal_Int32 GetWordBoundary(const sal_Int32 nLocalCharacterIndex, const sal_Int32 nDistance)
css::accessibility::TextSegment CreateTextSegment(sal_Int32 nStartIndex, sal_Int32 nEndIndex) const
void SetCharacterOffset(const sal_Int32 nCharacterOffset)
css::uno::Reference< css::i18n::XBreakIterator > mxBreakIterator
void AddWord(const double nWidth, css::i18n::Boundary &rCurrentLine, const sal_Int32 nWordBoundary, const PresenterTheme::SharedFontDescriptor &rpFont)
css::awt::Rectangle GetCharacterBounds(sal_Int32 nGlobalCharacterIndex, const bool bCaretBox)
css::accessibility::TextSegment GetWordTextSegment(const sal_Int32 nOffset, const sal_Int32 nIndex) const
void SetOrigin(const double nXOrigin, const double nYOrigin)
void Paint(const css::uno::Reference< css::rendering::XCanvas > &rxCanvas, const css::geometry::RealSize2D &rSize, const PresenterTheme::SharedFontDescriptor &rpFont, const css::rendering::ViewState &rViewState, css::rendering::RenderState &rRenderState, const double nTopOffset, const double nClipTop, const double nClipBottom)
void Format(const double nY, const double nWidth, const PresenterTheme::SharedFontDescriptor &rpFont)
PresenterTextView(const css::uno::Reference< css::uno::XComponentContext > &rxContext, const css::uno::Reference< css::rendering::XCanvas > &rxCanvas, const ::std::function< void(const css::awt::Rectangle &)> &rInvalidator)
std::shared_ptr< FontDescriptor > SharedFontDescriptor
static void CancelTask(const sal_Int32 nTaskId)
static sal_Int32 ScheduleRepeatedTask(const css::uno::Reference< css::uno::XComponentContext > &xContext, const Task &rTask, const sal_Int64 nFirst, const sal_Int64 nInterval)
Schedule a task to be executed repeatedly.
int nCount
#define TOOLS_WARN_EXCEPTION(area, stream)
Reference< XSingleServiceFactory > xFactory
sal_Int16 nValue
sal_Int32 nIndex
def text(shape, orig_st)
constexpr std::enable_if_t< std::is_signed_v< T >, std::make_unsigned_t< T > > make_unsigned(T value)
std::shared_ptr< T > make_shared(Args &&... args)
const sal_Int32 nCellWidth
std::shared_ptr< PresenterTextParagraph > SharedPresenterTextParagraph
std::shared_ptr< PresenterTextCaret > SharedPresenterTextCaret
double mnWidth
sal_Int16 mnParagraphIndex
sal_uInt16 sal_Unicode
signed char sal_Int8
const uno::Reference< uno::XComponentContext > m_xContext
Definition: wrapper.cxx:144