fix baseline build (old cairo) - 'cairo_rectangle_int_t' does not name a type
[LibreOffice.git] / sdext / source / presenter / PresenterTextView.cxx
blobe0b3838ef6608d39e085bfeaee1fa674497b8b03
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 <cmath>
27 #include <com/sun/star/accessibility/AccessibleTextType.hpp>
28 #include <com/sun/star/container/XEnumerationAccess.hpp>
29 #include <com/sun/star/i18n/BreakIterator.hpp>
30 #include <com/sun/star/i18n/CharType.hpp>
31 #include <com/sun/star/i18n/CharacterIteratorMode.hpp>
32 #include <com/sun/star/i18n/CTLScriptType.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 <boost/bind.hpp>
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 CaretBlinkIntervall = 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 ::boost::function<void(const css::awt::Rectangle&)>& rInvalidator)
68 : mxCanvas(rxCanvas),
69 mbDoOuput(true),
70 mxBreakIterator(),
71 mxScriptTypeDetector(),
72 maLocation(0,0),
73 maSize(0,0),
74 mpFont(),
75 maParagraphs(),
76 mpCaret(new PresenterTextCaret(
77 ::boost::bind(&PresenterTextView::GetCaretBounds, this, _1, _2),
78 rInvalidator)),
79 mnLeftOffset(0),
80 mnTopOffset(0),
81 maInvalidator(rInvalidator),
82 mbIsFormatPending(false),
83 mnCharacterCount(-1),
84 maTextChangeBroadcaster()
86 Reference<lang::XMultiComponentFactory> xFactory (
87 rxContext->getServiceManager(), UNO_QUERY);
88 if ( ! xFactory.is())
89 return;
91 // Create the break iterator that we use to break text into lines.
92 mxBreakIterator = i18n::BreakIterator::create(rxContext);
94 // Create the script type detector that is used to split paragraphs into
95 // portions of the same text direction.
96 mxScriptTypeDetector = Reference<i18n::XScriptTypeDetector>(
97 xFactory->createInstanceWithContext(
98 "com.sun.star.i18n.ScriptTypeDetector",
99 rxContext),
100 UNO_QUERY_THROW);
103 void PresenterTextView::SetText (const Reference<text::XText>& rxText)
105 maParagraphs.clear();
106 mnCharacterCount = -1;
108 Reference<container::XEnumerationAccess> xParagraphAccess (rxText, UNO_QUERY);
109 if ( ! xParagraphAccess.is())
110 return;
112 Reference<container::XEnumeration> xParagraphs (
113 xParagraphAccess->createEnumeration() , UNO_QUERY);
114 if ( ! xParagraphs.is())
115 return;
117 if ( ! mpFont || ! mpFont->PrepareFont(mxCanvas))
118 return;
120 sal_Int32 nCharacterCount (0);
121 while (xParagraphs->hasMoreElements())
123 SharedPresenterTextParagraph pParagraph (new PresenterTextParagraph(
124 maParagraphs.size(),
125 mxBreakIterator,
126 mxScriptTypeDetector,
127 Reference<text::XTextRange>(xParagraphs->nextElement(), UNO_QUERY),
128 mpCaret));
129 pParagraph->SetupCellArray(mpFont);
130 pParagraph->SetCharacterOffset(nCharacterCount);
131 nCharacterCount += pParagraph->GetCharacterCount();
132 maParagraphs.push_back(pParagraph);
135 if (mpCaret)
136 mpCaret->HideCaret();
138 RequestFormat();
141 void PresenterTextView::SetTextChangeBroadcaster (
142 const ::boost::function<void()>& rBroadcaster)
144 maTextChangeBroadcaster = rBroadcaster;
147 void PresenterTextView::SetLocation (const css::geometry::RealPoint2D& rLocation)
149 maLocation = rLocation;
151 for (::std::vector<SharedPresenterTextParagraph>::iterator
152 iParagraph(maParagraphs.begin()),
153 iEnd(maParagraphs.end());
154 iParagraph!=iEnd;
155 ++iParagraph)
157 (*iParagraph)->SetOrigin(
158 maLocation.X - mnLeftOffset,
159 maLocation.Y - mnTopOffset);
163 void PresenterTextView::SetSize (const css::geometry::RealSize2D& rSize)
165 maSize = rSize;
166 RequestFormat();
169 double PresenterTextView::GetTotalTextHeight()
171 double nTotalHeight (0);
173 if (mbIsFormatPending)
175 if ( ! mpFont->PrepareFont(mxCanvas))
176 return 0;
177 Format();
180 for (::std::vector<SharedPresenterTextParagraph>::iterator
181 iParagraph(maParagraphs.begin()),
182 iEnd(maParagraphs.end());
183 iParagraph!=iEnd;
184 ++iParagraph)
186 nTotalHeight += (*iParagraph)->GetTotalTextHeight();
189 return nTotalHeight;
192 void PresenterTextView::SetFont (const PresenterTheme::SharedFontDescriptor& rpFont)
194 mpFont = rpFont;
195 RequestFormat();
198 void PresenterTextView::SetOffset(
199 const double nLeft,
200 const double nTop)
202 mnLeftOffset = nLeft;
203 mnTopOffset = nTop;
205 // Trigger an update of the text origin stored at the individual paragraphs.
206 SetLocation(maLocation);
209 void PresenterTextView::MoveCaret (
210 const sal_Int32 nDistance,
211 const sal_Int16 nTextType)
213 if ( ! mpCaret)
214 return;
216 // When the caret has not been visible yet then move it to the beginning
217 // of the text.
218 if (mpCaret->GetParagraphIndex() < 0)
220 mpCaret->SetPosition(0,0);
221 return;
224 sal_Int32 nParagraphIndex (mpCaret->GetParagraphIndex());
225 sal_Int32 nCharacterIndex (mpCaret->GetCharacterIndex());
226 switch (nTextType)
228 default:
229 case AccessibleTextType::CHARACTER:
230 nCharacterIndex += nDistance;
231 break;
233 case AccessibleTextType::WORD:
235 sal_Int32 nRemainingDistance (nDistance);
236 while (nRemainingDistance != 0)
238 SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
239 if (pParagraph)
241 const sal_Int32 nDelta (Signum(nDistance));
242 nCharacterIndex = pParagraph->GetWordBoundary(nCharacterIndex, nDelta);
243 if (nCharacterIndex < 0)
245 // Go to previous or next paragraph.
246 nParagraphIndex += nDelta;
247 if (nParagraphIndex < 0)
249 nParagraphIndex = 0;
250 nCharacterIndex = 0;
251 nRemainingDistance = 0;
253 else if (sal_uInt32(nParagraphIndex) >= maParagraphs.size())
255 nParagraphIndex = maParagraphs.size()-1;
256 pParagraph = GetParagraph(nParagraphIndex);
257 if (pParagraph)
258 nCharacterIndex = pParagraph->GetCharacterCount();
259 nRemainingDistance = 0;
261 else
263 nRemainingDistance -= nDelta;
265 // Move caret one character to the end of
266 // the previous or the start of the next paragraph.
267 pParagraph = GetParagraph(nParagraphIndex);
268 if (pParagraph)
270 if (nDistance<0)
271 nCharacterIndex = pParagraph->GetCharacterCount();
272 else
273 nCharacterIndex = 0;
277 else
278 nRemainingDistance -= nDelta;
280 else
281 break;
283 break;
287 // Move the caret to the new position.
288 mpCaret->SetPosition(nParagraphIndex, nCharacterIndex);
291 void PresenterTextView::Paint (
292 const css::awt::Rectangle& rUpdateBox)
294 if ( ! mbDoOuput)
295 return;
296 if ( ! mxCanvas.is())
297 return;
298 if ( ! mpFont->PrepareFont(mxCanvas))
299 return;
301 if (mbIsFormatPending)
302 Format();
304 // Setup the clipping rectangle. Horizontally we make it a little
305 // larger to allow characters (and the caret) to stick out of their
306 // bounding boxes. This can happen on some characters (like the
307 // uppercase J) for typographical reasons.
308 const sal_Int32 nAdditionalLeftBorder (10);
309 const sal_Int32 nAdditionalRightBorder (5);
310 double nX (maLocation.X - mnLeftOffset);
311 double nY (maLocation.Y - mnTopOffset);
312 const sal_Int32 nClipLeft (::std::max(
313 PresenterGeometryHelper::Round(maLocation.X)-nAdditionalLeftBorder, rUpdateBox.X));
314 const sal_Int32 nClipTop (::std::max(
315 PresenterGeometryHelper::Round(maLocation.Y), rUpdateBox.Y));
316 const sal_Int32 nClipRight (::std::min(
317 PresenterGeometryHelper::Round(maLocation.X+maSize.Width)+nAdditionalRightBorder, rUpdateBox.X+rUpdateBox.Width));
318 const sal_Int32 nClipBottom (::std::min(
319 PresenterGeometryHelper::Round(maLocation.Y+maSize.Height), rUpdateBox.Y+rUpdateBox.Height));
320 if (nClipLeft>=nClipRight || nClipTop>=nClipBottom)
321 return;
323 const awt::Rectangle aClipBox(
324 nClipLeft,
325 nClipTop,
326 nClipRight - nClipLeft,
327 nClipBottom - nClipTop);
328 Reference<rendering::XPolyPolygon2D> xClipPolygon (
329 PresenterGeometryHelper::CreatePolygon(aClipBox, mxCanvas->getDevice()));
331 const rendering::ViewState aViewState(
332 geometry::AffineMatrix2D(1,0,0, 0,1,0),
333 xClipPolygon);
335 rendering::RenderState aRenderState (
336 geometry::AffineMatrix2D(1,0,nX, 0,1,nY),
337 NULL,
338 Sequence<double>(4),
339 rendering::CompositeOperation::SOURCE);
340 PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor);
342 for (::std::vector<SharedPresenterTextParagraph>::const_iterator
343 iParagraph(maParagraphs.begin()),
344 iEnd(maParagraphs.end());
345 iParagraph!=iEnd;
346 ++iParagraph)
348 (*iParagraph)->Paint(
349 mxCanvas,
350 maSize,
351 mpFont,
352 aViewState,
353 aRenderState,
354 mnTopOffset,
355 nClipTop,
356 nClipBottom);
359 aRenderState.AffineTransform.m02 = 0;
360 aRenderState.AffineTransform.m12 = 0;
362 #ifdef SHOW_CHARACTER_BOXES
363 PresenterCanvasHelper::SetDeviceColor(aRenderState, 0x00808080);
364 for (sal_Int32 nParagraphIndex(0), nParagraphCount(GetParagraphCount());
365 nParagraphIndex<nParagraphCount;
366 ++nParagraphIndex)
368 const SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
369 if ( ! pParagraph)
370 continue;
371 for (sal_Int32 nCharacterIndex(0),nCharacterCount(pParagraph->GetCharacterCount());
372 nCharacterIndex<nCharacterCount; ++nCharacterIndex)
374 const awt::Rectangle aBox (pParagraph->GetCharacterBounds(nCharacterIndex, false));
375 mxCanvas->drawPolyPolygon (
376 PresenterGeometryHelper::CreatePolygon(
377 aBox,
378 mxCanvas->getDevice()),
379 aViewState,
380 aRenderState);
383 PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor);
384 #endif
386 if (mpCaret && mpCaret->IsVisible())
388 mxCanvas->fillPolyPolygon (
389 PresenterGeometryHelper::CreatePolygon(
390 mpCaret->GetBounds(),
391 mxCanvas->getDevice()),
392 aViewState,
393 aRenderState);
397 SharedPresenterTextCaret PresenterTextView::GetCaret() const
399 return mpCaret;
402 awt::Rectangle PresenterTextView::GetCaretBounds (
403 sal_Int32 nParagraphIndex,
404 const sal_Int32 nCharacterIndex) const
406 SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex));
408 if (pParagraph)
409 return pParagraph->GetCharacterBounds(nCharacterIndex, true);
410 else
411 return awt::Rectangle(0,0,0,0);
414 //----- private ---------------------------------------------------------------
416 void PresenterTextView::RequestFormat()
418 mbIsFormatPending = true;
421 void PresenterTextView::Format()
423 mbIsFormatPending = false;
425 double nY (0);
426 for (::std::vector<SharedPresenterTextParagraph>::const_iterator
427 iParagraph(maParagraphs.begin()),
428 iEnd(maParagraphs.end());
429 iParagraph!=iEnd;
430 ++iParagraph)
432 (*iParagraph)->Format(nY, maSize.Width, mpFont);
433 nY += (*iParagraph)->GetTotalTextHeight();
436 if (maTextChangeBroadcaster)
437 maTextChangeBroadcaster();
440 sal_Int32 PresenterTextView::GetParagraphCount() const
442 return maParagraphs.size();
445 SharedPresenterTextParagraph PresenterTextView::GetParagraph (
446 const sal_Int32 nParagraphIndex) const
448 if (nParagraphIndex < 0)
449 return SharedPresenterTextParagraph();
450 else if (nParagraphIndex>=sal_Int32(maParagraphs.size()))
451 return SharedPresenterTextParagraph();
452 else
453 return maParagraphs[nParagraphIndex];
456 //===== PresenterTextParagraph ================================================
458 PresenterTextParagraph::PresenterTextParagraph (
459 const sal_Int32 nParagraphIndex,
460 const Reference<i18n::XBreakIterator>& rxBreakIterator,
461 const Reference<i18n::XScriptTypeDetector>& rxScriptTypeDetector,
462 const Reference<text::XTextRange>& rxTextRange,
463 const SharedPresenterTextCaret& rpCaret)
464 : msParagraphText(),
465 mnParagraphIndex(nParagraphIndex),
466 mpCaret(rpCaret),
467 mxBreakIterator(rxBreakIterator),
468 mxScriptTypeDetector(rxScriptTypeDetector),
469 maLines(),
470 mnVerticalOffset(0),
471 mnXOrigin(0),
472 mnYOrigin(0),
473 mnWidth(0),
474 mnAscent(0),
475 mnDescent(0),
476 mnLineHeight(-1),
477 meAdjust(style::ParagraphAdjust_LEFT),
478 mnWritingMode (text::WritingMode2::LR_TB),
479 mnCharacterOffset(0),
480 maCells()
482 if (rxTextRange.is())
484 Reference<beans::XPropertySet> xProperties (rxTextRange, UNO_QUERY);
485 lang::Locale aLocale;
488 xProperties->getPropertyValue("CharLocale") >>= aLocale;
490 catch(beans::UnknownPropertyException&)
492 // Ignore the exception. Use the default value.
496 xProperties->getPropertyValue("ParaAdjust") >>= meAdjust;
498 catch(beans::UnknownPropertyException&)
500 // Ignore the exception. Use the default value.
504 xProperties->getPropertyValue("WritingMode") >>= mnWritingMode;
506 catch(beans::UnknownPropertyException&)
508 // Ignore the exception. Use the default value.
511 msParagraphText = rxTextRange->getString();
515 void PresenterTextParagraph::Paint (
516 const Reference<rendering::XCanvas>& rxCanvas,
517 const geometry::RealSize2D& rSize,
518 const PresenterTheme::SharedFontDescriptor& rpFont,
519 const rendering::ViewState& rViewState,
520 rendering::RenderState& rRenderState,
521 const double nTopOffset,
522 const double nClipTop,
523 const double nClipBottom)
525 if (mnLineHeight <= 0)
526 return;
528 sal_Int8 nTextDirection (GetTextDirection());
530 const double nSavedM12 (rRenderState.AffineTransform.m12);
532 if ( ! IsTextReferencePointLeft())
533 rRenderState.AffineTransform.m02 += rSize.Width;
535 #ifdef SHOW_CHARACTER_BOXES
536 for (sal_Int32 nIndex=0,nCount=maLines.size();
537 nIndex<nCount;
538 ++nIndex)
540 Line& rLine (maLines[nIndex]);
541 rLine.ProvideLayoutedLine(msParagraphText, rpFont, nTextDirection);
543 #endif
545 for (sal_Int32 nIndex=0,nCount=maLines.size();
546 nIndex<nCount;
547 ++nIndex, rRenderState.AffineTransform.m12 += mnLineHeight)
549 Line& rLine (maLines[nIndex]);
551 // Paint only visible lines.
552 const double nLineTop = rLine.mnBaseLine - mnAscent - nTopOffset;
553 if (nLineTop + mnLineHeight< nClipTop)
554 continue;
555 else if (nLineTop > nClipBottom)
556 break;
557 rLine.ProvideLayoutedLine(msParagraphText, rpFont, nTextDirection);
559 rRenderState.AffineTransform.m12 = nSavedM12 + rLine.mnBaseLine;
561 rxCanvas->drawTextLayout (
562 rLine.mxLayoutedLine,
563 rViewState,
564 rRenderState);
566 rRenderState.AffineTransform.m12 = nSavedM12;
568 if ( ! IsTextReferencePointLeft())
569 rRenderState.AffineTransform.m02 -= rSize.Width;
572 void PresenterTextParagraph::Format (
573 const double nY,
574 const double nWidth,
575 const PresenterTheme::SharedFontDescriptor& rpFont)
577 // Make sure that the text view is in a valid and sane state.
578 if ( ! mxBreakIterator.is() || ! mxScriptTypeDetector.is())
579 return;
580 if (nWidth<=0)
581 return;
582 if ( ! rpFont || ! rpFont->mxFont.is())
583 return;
585 sal_Int32 nPosition (0);
587 mnWidth = nWidth;
588 maLines.clear();
589 mnLineHeight = 0;
590 mnAscent = 0;
591 mnDescent = 0;
592 mnVerticalOffset = nY;
593 maWordBoundaries.clear();
594 maWordBoundaries.push_back(0);
596 const rendering::FontMetrics aMetrics (rpFont->mxFont->getFontMetrics());
597 mnAscent = aMetrics.Ascent;
598 mnDescent = aMetrics.Descent;
599 mnLineHeight = aMetrics.Ascent + aMetrics.Descent + aMetrics.ExternalLeading;
600 nPosition = 0;
601 i18n::Boundary aCurrentLine(0,0);
602 while (true)
604 const i18n::Boundary aWordBoundary = mxBreakIterator->nextWord(
605 msParagraphText,
606 nPosition,
607 lang::Locale(),
608 i18n::WordType::ANYWORD_IGNOREWHITESPACES);
609 AddWord(nWidth, aCurrentLine, aWordBoundary.startPos, rpFont);
611 // Remember the new word boundary for caret travelling by words.
612 // Prevent duplicates.
613 if (aWordBoundary.startPos > maWordBoundaries.back())
614 maWordBoundaries.push_back(aWordBoundary.startPos);
616 if (aWordBoundary.endPos>aWordBoundary.startPos)
617 AddWord(nWidth, aCurrentLine, aWordBoundary.endPos, rpFont);
619 if (aWordBoundary.startPos<0 || aWordBoundary.endPos<0)
620 break;
621 if (nPosition >= aWordBoundary.endPos)
622 break;
623 nPosition = aWordBoundary.endPos;
626 if (aCurrentLine.endPos>aCurrentLine.startPos)
627 AddLine(aCurrentLine);
631 sal_Int32 PresenterTextParagraph::GetWordBoundary(
632 const sal_Int32 nLocalCharacterIndex,
633 const sal_Int32 nDistance)
635 OSL_ASSERT(nDistance==-1 || nDistance==+1);
637 if (nLocalCharacterIndex < 0)
639 // The caller asked for the start or end position of the paragraph.
640 if (nDistance < 0)
641 return 0;
642 else
643 return GetCharacterCount();
646 sal_Int32 nIndex (0);
647 for (sal_Int32 nCount (maWordBoundaries.size()); nIndex<nCount; ++nIndex)
649 if (maWordBoundaries[nIndex] >= nLocalCharacterIndex)
651 // When inside the word (not at its start or end) then
652 // first move to the start or end before going the previous or
653 // next word.
654 if (maWordBoundaries[nIndex] > nLocalCharacterIndex)
655 if (nDistance > 0)
656 --nIndex;
657 break;
661 nIndex += nDistance;
663 if (nIndex < 0)
664 return -1;
665 else if (sal_uInt32(nIndex)>=maWordBoundaries.size())
666 return -1;
667 else
668 return maWordBoundaries[nIndex];
671 sal_Int32 PresenterTextParagraph::GetCaretPosition() const
673 if (mpCaret && mpCaret->GetParagraphIndex()==mnParagraphIndex)
674 return mpCaret->GetCharacterIndex();
675 else
676 return -1;
679 void PresenterTextParagraph::SetCaretPosition (const sal_Int32 nPosition) const
681 if (mpCaret && mpCaret->GetParagraphIndex()==mnParagraphIndex)
682 return mpCaret->SetPosition(mnParagraphIndex, nPosition);
685 void PresenterTextParagraph::SetOrigin (const double nXOrigin, const double nYOrigin)
687 mnXOrigin = nXOrigin;
688 mnYOrigin = nYOrigin;
691 awt::Point PresenterTextParagraph::GetRelativeLocation() const
693 return awt::Point(
694 sal_Int32(mnXOrigin),
695 sal_Int32(mnYOrigin + mnVerticalOffset));
698 awt::Size PresenterTextParagraph::GetSize()
700 return awt::Size(
701 sal_Int32(mnWidth),
702 sal_Int32(GetTotalTextHeight()));
705 void PresenterTextParagraph::AddWord (
706 const double nWidth,
707 i18n::Boundary& rCurrentLine,
708 const sal_Int32 nWordBoundary,
709 const PresenterTheme::SharedFontDescriptor& rpFont)
711 sal_Int32 nLineStart (0);
712 if ( ! maLines.empty())
713 nLineStart = rCurrentLine.startPos;
715 const OUString sLineCandidate (
716 msParagraphText.copy(nLineStart, nWordBoundary-nLineStart));
718 css::geometry::RealRectangle2D aLineBox (
719 PresenterCanvasHelper::GetTextBoundingBox (
720 rpFont->mxFont,
721 sLineCandidate,
722 mnWritingMode));
723 const double nLineWidth (aLineBox.X2 - aLineBox.X1);
725 if (nLineWidth >= nWidth)
727 // Add new line with a single word (so far).
728 AddLine(rCurrentLine);
730 rCurrentLine.endPos = nWordBoundary;
733 void PresenterTextParagraph::AddLine (
734 i18n::Boundary& rCurrentLine)
736 Line aLine (rCurrentLine.startPos, rCurrentLine.endPos);
738 // Find the start and end of the line with respect to cells.
739 if (maLines.size() > 0)
741 aLine.mnLineStartCellIndex = maLines.back().mnLineEndCellIndex;
742 aLine.mnBaseLine = maLines.back().mnBaseLine + mnLineHeight;
744 else
746 aLine.mnLineStartCellIndex = 0;
747 aLine.mnBaseLine = mnVerticalOffset + mnAscent;
749 sal_Int32 nCellIndex (aLine.mnLineStartCellIndex);
750 double nWidth (0);
751 for ( ; nCellIndex<sal_Int32(maCells.size()); ++nCellIndex)
753 const Cell& rCell (maCells[nCellIndex]);
754 if (rCell.mnCharacterIndex+rCell.mnCharacterCount > aLine.mnLineEndCharacterIndex)
755 break;
756 nWidth += rCell.mnCellWidth;
758 aLine.mnLineEndCellIndex = nCellIndex;
759 aLine.mnWidth = nWidth;
761 maLines.push_back(aLine);
763 rCurrentLine.startPos = rCurrentLine.endPos;
766 double PresenterTextParagraph::GetTotalTextHeight()
768 return maLines.size() * mnLineHeight;
771 void PresenterTextParagraph::SetCharacterOffset (const sal_Int32 nCharacterOffset)
773 mnCharacterOffset = nCharacterOffset;
776 sal_Int32 PresenterTextParagraph::GetCharacterCount() const
778 return msParagraphText.getLength();
781 sal_Unicode PresenterTextParagraph::GetCharacter (
782 const sal_Int32 nGlobalCharacterIndex) const
784 if (nGlobalCharacterIndex<mnCharacterOffset
785 || nGlobalCharacterIndex>=mnCharacterOffset+msParagraphText.getLength())
787 return sal_Unicode();
789 else
791 return msParagraphText[nGlobalCharacterIndex - mnCharacterOffset];
795 OUString PresenterTextParagraph::GetText() const
797 return msParagraphText;
800 TextSegment PresenterTextParagraph::GetTextSegment (
801 const sal_Int32 nOffset,
802 const sal_Int32 nIndex,
803 const sal_Int16 nTextType) const
805 switch(nTextType)
807 case AccessibleTextType::PARAGRAPH:
808 return TextSegment(
809 msParagraphText,
810 mnCharacterOffset,
811 mnCharacterOffset+msParagraphText.getLength());
813 case AccessibleTextType::SENTENCE:
814 if (mxBreakIterator.is())
816 const sal_Int32 nStart (mxBreakIterator->beginOfSentence(
817 msParagraphText, nIndex-mnCharacterOffset, lang::Locale()));
818 const sal_Int32 nEnd (mxBreakIterator->endOfSentence(
819 msParagraphText, nIndex-mnCharacterOffset, lang::Locale()));
820 if (nStart < nEnd)
821 return TextSegment(
822 msParagraphText.copy(nStart, nEnd-nStart),
823 nStart+mnCharacterOffset,
824 nEnd+mnCharacterOffset);
826 break;
828 case AccessibleTextType::WORD:
829 if (mxBreakIterator.is())
830 return GetWordTextSegment(nOffset, nIndex);
831 break;
833 case AccessibleTextType::LINE:
835 for (::std::vector<Line>::const_iterator
836 iLine(maLines.begin()),
837 iEnd(maLines.end());
838 iLine!=iEnd;
839 ++iLine)
841 if (nIndex < iLine->mnLineEndCharacterIndex)
843 return TextSegment(
844 msParagraphText.copy(
845 iLine->mnLineStartCharacterIndex,
846 iLine->mnLineEndCharacterIndex - iLine->mnLineStartCharacterIndex),
847 iLine->mnLineStartCharacterIndex,
848 iLine->mnLineEndCharacterIndex);
852 break;
854 // Handle GLYPH and ATTRIBUTE_RUN like CHARACTER because we can not
855 // do better at the moment.
856 case AccessibleTextType::CHARACTER:
857 case AccessibleTextType::GLYPH:
858 case AccessibleTextType::ATTRIBUTE_RUN:
859 return CreateTextSegment(nIndex+nOffset, nIndex+nOffset+1);
862 return TextSegment(OUString(), 0,0);
865 TextSegment PresenterTextParagraph::GetWordTextSegment (
866 const sal_Int32 nOffset,
867 const sal_Int32 nIndex) const
869 sal_Int32 nCurrentOffset (nOffset);
870 sal_Int32 nCurrentIndex (nIndex);
872 i18n::Boundary aWordBoundary;
873 if (nCurrentOffset == 0)
874 aWordBoundary = mxBreakIterator->getWordBoundary(
875 msParagraphText,
876 nIndex,
877 lang::Locale(),
878 i18n::WordType::ANYWORD_IGNOREWHITESPACES,
879 sal_True);
880 else if (nCurrentOffset < 0)
882 while (nCurrentOffset<0 && nCurrentIndex>0)
884 aWordBoundary = mxBreakIterator->previousWord(
885 msParagraphText,
886 nCurrentIndex,
887 lang::Locale(),
888 i18n::WordType::ANYWORD_IGNOREWHITESPACES);
889 nCurrentIndex = aWordBoundary.startPos;
890 ++nCurrentOffset;
893 else
895 while (nCurrentOffset>0 && nCurrentIndex<=GetCharacterCount())
897 aWordBoundary = mxBreakIterator->nextWord(
898 msParagraphText,
899 nCurrentIndex,
900 lang::Locale(),
901 i18n::WordType::ANYWORD_IGNOREWHITESPACES);
902 nCurrentIndex = aWordBoundary.endPos;
903 --nCurrentOffset;
907 return CreateTextSegment(aWordBoundary.startPos, aWordBoundary.endPos);
910 TextSegment PresenterTextParagraph::CreateTextSegment (
911 sal_Int32 nStartIndex,
912 sal_Int32 nEndIndex) const
914 if (nEndIndex <= nStartIndex)
915 return TextSegment(
916 OUString(),
917 nStartIndex,
918 nEndIndex);
919 else
920 return TextSegment(
921 msParagraphText.copy(nStartIndex, nEndIndex-nStartIndex),
922 nStartIndex,
923 nEndIndex);
926 awt::Rectangle PresenterTextParagraph::GetCharacterBounds (
927 sal_Int32 nGlobalCharacterIndex,
928 const bool bCaretBox)
930 // Find the line that contains the requested character and accumulate
931 // the previous line heights.
932 double nX (mnXOrigin);
933 double nY (mnYOrigin + mnVerticalOffset + mnAscent);
934 const sal_Int8 nTextDirection (GetTextDirection());
935 for (sal_Int32 nLineIndex=0,nLineCount=maLines.size();
936 nLineIndex<nLineCount;
937 ++nLineIndex, nY+=mnLineHeight)
939 Line& rLine (maLines[nLineIndex]);
940 // Skip lines before the indexed character.
941 if (nGlobalCharacterIndex >= rLine.mnLineEndCharacterIndex)
942 // When in the last line then allow the index past the last char.
943 if (nLineIndex<nLineCount-1)
944 continue;
946 rLine.ProvideCellBoxes();
948 const sal_Int32 nCellIndex (nGlobalCharacterIndex - rLine.mnLineStartCharacterIndex);
950 // The cell bounding box is defined relative to the origin of
951 // the current line. Therefore we have to add the absolute
952 // position of the line.
953 geometry::RealRectangle2D rCellBox (rLine.maCellBoxes[
954 ::std::min(nCellIndex, rLine.maCellBoxes.getLength()-1)]);
956 double nLeft = nX + rCellBox.X1;
957 double nRight = nX + rCellBox.X2;
958 if (nTextDirection == rendering::TextDirection::WEAK_RIGHT_TO_LEFT)
960 const double nOldRight (nRight);
961 nRight = rLine.mnWidth - nLeft;
962 nLeft = rLine.mnWidth - nOldRight;
964 double nTop (nY + rCellBox.Y1);
965 double nBottom (nY + rCellBox.Y2);
966 if (bCaretBox)
968 nTop = nTop - rCellBox.Y1 - mnAscent;
969 nBottom = nTop + mnLineHeight;
970 if (nCellIndex >= rLine.maCellBoxes.getLength())
971 nLeft = nRight-2;
972 if (nLeft < nX)
973 nLeft = nX;
974 nRight = nLeft+2;
976 else
978 nTop = nTop - rCellBox.Y1 - mnAscent;
979 nBottom = nTop + mnAscent + mnDescent;
981 const sal_Int32 nX1 = sal_Int32(floor(nLeft));
982 const sal_Int32 nY1 = sal_Int32(floor(nTop));
983 const sal_Int32 nX2 = sal_Int32(ceil(nRight));
984 const sal_Int32 nY2 = sal_Int32(ceil(nBottom));
986 return awt::Rectangle(nX1,nY1,nX2-nX1+1,nY2-nY1+1);
989 // We are still here. That means that the given index lies past the
990 // last character in the paragraph.
991 // Return an empty box that lies past the last character. Better than nothing.
992 return awt::Rectangle(sal_Int32(nX+0.5), sal_Int32(nY+0.5), 0, 0);
995 sal_Int8 PresenterTextParagraph::GetTextDirection() const
997 // Find first portion that has a non-neutral text direction.
998 sal_Int32 nPosition (0);
999 sal_Int32 nTextLength (msParagraphText.getLength());
1000 while (nPosition < nTextLength)
1002 const sal_Int16 nScriptDirection (
1003 mxScriptTypeDetector->getScriptDirection(
1004 msParagraphText, nPosition, i18n::ScriptDirection::NEUTRAL));
1005 switch (nScriptDirection)
1007 case i18n::ScriptDirection::NEUTRAL:
1008 // continue looping.
1009 break;
1010 case i18n::ScriptDirection::LEFT_TO_RIGHT:
1011 return rendering::TextDirection::WEAK_LEFT_TO_RIGHT;
1013 case i18n::ScriptDirection::RIGHT_TO_LEFT:
1014 return rendering::TextDirection::WEAK_RIGHT_TO_LEFT;
1017 nPosition = mxScriptTypeDetector->endOfScriptDirection(
1018 msParagraphText, nPosition, nScriptDirection);
1021 // All text in paragraph is neutral. Fall back on writing mode taken
1022 // from the XText (which may not be properly initialized.)
1023 sal_Int8 nTextDirection(rendering::TextDirection::WEAK_LEFT_TO_RIGHT);
1024 switch(mnWritingMode)
1026 case text::WritingMode2::LR_TB:
1027 nTextDirection = rendering::TextDirection::WEAK_LEFT_TO_RIGHT;
1028 break;
1030 case text::WritingMode2::RL_TB:
1031 nTextDirection = rendering::TextDirection::WEAK_RIGHT_TO_LEFT;
1032 break;
1034 default:
1035 case text::WritingMode2::TB_RL:
1036 case text::WritingMode2::TB_LR:
1037 // Can not handle this. Use default and hope for the best.
1038 break;
1040 return nTextDirection;
1043 bool PresenterTextParagraph::IsTextReferencePointLeft() const
1045 return mnWritingMode != text::WritingMode2::RL_TB;
1048 void PresenterTextParagraph::SetupCellArray (
1049 const PresenterTheme::SharedFontDescriptor& rpFont)
1051 maCells.clear();
1053 if ( ! rpFont || ! rpFont->mxFont.is())
1054 return;
1056 sal_Int32 nPosition (0);
1057 sal_Int32 nIndex (0);
1058 const sal_Int32 nTextLength (msParagraphText.getLength());
1059 const sal_Int8 nTextDirection (GetTextDirection());
1060 while (nPosition < nTextLength)
1062 const sal_Int32 nNewPosition (mxBreakIterator->nextCharacters(
1063 msParagraphText,
1064 nPosition,
1065 lang::Locale(),
1066 i18n::CharacterIteratorMode::SKIPCELL,
1068 nIndex));
1070 rendering::StringContext aContext (msParagraphText, nPosition, nNewPosition-nPosition);
1071 Reference<rendering::XTextLayout> xLayout (
1072 rpFont->mxFont->createTextLayout(aContext, nTextDirection, 0));
1073 css::geometry::RealRectangle2D aCharacterBox (xLayout->queryTextBounds());
1075 maCells.push_back(Cell(
1076 nPosition,
1077 nNewPosition-nPosition,
1078 aCharacterBox.X2-aCharacterBox.X1));
1080 nPosition = nNewPosition;
1084 //===== PresenterTextCaret ================================================----
1086 PresenterTextCaret::PresenterTextCaret (
1087 const ::boost::function<css::awt::Rectangle(const sal_Int32,const sal_Int32)>& rCharacterBoundsAccess,
1088 const ::boost::function<void(const css::awt::Rectangle&)>& rInvalidator)
1089 : mnParagraphIndex(-1),
1090 mnCharacterIndex(-1),
1091 mnCaretBlinkTaskId(0),
1092 mbIsCaretVisible(false),
1093 maCharacterBoundsAccess(rCharacterBoundsAccess),
1094 maInvalidator(rInvalidator),
1095 maBroadcaster(),
1096 maCaretBounds()
1100 PresenterTextCaret::~PresenterTextCaret()
1102 HideCaret();
1105 void PresenterTextCaret::ShowCaret()
1107 if (mnCaretBlinkTaskId == 0)
1109 mnCaretBlinkTaskId = PresenterTimer::ScheduleRepeatedTask (
1110 ::boost::bind(&PresenterTextCaret::InvertCaret, this),
1111 CaretBlinkIntervall,
1112 CaretBlinkIntervall);
1114 mbIsCaretVisible = true;
1117 void PresenterTextCaret::HideCaret()
1119 if (mnCaretBlinkTaskId != 0)
1121 PresenterTimer::CancelTask(mnCaretBlinkTaskId);
1122 mnCaretBlinkTaskId = 0;
1124 mbIsCaretVisible = false;
1125 // Reset the caret position.
1126 mnParagraphIndex = -1;
1127 mnCharacterIndex = -1;
1132 void PresenterTextCaret::SetPosition (
1133 const sal_Int32 nParagraphIndex,
1134 const sal_Int32 nCharacterIndex)
1136 if (mnParagraphIndex != nParagraphIndex
1137 || mnCharacterIndex != nCharacterIndex)
1139 if (mnParagraphIndex >= 0)
1140 maInvalidator(maCaretBounds);
1142 const sal_Int32 nOldParagraphIndex (mnParagraphIndex);
1143 const sal_Int32 nOldCharacterIndex (mnCharacterIndex);
1144 mnParagraphIndex = nParagraphIndex;
1145 mnCharacterIndex = nCharacterIndex;
1146 maCaretBounds = maCharacterBoundsAccess(mnParagraphIndex, mnCharacterIndex);
1147 if (mnParagraphIndex >= 0)
1148 ShowCaret();
1149 else
1150 HideCaret();
1152 if (mnParagraphIndex >= 0)
1153 maInvalidator(maCaretBounds);
1155 if (maBroadcaster)
1156 maBroadcaster(
1157 nOldParagraphIndex,
1158 nOldCharacterIndex,
1159 mnParagraphIndex,
1160 mnCharacterIndex);
1166 void PresenterTextCaret::SetCaretMotionBroadcaster (
1167 const ::boost::function<void(sal_Int32,sal_Int32,sal_Int32,sal_Int32)>& rBroadcaster)
1169 maBroadcaster = rBroadcaster;
1172 css::awt::Rectangle PresenterTextCaret::GetBounds() const
1174 return maCaretBounds;
1177 void PresenterTextCaret::InvertCaret()
1179 mbIsCaretVisible = !mbIsCaretVisible;
1180 if (mnParagraphIndex >= 0)
1181 maInvalidator(maCaretBounds);
1184 //===== PresenterTextParagraph::Cell ==========================================
1186 PresenterTextParagraph::Cell::Cell (
1187 const sal_Int32 nCharacterIndex,
1188 const sal_Int32 nCharacterCount,
1189 const double nCellWidth)
1190 : mnCharacterIndex(nCharacterIndex),
1191 mnCharacterCount(nCharacterCount),
1192 mnCellWidth(nCellWidth)
1196 //===== PresenterTextParagraph::Line ==========================================
1198 PresenterTextParagraph::Line::Line (
1199 const sal_Int32 nLineStartCharacterIndex,
1200 const sal_Int32 nLineEndCharacterIndex)
1201 : mnLineStartCharacterIndex(nLineStartCharacterIndex),
1202 mnLineEndCharacterIndex(nLineEndCharacterIndex),
1203 mnLineStartCellIndex(-1), mnLineEndCellIndex(-1),
1204 mxLayoutedLine(),
1205 mnBaseLine(0), mnWidth(0),
1206 maCellBoxes()
1210 void PresenterTextParagraph::Line::ProvideCellBoxes()
1212 if ( ! IsEmpty() && maCellBoxes.getLength()==0)
1214 if (mxLayoutedLine.is())
1215 maCellBoxes = mxLayoutedLine->queryInkMeasures();
1216 else
1218 OSL_ASSERT(mxLayoutedLine.is());
1223 void PresenterTextParagraph::Line::ProvideLayoutedLine (
1224 const OUString& rsParagraphText,
1225 const PresenterTheme::SharedFontDescriptor& rpFont,
1226 const sal_Int8 nTextDirection)
1228 if ( ! mxLayoutedLine.is())
1230 const rendering::StringContext aContext (
1231 rsParagraphText,
1232 mnLineStartCharacterIndex,
1233 mnLineEndCharacterIndex - mnLineStartCharacterIndex);
1235 mxLayoutedLine = rpFont->mxFont->createTextLayout(
1236 aContext,
1237 nTextDirection,
1242 bool PresenterTextParagraph::Line::IsEmpty() const
1244 return mnLineStartCharacterIndex >= mnLineEndCharacterIndex;
1247 } } // end of namespace ::sdext::presenter
1249 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */