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 <tools/diagnose_ex.h>
40 
41 using namespace ::com::sun::star;
42 using namespace ::com::sun::star::accessibility;
43 using namespace ::com::sun::star::uno;
44 
45 const static sal_Int64 CaretBlinkInterval = 500 * 1000 * 1000;
46 
47 //#define SHOW_CHARACTER_BOXES
48 
49 namespace {
50  sal_Int32 Signum (const sal_Int32 nValue)
51  {
52  if (nValue < 0)
53  return -1;
54  else if (nValue > 0)
55  return +1;
56  else
57  return 0;
58  }
59 }
60 
61 namespace sdext::presenter {
62 
63 //===== PresenterTextView =====================================================
64 
66  const Reference<XComponentContext>& rxContext,
67  const Reference<rendering::XCanvas>& rxCanvas,
68  const ::std::function<void (const css::awt::Rectangle&)>& rInvalidator)
69  : mxCanvas(rxCanvas),
70  mxBreakIterator(),
71  mxScriptTypeDetector(),
72  maLocation(0,0),
73  maSize(0,0),
74  mpFont(),
75  maParagraphs(),
76  mpCaret(std::make_shared<PresenterTextCaret>(
77  rxContext,
78  [this] (sal_Int32 const nParagraphIndex, sal_Int32 const nCharacterIndex)
79  { return this->GetCaretBounds(nParagraphIndex, nCharacterIndex); },
80  rInvalidator)),
81  mnLeftOffset(0),
82  mnTopOffset(0),
83  mbIsFormatPending(false),
84  maTextChangeBroadcaster()
85 {
86  Reference<lang::XMultiComponentFactory> xFactory =
87  rxContext->getServiceManager();
88  if ( ! xFactory.is())
89  return;
90 
91  // Create the break iterator that we use to break text into lines.
92  mxBreakIterator = i18n::BreakIterator::create(rxContext);
93 
94  // Create the script type detector that is used to split paragraphs into
95  // portions of the same text direction.
96  mxScriptTypeDetector.set(
97  xFactory->createInstanceWithContext(
98  "com.sun.star.i18n.ScriptTypeDetector",
99  rxContext),
100  UNO_QUERY_THROW);
101 }
102 
103 void PresenterTextView::SetText (const Reference<text::XText>& rxText)
104 {
105  maParagraphs.clear();
106 
107  Reference<container::XEnumerationAccess> xParagraphAccess (rxText, UNO_QUERY);
108  if ( ! xParagraphAccess.is())
109  return;
110 
111  Reference<container::XEnumeration> xParagraphs =
112  xParagraphAccess->createEnumeration();
113  if ( ! xParagraphs.is())
114  return;
115 
116  if ( ! mpFont || ! mpFont->PrepareFont(mxCanvas))
117  return;
118 
119  sal_Int32 nCharacterCount (0);
120  while (xParagraphs->hasMoreElements())
121  {
122  SharedPresenterTextParagraph pParagraph = std::make_shared<PresenterTextParagraph>(
123  maParagraphs.size(),
124  mxBreakIterator,
125  mxScriptTypeDetector,
126  Reference<text::XTextRange>(xParagraphs->nextElement(), UNO_QUERY),
127  mpCaret);
128  pParagraph->SetupCellArray(mpFont);
129  pParagraph->SetCharacterOffset(nCharacterCount);
130  nCharacterCount += pParagraph->GetCharacterCount();
131  maParagraphs.push_back(pParagraph);
132  }
133 
134  if (mpCaret)
135  mpCaret->HideCaret();
136 
137  RequestFormat();
138 }
139 
140 void PresenterTextView::SetTextChangeBroadcaster (
141  const ::std::function<void ()>& rBroadcaster)
142 {
143  maTextChangeBroadcaster = rBroadcaster;
144 }
145 
146 void PresenterTextView::SetLocation (const css::geometry::RealPoint2D& rLocation)
147 {
148  maLocation = rLocation;
149 
150  for (auto& rxParagraph : maParagraphs)
151  {
152  rxParagraph->SetOrigin(
153  maLocation.X - mnLeftOffset,
154  maLocation.Y - mnTopOffset);
155  }
156 }
157 
158 void PresenterTextView::SetSize (const css::geometry::RealSize2D& rSize)
159 {
160  maSize = rSize;
161  RequestFormat();
162 }
163 
164 double PresenterTextView::GetTotalTextHeight()
165 {
166  if (mbIsFormatPending)
167  {
168  if ( ! mpFont->PrepareFont(mxCanvas))
169  return 0;
170  Format();
171  }
172 
173  return std::accumulate(maParagraphs.begin(), maParagraphs.end(), double(0),
174  [](const double& nTotalHeight, const SharedPresenterTextParagraph& rxParagraph) {
175  return nTotalHeight + rxParagraph->GetTotalTextHeight();
176  });
177 }
178 
179 void PresenterTextView::SetFont (const PresenterTheme::SharedFontDescriptor& rpFont)
180 {
181  mpFont = rpFont;
182  RequestFormat();
183 }
184 
185 void PresenterTextView::SetOffset(
186  const double nLeft,
187  const double nTop)
188 {
189  mnLeftOffset = nLeft;
190  mnTopOffset = nTop;
191 
192  // Trigger an update of the text origin stored at the individual paragraphs.
193  SetLocation(maLocation);
194 }
195 
196 void PresenterTextView::MoveCaret (
197  const sal_Int32 nDistance,
198  const sal_Int16 nTextType)
199 {
200  if ( ! mpCaret)
201  return;
202 
203  // When the caret has not been visible yet then move it to the beginning
204  // of the text.
205  if (mpCaret->GetParagraphIndex() < 0)
206  {
207  mpCaret->SetPosition(0,0);
208  return;
209  }
210 
211  sal_Int32 nParagraphIndex (mpCaret->GetParagraphIndex());
212  sal_Int32 nCharacterIndex (mpCaret->GetCharacterIndex());
213  switch (nTextType)
214  {
215  default:
216  case AccessibleTextType::CHARACTER:
217  nCharacterIndex += nDistance;
218  break;
219 
220  case AccessibleTextType::WORD:
221  {
222  sal_Int32 nRemainingDistance (nDistance);
223  while (nRemainingDistance != 0)
224  {
225  SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
226  if (pParagraph)
227  {
228  const sal_Int32 nDelta (Signum(nDistance));
229  nCharacterIndex = pParagraph->GetWordBoundary(nCharacterIndex, nDelta);
230  if (nCharacterIndex < 0)
231  {
232  // Go to previous or next paragraph.
233  nParagraphIndex += nDelta;
234  if (nParagraphIndex < 0)
235  {
236  nParagraphIndex = 0;
237  nCharacterIndex = 0;
238  nRemainingDistance = 0;
239  }
240  else if (o3tl::make_unsigned(nParagraphIndex) >= maParagraphs.size())
241  {
242  nParagraphIndex = maParagraphs.size()-1;
243  pParagraph = GetParagraph(nParagraphIndex);
244  if (pParagraph)
245  nCharacterIndex = pParagraph->GetCharacterCount();
246  nRemainingDistance = 0;
247  }
248  else
249  {
250  nRemainingDistance -= nDelta;
251 
252  // Move caret one character to the end of
253  // the previous or the start of the next paragraph.
254  pParagraph = GetParagraph(nParagraphIndex);
255  if (pParagraph)
256  {
257  if (nDistance<0)
258  nCharacterIndex = pParagraph->GetCharacterCount();
259  else
260  nCharacterIndex = 0;
261  }
262  }
263  }
264  else
265  nRemainingDistance -= nDelta;
266  }
267  else
268  break;
269  }
270  break;
271  }
272  }
273 
274  // Move the caret to the new position.
275  mpCaret->SetPosition(nParagraphIndex, nCharacterIndex);
276 }
277 
278 void PresenterTextView::Paint (
279  const css::awt::Rectangle& rUpdateBox)
280 {
281  if ( ! mxCanvas.is())
282  return;
283  if ( ! mpFont->PrepareFont(mxCanvas))
284  return;
285 
286  if (mbIsFormatPending)
287  Format();
288 
289  // Setup the clipping rectangle. Horizontally we make it a little
290  // larger to allow characters (and the caret) to stick out of their
291  // bounding boxes. This can happen on some characters (like the
292  // uppercase J) for typographical reasons.
293  const sal_Int32 nAdditionalLeftBorder (10);
294  const sal_Int32 nAdditionalRightBorder (5);
295  double nX (maLocation.X - mnLeftOffset);
296  double nY (maLocation.Y - mnTopOffset);
297  const sal_Int32 nClipLeft (::std::max(
298  PresenterGeometryHelper::Round(maLocation.X)-nAdditionalLeftBorder, rUpdateBox.X));
299  const sal_Int32 nClipTop (::std::max(
300  PresenterGeometryHelper::Round(maLocation.Y), rUpdateBox.Y));
301  const sal_Int32 nClipRight (::std::min(
302  PresenterGeometryHelper::Round(maLocation.X+maSize.Width)+nAdditionalRightBorder, rUpdateBox.X+rUpdateBox.Width));
303  const sal_Int32 nClipBottom (::std::min(
304  PresenterGeometryHelper::Round(maLocation.Y+maSize.Height), rUpdateBox.Y+rUpdateBox.Height));
305  if (nClipLeft>=nClipRight || nClipTop>=nClipBottom)
306  return;
307 
308  const awt::Rectangle aClipBox(
309  nClipLeft,
310  nClipTop,
311  nClipRight - nClipLeft,
312  nClipBottom - nClipTop);
313  Reference<rendering::XPolyPolygon2D> xClipPolygon (
314  PresenterGeometryHelper::CreatePolygon(aClipBox, mxCanvas->getDevice()));
315 
316  const rendering::ViewState aViewState(
317  geometry::AffineMatrix2D(1,0,0, 0,1,0),
318  xClipPolygon);
319 
320  rendering::RenderState aRenderState (
321  geometry::AffineMatrix2D(1,0,nX, 0,1,nY),
322  nullptr,
323  Sequence<double>(4),
324  rendering::CompositeOperation::SOURCE);
325  PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor);
326 
327  for (const auto& rxParagraph : maParagraphs)
328  {
329  rxParagraph->Paint(
330  mxCanvas,
331  maSize,
332  mpFont,
333  aViewState,
334  aRenderState,
335  mnTopOffset,
336  nClipTop,
337  nClipBottom);
338  }
339 
340  aRenderState.AffineTransform.m02 = 0;
341  aRenderState.AffineTransform.m12 = 0;
342 
343 #ifdef SHOW_CHARACTER_BOXES
344  PresenterCanvasHelper::SetDeviceColor(aRenderState, 0x00808080);
345  for (sal_Int32 nParagraphIndex(0), nParagraphCount(GetParagraphCount());
346  nParagraphIndex<nParagraphCount;
347  ++nParagraphIndex)
348  {
349  const SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
350  if ( ! pParagraph)
351  continue;
352  for (sal_Int32 nCharacterIndex(0),nCharacterCount(pParagraph->GetCharacterCount());
353  nCharacterIndex<nCharacterCount; ++nCharacterIndex)
354  {
355  const awt::Rectangle aBox (pParagraph->GetCharacterBounds(nCharacterIndex, false));
356  mxCanvas->drawPolyPolygon (
357  PresenterGeometryHelper::CreatePolygon(
358  aBox,
359  mxCanvas->getDevice()),
360  aViewState,
361  aRenderState);
362  }
363  }
364  PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor);
365 #endif
366 
367  if (mpCaret && mpCaret->IsVisible())
368  {
369  mxCanvas->fillPolyPolygon (
370  PresenterGeometryHelper::CreatePolygon(
371  mpCaret->GetBounds(),
372  mxCanvas->getDevice()),
373  aViewState,
374  aRenderState);
375  }
376 }
377 
378 const SharedPresenterTextCaret& PresenterTextView::GetCaret() const
379 {
380  return mpCaret;
381 }
382 
383 awt::Rectangle PresenterTextView::GetCaretBounds (
384  sal_Int32 nParagraphIndex,
385  const sal_Int32 nCharacterIndex) const
386 {
387  SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
388 
389  if (pParagraph)
390  return pParagraph->GetCharacterBounds(nCharacterIndex, true);
391  else
392  return awt::Rectangle(0,0,0,0);
393 }
394 
395 //----- private ---------------------------------------------------------------
396 
397 void PresenterTextView::RequestFormat()
398 {
399  mbIsFormatPending = true;
400 }
401 
402 void PresenterTextView::Format()
403 {
404  mbIsFormatPending = false;
405 
406  double nY (0);
407  for (const auto& rxParagraph : maParagraphs)
408  {
409  rxParagraph->Format(nY, maSize.Width, mpFont);
410  nY += rxParagraph->GetTotalTextHeight();
411  }
412 
413  if (maTextChangeBroadcaster)
414  maTextChangeBroadcaster();
415 }
416 
417 sal_Int32 PresenterTextView::GetParagraphCount() const
418 {
419  return maParagraphs.size();
420 }
421 
422 SharedPresenterTextParagraph PresenterTextView::GetParagraph (
423  const sal_Int32 nParagraphIndex) const
424 {
425  if (nParagraphIndex < 0)
427  else if (nParagraphIndex>=sal_Int32(maParagraphs.size()))
429  else
430  return maParagraphs[nParagraphIndex];
431 }
432 
433 //===== PresenterTextParagraph ================================================
434 
435 PresenterTextParagraph::PresenterTextParagraph (
436  const sal_Int32 nParagraphIndex,
437  const Reference<i18n::XBreakIterator>& rxBreakIterator,
438  const Reference<i18n::XScriptTypeDetector>& rxScriptTypeDetector,
439  const Reference<text::XTextRange>& rxTextRange,
440  const SharedPresenterTextCaret& rpCaret)
441  : msParagraphText(),
442  mnParagraphIndex(nParagraphIndex),
443  mpCaret(rpCaret),
444  mxBreakIterator(rxBreakIterator),
445  mxScriptTypeDetector(rxScriptTypeDetector),
446  maLines(),
447  mnVerticalOffset(0),
448  mnXOrigin(0),
449  mnYOrigin(0),
450  mnWidth(0),
451  mnAscent(0),
452  mnDescent(0),
453  mnLineHeight(-1),
454  mnWritingMode (text::WritingMode2::LR_TB),
455  mnCharacterOffset(0),
456  maCells()
457 {
458  if (!rxTextRange.is())
459  return;
460 
461  Reference<beans::XPropertySet> xProperties (rxTextRange, UNO_QUERY);
462  try
463  {
464  xProperties->getPropertyValue("WritingMode") >>= mnWritingMode;
465  }
466  catch(beans::UnknownPropertyException&)
467  {
468  // Ignore the exception. Use the default value.
469  }
470 
471  msParagraphText = rxTextRange->getString();
472 }
473 
475  const Reference<rendering::XCanvas>& rxCanvas,
476  const geometry::RealSize2D& rSize,
478  const rendering::ViewState& rViewState,
479  rendering::RenderState& rRenderState,
480  const double nTopOffset,
481  const double nClipTop,
482  const double nClipBottom)
483 {
484  if (mnLineHeight <= 0)
485  return;
486 
487  sal_Int8 nTextDirection (GetTextDirection());
488 
489  const double nSavedM12 (rRenderState.AffineTransform.m12);
490 
491  if ( ! IsTextReferencePointLeft())
492  rRenderState.AffineTransform.m02 += rSize.Width;
493 
494 #ifdef SHOW_CHARACTER_BOXES
495  for (sal_Int32 nIndex=0,nCount=maLines.size();
496  nIndex<nCount;
497  ++nIndex)
498  {
499  Line& rLine (maLines[nIndex]);
500  rLine.ProvideLayoutedLine(msParagraphText, rpFont, nTextDirection);
501  }
502 #endif
503 
504  for (sal_Int32 nIndex=0,nCount=maLines.size();
505  nIndex<nCount;
506  ++nIndex, rRenderState.AffineTransform.m12 += mnLineHeight)
507  {
508  Line& rLine (maLines[nIndex]);
509 
510  // Paint only visible lines.
511  const double nLineTop = rLine.mnBaseLine - mnAscent - nTopOffset;
512  if (nLineTop + mnLineHeight< nClipTop)
513  continue;
514  else if (nLineTop > nClipBottom)
515  break;
516  rLine.ProvideLayoutedLine(msParagraphText, rpFont, nTextDirection);
517 
518  rRenderState.AffineTransform.m12 = nSavedM12 + rLine.mnBaseLine;
519 
520  rxCanvas->drawTextLayout (
521  rLine.mxLayoutedLine,
522  rViewState,
523  rRenderState);
524  }
525  rRenderState.AffineTransform.m12 = nSavedM12;
526 
527  if ( ! IsTextReferencePointLeft())
528  rRenderState.AffineTransform.m02 -= rSize.Width;
529 }
530 
532  const double nY,
533  const double nWidth,
535 {
536  // Make sure that the text view is in a valid and sane state.
537  if ( ! mxBreakIterator.is() || ! mxScriptTypeDetector.is())
538  return;
539  if (nWidth<=0)
540  return;
541  if ( ! rpFont || ! rpFont->mxFont.is())
542  return;
543 
544  sal_Int32 nPosition (0);
545 
546  mnWidth = nWidth;
547  maLines.clear();
548  mnLineHeight = 0;
549  mnAscent = 0;
550  mnDescent = 0;
551  mnVerticalOffset = nY;
552  maWordBoundaries.clear();
553  maWordBoundaries.push_back(0);
554 
555  const rendering::FontMetrics aMetrics (rpFont->mxFont->getFontMetrics());
556  mnAscent = aMetrics.Ascent;
557  mnDescent = aMetrics.Descent;
558  mnLineHeight = aMetrics.Ascent + aMetrics.Descent + aMetrics.ExternalLeading;
559  nPosition = 0;
560  i18n::Boundary aCurrentLine(0,0);
561  while (true)
562  {
563  const i18n::Boundary aWordBoundary = mxBreakIterator->nextWord(
565  nPosition,
566  lang::Locale(),
567  i18n::WordType::ANYWORD_IGNOREWHITESPACES);
568  AddWord(nWidth, aCurrentLine, aWordBoundary.startPos, rpFont);
569 
570  // Remember the new word boundary for caret travelling by words.
571  // Prevent duplicates.
572  if (aWordBoundary.startPos > maWordBoundaries.back())
573  maWordBoundaries.push_back(aWordBoundary.startPos);
574 
575  if (aWordBoundary.endPos>aWordBoundary.startPos)
576  AddWord(nWidth, aCurrentLine, aWordBoundary.endPos, rpFont);
577 
578  if (aWordBoundary.startPos<0 || aWordBoundary.endPos<0)
579  break;
580  if (nPosition >= aWordBoundary.endPos)
581  break;
582  nPosition = aWordBoundary.endPos;
583  }
584 
585  if (aCurrentLine.endPos>aCurrentLine.startPos)
586  AddLine(aCurrentLine);
587 
588 }
589 
591  const sal_Int32 nLocalCharacterIndex,
592  const sal_Int32 nDistance)
593 {
594  OSL_ASSERT(nDistance==-1 || nDistance==+1);
595 
596  if (nLocalCharacterIndex < 0)
597  {
598  // The caller asked for the start or end position of the paragraph.
599  if (nDistance < 0)
600  return 0;
601  else
602  return GetCharacterCount();
603  }
604 
605  sal_Int32 nIndex (0);
606  for (sal_Int32 nCount (maWordBoundaries.size()); nIndex<nCount; ++nIndex)
607  {
608  if (maWordBoundaries[nIndex] >= nLocalCharacterIndex)
609  {
610  // When inside the word (not at its start or end) then
611  // first move to the start or end before going the previous or
612  // next word.
613  if (maWordBoundaries[nIndex] > nLocalCharacterIndex)
614  if (nDistance > 0)
615  --nIndex;
616  break;
617  }
618  }
619 
620  nIndex += nDistance;
621 
622  if (nIndex < 0)
623  return -1;
624  else if (o3tl::make_unsigned(nIndex)>=maWordBoundaries.size())
625  return -1;
626  else
627  return maWordBoundaries[nIndex];
628 }
629 
631 {
632  if (mpCaret && mpCaret->GetParagraphIndex()==mnParagraphIndex)
633  return mpCaret->GetCharacterIndex();
634  else
635  return -1;
636 }
637 
638 void PresenterTextParagraph::SetCaretPosition (const sal_Int32 nPosition) const
639 {
640  if (mpCaret && mpCaret->GetParagraphIndex()==mnParagraphIndex)
641  return mpCaret->SetPosition(mnParagraphIndex, nPosition);
642 }
643 
644 void PresenterTextParagraph::SetOrigin (const double nXOrigin, const double nYOrigin)
645 {
646  mnXOrigin = nXOrigin;
647  mnYOrigin = nYOrigin;
648 }
649 
651 {
652  return awt::Point(
653  sal_Int32(mnXOrigin),
654  sal_Int32(mnYOrigin + mnVerticalOffset));
655 }
656 
658 {
659  return awt::Size(
660  sal_Int32(mnWidth),
661  sal_Int32(GetTotalTextHeight()));
662 }
663 
665  const double nWidth,
666  i18n::Boundary& rCurrentLine,
667  const sal_Int32 nWordBoundary,
669 {
670  sal_Int32 nLineStart (0);
671  if ( ! maLines.empty())
672  nLineStart = rCurrentLine.startPos;
673 
674  const OUString sLineCandidate (
675  msParagraphText.copy(nLineStart, nWordBoundary-nLineStart));
676 
677  css::geometry::RealRectangle2D aLineBox (
679  rpFont->mxFont,
680  sLineCandidate,
681  mnWritingMode));
682  const double nLineWidth (aLineBox.X2 - aLineBox.X1);
683 
684  if (nLineWidth >= nWidth)
685  {
686  // Add new line with a single word (so far).
687  AddLine(rCurrentLine);
688  }
689  rCurrentLine.endPos = nWordBoundary;
690 }
691 
693  i18n::Boundary& rCurrentLine)
694 {
695  Line aLine (rCurrentLine.startPos, rCurrentLine.endPos);
696 
697  // Find the start and end of the line with respect to cells.
698  if (!maLines.empty())
699  {
700  aLine.mnLineStartCellIndex = maLines.back().mnLineEndCellIndex;
701  aLine.mnBaseLine = maLines.back().mnBaseLine + mnLineHeight;
702  }
703  else
704  {
705  aLine.mnLineStartCellIndex = 0;
707  }
708  sal_Int32 nCellIndex (aLine.mnLineStartCellIndex);
709  double nWidth (0);
710  for ( ; nCellIndex<sal_Int32(maCells.size()); ++nCellIndex)
711  {
712  const Cell& rCell (maCells[nCellIndex]);
714  break;
715  nWidth += rCell.mnCellWidth;
716  }
717  aLine.mnLineEndCellIndex = nCellIndex;
718  aLine.mnWidth = nWidth;
719 
720  maLines.push_back(aLine);
721 
722  rCurrentLine.startPos = rCurrentLine.endPos;
723 }
724 
726 {
727  return maLines.size() * mnLineHeight;
728 }
729 
730 void PresenterTextParagraph::SetCharacterOffset (const sal_Int32 nCharacterOffset)
731 {
732  mnCharacterOffset = nCharacterOffset;
733 }
734 
736 {
737  return msParagraphText.getLength();
738 }
739 
741  const sal_Int32 nGlobalCharacterIndex) const
742 {
743  if (nGlobalCharacterIndex<mnCharacterOffset
744  || nGlobalCharacterIndex>=mnCharacterOffset+msParagraphText.getLength())
745  {
746  return sal_Unicode();
747  }
748  else
749  {
750  return msParagraphText[nGlobalCharacterIndex - mnCharacterOffset];
751  }
752 }
753 
754 const OUString& PresenterTextParagraph::GetText() const
755 {
756  return msParagraphText;
757 }
758 
760  const sal_Int32 nOffset,
761  const sal_Int32 nIndex,
762  const sal_Int16 nTextType) const
763 {
764  switch(nTextType)
765  {
766  case AccessibleTextType::PARAGRAPH:
767  return TextSegment(
770  mnCharacterOffset+msParagraphText.getLength());
771 
772  case AccessibleTextType::SENTENCE:
773  if (mxBreakIterator.is())
774  {
775  const sal_Int32 nStart (mxBreakIterator->beginOfSentence(
776  msParagraphText, nIndex-mnCharacterOffset, lang::Locale()));
777  const sal_Int32 nEnd (mxBreakIterator->endOfSentence(
778  msParagraphText, nIndex-mnCharacterOffset, lang::Locale()));
779  if (nStart < nEnd)
780  return TextSegment(
781  msParagraphText.copy(nStart, nEnd-nStart),
782  nStart+mnCharacterOffset,
783  nEnd+mnCharacterOffset);
784  }
785  break;
786 
787  case AccessibleTextType::WORD:
788  if (mxBreakIterator.is())
789  return GetWordTextSegment(nOffset, nIndex);
790  break;
791 
792  case AccessibleTextType::LINE:
793  {
794  auto iLine = std::find_if(maLines.begin(), maLines.end(),
795  [nIndex](const Line& rLine) { return nIndex < rLine.mnLineEndCharacterIndex; });
796  if (iLine != maLines.end())
797  {
798  return TextSegment(
799  msParagraphText.copy(
800  iLine->mnLineStartCharacterIndex,
801  iLine->mnLineEndCharacterIndex - iLine->mnLineStartCharacterIndex),
802  iLine->mnLineStartCharacterIndex,
803  iLine->mnLineEndCharacterIndex);
804  }
805  }
806  break;
807 
808  // Handle GLYPH and ATTRIBUTE_RUN like CHARACTER because we can not
809  // do better at the moment.
810  case AccessibleTextType::CHARACTER:
811  case AccessibleTextType::GLYPH:
812  case AccessibleTextType::ATTRIBUTE_RUN:
813  return CreateTextSegment(nIndex+nOffset, nIndex+nOffset+1);
814  }
815 
816  return TextSegment(OUString(), 0,0);
817 }
818 
820  const sal_Int32 nOffset,
821  const sal_Int32 nIndex) const
822 {
823  sal_Int32 nCurrentOffset (nOffset);
824  sal_Int32 nCurrentIndex (nIndex);
825 
826  i18n::Boundary aWordBoundary;
827  if (nCurrentOffset == 0)
828  aWordBoundary = mxBreakIterator->getWordBoundary(
830  nIndex,
831  lang::Locale(),
832  i18n::WordType::ANYWORD_IGNOREWHITESPACES,
833  true);
834  else if (nCurrentOffset < 0)
835  {
836  while (nCurrentOffset<0 && nCurrentIndex>0)
837  {
838  aWordBoundary = mxBreakIterator->previousWord(
840  nCurrentIndex,
841  lang::Locale(),
842  i18n::WordType::ANYWORD_IGNOREWHITESPACES);
843  nCurrentIndex = aWordBoundary.startPos;
844  ++nCurrentOffset;
845  }
846  }
847  else
848  {
849  while (nCurrentOffset>0 && nCurrentIndex<=GetCharacterCount())
850  {
851  aWordBoundary = mxBreakIterator->nextWord(
853  nCurrentIndex,
854  lang::Locale(),
855  i18n::WordType::ANYWORD_IGNOREWHITESPACES);
856  nCurrentIndex = aWordBoundary.endPos;
857  --nCurrentOffset;
858  }
859  }
860 
861  return CreateTextSegment(aWordBoundary.startPos, aWordBoundary.endPos);
862 }
863 
865  sal_Int32 nStartIndex,
866  sal_Int32 nEndIndex) const
867 {
868  if (nEndIndex <= nStartIndex)
869  return TextSegment(
870  OUString(),
871  nStartIndex,
872  nEndIndex);
873  else
874  return TextSegment(
875  msParagraphText.copy(nStartIndex, nEndIndex-nStartIndex),
876  nStartIndex,
877  nEndIndex);
878 }
879 
881  sal_Int32 nGlobalCharacterIndex,
882  const bool bCaretBox)
883 {
884  // Find the line that contains the requested character and accumulate
885  // the previous line heights.
886  double nX (mnXOrigin);
887  double nY (mnYOrigin + mnVerticalOffset + mnAscent);
888  const sal_Int8 nTextDirection (GetTextDirection());
889  for (sal_Int32 nLineIndex=0,nLineCount=maLines.size();
890  nLineIndex<nLineCount;
891  ++nLineIndex, nY+=mnLineHeight)
892  {
893  Line& rLine (maLines[nLineIndex]);
894  // Skip lines before the indexed character.
895  if (nGlobalCharacterIndex >= rLine.mnLineEndCharacterIndex)
896  // When in the last line then allow the index past the last char.
897  if (nLineIndex<nLineCount-1)
898  continue;
899 
900  rLine.ProvideCellBoxes();
901 
902  const sal_Int32 nCellIndex (nGlobalCharacterIndex - rLine.mnLineStartCharacterIndex);
903 
904  // The cell bounding box is defined relative to the origin of
905  // the current line. Therefore we have to add the absolute
906  // position of the line.
907  geometry::RealRectangle2D rCellBox (rLine.maCellBoxes[
908  ::std::min(nCellIndex, rLine.maCellBoxes.getLength()-1)]);
909 
910  double nLeft = nX + rCellBox.X1;
911  double nRight = nX + rCellBox.X2;
912  if (nTextDirection == rendering::TextDirection::WEAK_RIGHT_TO_LEFT)
913  {
914  const double nOldRight (nRight);
915  nRight = rLine.mnWidth - nLeft;
916  nLeft = rLine.mnWidth - nOldRight;
917  }
918  double nTop = nY - mnAscent;
919  double nBottom;
920  if (bCaretBox)
921  {
922  nBottom = nTop + mnLineHeight;
923  if (nCellIndex >= rLine.maCellBoxes.getLength())
924  nLeft = nRight-2;
925  if (nLeft < nX)
926  nLeft = nX;
927  nRight = nLeft+2;
928  }
929  else
930  {
931  nBottom = nTop + mnAscent + mnDescent;
932  }
933  const sal_Int32 nX1 = sal_Int32(floor(nLeft));
934  const sal_Int32 nY1 = sal_Int32(floor(nTop));
935  const sal_Int32 nX2 = sal_Int32(ceil(nRight));
936  const sal_Int32 nY2 = sal_Int32(ceil(nBottom));
937 
938  return awt::Rectangle(nX1,nY1,nX2-nX1+1,nY2-nY1+1);
939  }
940 
941  // We are still here. That means that the given index lies past the
942  // last character in the paragraph.
943  // Return an empty box that lies past the last character. Better than nothing.
944  return awt::Rectangle(sal_Int32(nX+0.5), sal_Int32(nY+0.5), 0, 0);
945 }
946 
948 {
949  // Find first portion that has a non-neutral text direction.
950  sal_Int32 nPosition (0);
951  sal_Int32 nTextLength (msParagraphText.getLength());
952  while (nPosition < nTextLength)
953  {
954  const sal_Int16 nScriptDirection (
955  mxScriptTypeDetector->getScriptDirection(
956  msParagraphText, nPosition, i18n::ScriptDirection::NEUTRAL));
957  switch (nScriptDirection)
958  {
959  case i18n::ScriptDirection::NEUTRAL:
960  // continue looping.
961  break;
962  case i18n::ScriptDirection::LEFT_TO_RIGHT:
963  return rendering::TextDirection::WEAK_LEFT_TO_RIGHT;
964 
965  case i18n::ScriptDirection::RIGHT_TO_LEFT:
966  return rendering::TextDirection::WEAK_RIGHT_TO_LEFT;
967  }
968 
969  nPosition = mxScriptTypeDetector->endOfScriptDirection(
970  msParagraphText, nPosition, nScriptDirection);
971  }
972 
973  // All text in paragraph is neutral. Fall back on writing mode taken
974  // from the XText (which may not be properly initialized.)
975  sal_Int8 nTextDirection(rendering::TextDirection::WEAK_LEFT_TO_RIGHT);
976  switch(mnWritingMode)
977  {
978  case text::WritingMode2::LR_TB:
979  nTextDirection = rendering::TextDirection::WEAK_LEFT_TO_RIGHT;
980  break;
981 
982  case text::WritingMode2::RL_TB:
983  nTextDirection = rendering::TextDirection::WEAK_RIGHT_TO_LEFT;
984  break;
985 
986  default:
987  case text::WritingMode2::TB_RL:
988  case text::WritingMode2::TB_LR:
989  // Can not handle this. Use default and hope for the best.
990  break;
991  }
992  return nTextDirection;
993 }
994 
996 {
997  return mnWritingMode != text::WritingMode2::RL_TB;
998 }
999 
1002 {
1003  maCells.clear();
1004 
1005  if ( ! rpFont || ! rpFont->mxFont.is())
1006  return;
1007 
1008  sal_Int32 nPosition (0);
1009  sal_Int32 nIndex (0);
1010  const sal_Int32 nTextLength (msParagraphText.getLength());
1011  const sal_Int8 nTextDirection (GetTextDirection());
1012  while (nPosition < nTextLength)
1013  {
1014  const sal_Int32 nNewPosition (mxBreakIterator->nextCharacters(
1016  nPosition,
1017  lang::Locale(),
1018  i18n::CharacterIteratorMode::SKIPCELL,
1019  1,
1020  nIndex));
1021 
1022  rendering::StringContext aContext (msParagraphText, nPosition, nNewPosition-nPosition);
1023  Reference<rendering::XTextLayout> xLayout (
1024  rpFont->mxFont->createTextLayout(aContext, nTextDirection, 0));
1025  css::geometry::RealRectangle2D aCharacterBox (xLayout->queryTextBounds());
1026 
1027  maCells.emplace_back(
1028  nPosition,
1029  nNewPosition-nPosition,
1030  aCharacterBox.X2-aCharacterBox.X1);
1031 
1032  nPosition = nNewPosition;
1033  }
1034 }
1035 
1036 //===== PresenterTextCaret ================================================----
1037 
1039  uno::Reference<uno::XComponentContext> const& xContext,
1040  const ::std::function<css::awt::Rectangle (const sal_Int32,const sal_Int32)>& rCharacterBoundsAccess,
1041  const ::std::function<void (const css::awt::Rectangle&)>& rInvalidator)
1042  : m_xContext(xContext)
1043  , mnParagraphIndex(-1),
1044  mnCharacterIndex(-1),
1045  mnCaretBlinkTaskId(0),
1046  mbIsCaretVisible(false),
1047  maCharacterBoundsAccess(rCharacterBoundsAccess),
1048  maInvalidator(rInvalidator),
1049  maBroadcaster(),
1050  maCaretBounds()
1051 {
1052 }
1053 
1055 {
1056  try
1057  {
1058  HideCaret();
1059  }
1060  catch (uno::Exception const&)
1061  {
1062  TOOLS_WARN_EXCEPTION("sdext.presenter", "unexpected exception in ~PresenterTextCaret");
1063  }
1064 }
1065 
1067 {
1068  if (mnCaretBlinkTaskId == 0)
1069  {
1071  m_xContext,
1072  [this] (TimeValue const&) { return this->InvertCaret(); },
1075  }
1076  mbIsCaretVisible = true;
1077 }
1078 
1080 {
1081  if (mnCaretBlinkTaskId != 0)
1082  {
1084  mnCaretBlinkTaskId = 0;
1085  }
1086  mbIsCaretVisible = false;
1087  // Reset the caret position.
1088  mnParagraphIndex = -1;
1089  mnCharacterIndex = -1;
1090 }
1091 
1092 
1094  const sal_Int32 nParagraphIndex,
1095  const sal_Int32 nCharacterIndex)
1096 {
1097  if (mnParagraphIndex == nParagraphIndex
1098  && mnCharacterIndex == nCharacterIndex)
1099  return;
1100 
1101  if (mnParagraphIndex >= 0)
1103 
1104  const sal_Int32 nOldParagraphIndex (mnParagraphIndex);
1105  const sal_Int32 nOldCharacterIndex (mnCharacterIndex);
1106  mnParagraphIndex = nParagraphIndex;
1107  mnCharacterIndex = nCharacterIndex;
1109  if (mnParagraphIndex >= 0)
1110  ShowCaret();
1111  else
1112  HideCaret();
1113 
1114  if (mnParagraphIndex >= 0)
1116 
1117  if (maBroadcaster)
1118  maBroadcaster(
1119  nOldParagraphIndex,
1120  nOldCharacterIndex,
1123 }
1124 
1125 
1127  const ::std::function<void (sal_Int32,sal_Int32,sal_Int32,sal_Int32)>& rBroadcaster)
1128 {
1129  maBroadcaster = rBroadcaster;
1130 }
1131 
1132 const css::awt::Rectangle& PresenterTextCaret::GetBounds() const
1133 {
1134  return maCaretBounds;
1135 }
1136 
1138 {
1140  if (mnParagraphIndex >= 0)
1142 }
1143 
1144 //===== PresenterTextParagraph::Cell ==========================================
1145 
1147  const sal_Int32 nCharacterIndex,
1148  const sal_Int32 nCharacterCount,
1149  const double nCellWidth)
1150  : mnCharacterIndex(nCharacterIndex),
1151  mnCharacterCount(nCharacterCount),
1152  mnCellWidth(nCellWidth)
1153 {
1154 }
1155 
1156 //===== PresenterTextParagraph::Line ==========================================
1157 
1159  const sal_Int32 nLineStartCharacterIndex,
1160  const sal_Int32 nLineEndCharacterIndex)
1161  : mnLineStartCharacterIndex(nLineStartCharacterIndex),
1162  mnLineEndCharacterIndex(nLineEndCharacterIndex),
1163  mnLineStartCellIndex(-1), mnLineEndCellIndex(-1),
1164  mxLayoutedLine(),
1165  mnBaseLine(0), mnWidth(0),
1166  maCellBoxes()
1167 {
1168 }
1169 
1171 {
1172  if ( mnLineStartCharacterIndex < mnLineEndCharacterIndex && !maCellBoxes.hasElements() )
1173  {
1174  if (mxLayoutedLine.is())
1175  maCellBoxes = mxLayoutedLine->queryInkMeasures();
1176  else
1177  {
1178  OSL_ASSERT(mxLayoutedLine.is());
1179  }
1180  }
1181 }
1182 
1184  const OUString& rsParagraphText,
1186  const sal_Int8 nTextDirection)
1187 {
1188  if ( ! mxLayoutedLine.is())
1189  {
1190  const rendering::StringContext aContext (
1191  rsParagraphText,
1192  mnLineStartCharacterIndex,
1193  mnLineEndCharacterIndex - mnLineStartCharacterIndex);
1194 
1195  mxLayoutedLine = rpFont->mxFont->createTextLayout(
1196  aContext,
1197  nTextDirection,
1198  0);
1199  }
1200 }
1201 
1202 } // end of namespace ::sdext::presenter
1203 
1204 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Reference< rendering::XCanvas > mxCanvas
css::accessibility::TextSegment GetTextSegment(const sal_Int32 nOffset, const sal_Int32 nGlobalCharacterIndex, const sal_Int16 nTextType) const
std::shared_ptr< FontDescriptor > SharedFontDescriptor
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.
sal_Int32 nIndex
css::uno::Reference< css::uno::XComponentContext > const & m_xContext
signed char sal_Int8
void SetCaretPosition(const sal_Int32 nPosition) const
awt::Point maLocation
PresenterTextCaret(css::uno::Reference< css::uno::XComponentContext > const &xContext, const ::std::function< css::awt::Rectangle(const sal_Int32, const sal_Int32)> &rCharacterBoundsAccess, const ::std::function< void(const css::awt::Rectangle &)> &rInvalidator)
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.
A portion of a string that encodes one unicode cell.
void ProvideLayoutedLine(const OUString &rsParagraphText, const PresenterTheme::SharedFontDescriptor &rpFont, const sal_Int8 nTextDirection)
css::awt::Rectangle GetCharacterBounds(sal_Int32 nGlobalCharacterIndex, const bool bCaretBox)
const css::awt::Rectangle & GetBounds() const
sal_uInt16 sal_Unicode
int nCount
static const sal_Int64 CaretBlinkInterval
css::uno::Reference< css::rendering::XTextLayout > mxLayoutedLine
Line(const sal_Int32 nLineStartCharacterIndex, const sal_Int32 nLineEndCharacterIndex)
const ::std::function< css::awt::Rectangle(const sal_Int32, const sal_Int32)> maCharacterBoundsAccess
static void CancelTask(const sal_Int32 nTaskId)
sal_Int32 GetWordBoundary(const sal_Int32 nLocalCharacterIndex, const sal_Int32 nDistance)
#define TOOLS_WARN_EXCEPTION(area, stream)
sal_Int32 mnCharacterOffset
The index of the first character in this paragraph with respect to the whole text.
sal_Int16 mnParagraphIndex
css::uno::Sequence< css::geometry::RealRectangle2D > maCellBoxes
void SetupCellArray(const PresenterTheme::SharedFontDescriptor &rpFont)
constexpr std::enable_if_t< std::is_signed_v< T >, std::make_unsigned_t< T > > make_unsigned(T value)
css::accessibility::TextSegment CreateTextSegment(sal_Int32 nStartIndex, sal_Int32 nEndIndex) const
Cell(const sal_Int32 nCharacterIndex, const sal_Int32 nCharacterCount, const double nCellWidth)
css::accessibility::TextSegment GetWordTextSegment(const sal_Int32 nOffset, const sal_Int32 nIndex) const
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 AddWord(const double nWidth, css::i18n::Boundary &rCurrentLine, const sal_Int32 nWordBoundary, const PresenterTheme::SharedFontDescriptor &rpFont)
sal_Int32 nLineWidth
css::uno::Reference< css::i18n::XScriptTypeDetector > mxScriptTypeDetector
PresenterTheme::SharedFontDescriptor mpFont
std::shared_ptr< PresenterTextCaret > SharedPresenterTextCaret
void SetPosition(const sal_Int32 nParagraphIndex, const sal_Int32 nCharacterIndex)
void AddLine(css::i18n::Boundary &rCurrentLine)
::std::function< void(sal_Int32, sal_Int32, sal_Int32, sal_Int32)> maBroadcaster
double mnWidth
void Format(const double nY, const double nWidth, const PresenterTheme::SharedFontDescriptor &rpFont)
double mnVerticalOffset
def text(shape, st)
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)
css::uno::Reference< css::i18n::XBreakIterator > mxBreakIterator
Reference< XSingleServiceFactory > xFactory
const ::std::function< void(const css::awt::Rectangle &)> maInvalidator
void SetCharacterOffset(const sal_Int32 nCharacterOffset)
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)
sal_Unicode GetCharacter(const sal_Int32 nGlobalCharacterIndex) const
const int nLineCount
geometry::RealSize2D maSize
std::shared_ptr< PresenterTextParagraph > SharedPresenterTextParagraph
const uno::Reference< uno::XComponentContext > m_xContext
Definition: wrapper.cxx:144
sal_Int16 nValue
void SetOrigin(const double nXOrigin, const double nYOrigin)