diff options
-rw-r--r-- | include/vcl/outdev.hxx | 23 | ||||
-rw-r--r-- | include/vcl/window.hxx | 2 | ||||
-rw-r--r-- | vcl/inc/textlayout.hxx | 26 | ||||
-rw-r--r-- | vcl/source/gdi/pdfwriter_impl.cxx | 2 | ||||
-rw-r--r-- | vcl/source/gdi/textlayout.cxx | 396 | ||||
-rw-r--r-- | vcl/source/outdev/text.cxx | 427 | ||||
-rw-r--r-- | vcl/source/window/window3.cxx | 2 |
7 files changed, 442 insertions, 436 deletions
diff --git a/include/vcl/outdev.hxx b/include/vcl/outdev.hxx index 4a6d217c2cb6..bea1e00d9b7f 100644 --- a/include/vcl/outdev.hxx +++ b/include/vcl/outdev.hxx @@ -102,7 +102,7 @@ class SalLayoutGlyphs; namespace vcl { class ExtOutDevData; - class ITextLayout; + class TextLayoutCommon; struct FontCapabilities; class Window; class WindowOutputDevice; @@ -869,11 +869,11 @@ public: void DrawText( const tools::Rectangle& rRect, const OUString& rStr, DrawTextFlags nStyle = DrawTextFlags::NONE, std::vector< tools::Rectangle >* pVector = nullptr, OUString* pDisplayText = nullptr, - vcl::ITextLayout* _pTextLayout = nullptr ); + vcl::TextLayoutCommon* _pTextLayout = nullptr ); static void ImplDrawText( OutputDevice& rTargetDevice, const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle, - std::vector< tools::Rectangle >* pVector, OUString* pDisplayText, vcl::ITextLayout& _rLayout ); + std::vector< tools::Rectangle >* pVector, OUString* pDisplayText, vcl::TextLayoutCommon& _rLayout ); void ImplDrawText( SalLayout& ); @@ -904,7 +904,7 @@ public: tools::Rectangle GetTextRect( const tools::Rectangle& rRect, const OUString& rStr, DrawTextFlags nStyle = DrawTextFlags::WordBreak, TextRectInfo* pInfo = nullptr, - const vcl::ITextLayout* _pTextLayout = nullptr ) const; + const vcl::TextLayoutCommon* _pTextLayout = nullptr ) const; /** Return the exact bounding rectangle of rStr. @@ -1073,17 +1073,6 @@ public: protected: SAL_DLLPRIVATE void ImplInitTextLineSize(); SAL_DLLPRIVATE void ImplInitAboveTextLineSize(); - static - SAL_DLLPRIVATE tools::Long ImplGetTextLines( const tools::Rectangle& rRect, tools::Long nTextHeight, ImplMultiTextLineInfo& rLineInfo, tools::Long nWidth, const OUString& rStr, DrawTextFlags nStyle, const vcl::ITextLayout& _rLayout ); - static - SAL_DLLPRIVATE sal_Int32 ImplBreakLinesWithIterator(const tools::Long nWidth, const OUString& rStr, const vcl::ITextLayout& _rLayout, - const css::uno::Reference<css::linguistic2::XHyphenator>& xHyph, - const css::uno::Reference<css::i18n::XBreakIterator>& xBI, - const bool bHyphenate, - const sal_Int32 nPos, sal_Int32 nBreakPos); - static - SAL_DLLPRIVATE sal_Int32 ImplBreakLinesSimple( const tools::Long nWidth, const OUString& rStr, - const vcl::ITextLayout& _rLayout, const sal_Int32 nPos, sal_Int32 nBreakPos, tools::Long& nLineWidth ); SAL_DLLPRIVATE float approximate_char_width() const; virtual bool shouldDrawWavePixelAsRect(tools::Long nLineWidth) const; @@ -1200,10 +1189,6 @@ private: SAL_DLLPRIVATE static void ImplUpdateFontDataForAllFrames( FontUpdateHandler_t pHdl, bool bNewFontLists ); - static - SAL_DLLPRIVATE OUString ImplGetEllipsisString( const OUString& rStr, - tools::Long nMaxWidth, DrawTextFlags nStyle, const vcl::ITextLayout& _rLayout ); - SAL_DLLPRIVATE void ImplDrawEmphasisMark( tools::Long nBaseX, tools::Long nX, tools::Long nY, const tools::PolyPolygon& rPolyPoly, bool bPolyLine, const tools::Rectangle& rRect1, const tools::Rectangle& rRect2 ); SAL_DLLPRIVATE void ImplDrawEmphasisMarks( SalLayout& ); ///@} diff --git a/include/vcl/window.hxx b/include/vcl/window.hxx index 73f2e4e5bc03..640e9c6c9983 100644 --- a/include/vcl/window.hxx +++ b/include/vcl/window.hxx @@ -1494,7 +1494,7 @@ public: tools::Rectangle GetTextRect( const tools::Rectangle& rRect, const OUString& rStr, DrawTextFlags nStyle = DrawTextFlags::WordBreak, TextRectInfo* pInfo = nullptr, - const vcl::ITextLayout* _pTextLayout = nullptr ) const; + const vcl::TextLayoutCommon* _pTextLayout = nullptr ) const; float GetDPIScaleFactor() const; tools::Long GetOutOffXPixel() const; tools::Long GetOutOffYPixel() const; diff --git a/vcl/inc/textlayout.hxx b/vcl/inc/textlayout.hxx index 5938a9d58ef7..ee9eb9e39266 100644 --- a/vcl/inc/textlayout.hxx +++ b/vcl/inc/textlayout.hxx @@ -26,6 +26,10 @@ #include <memory> #include <vector> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <com/sun/star/linguistic2/LinguServiceManager.hpp> + class Control; namespace vcl @@ -44,10 +48,30 @@ namespace vcl ~ITextLayout() COVERITY_NOEXCEPT_FALSE {} }; + class TextLayoutCommon : public ITextLayout + { + public: + OUString GetEllipsisString(OUString const& rOrigStr, tools::Long nMaxWidth, DrawTextFlags nStyle); + + sal_Int32 BreakLinesWithIterator(const tools::Long nWidth, OUString const& rStr, + css::uno::Reference< css::linguistic2::XHyphenator > const& xHyph, + css::uno::Reference<css::i18n::XBreakIterator> const& xBI, + const bool bHyphenate, + const sal_Int32 nPos, sal_Int32 nBreakPos); + + sal_Int32 BreakLinesSimple(const tools::Long nWidth, OUString const& rStr, + const sal_Int32 nPos, sal_Int32 nBreakPos, tools::Long& nLineWidth); + + tools::Long GetTextLines(tools::Rectangle const& rRect, const tools::Long nTextHeight, + ImplMultiTextLineInfo& rLineInfo, + tools::Long nWidth, OUString const& rStr, + DrawTextFlags nStyle); + }; + /** is an implementation of the ITextLayout interface which simply delegates its calls to the respective methods of an OutputDevice instance, without any inbetween magic. */ - class DefaultTextLayout final : public ITextLayout + class DefaultTextLayout final : public TextLayoutCommon { public: DefaultTextLayout( OutputDevice& _rTargetDevice ) diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 506fde66b6c7..5699b2dc6ddc 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -7304,7 +7304,7 @@ void PDFWriterImpl::drawText( const tools::Rectangle& rRect, const OUString& rOr { vcl::DefaultTextLayout aLayout( *this ); OUString aLastLine; - OutputDevice::ImplGetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle, aLayout ); + aLayout.GetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle ); sal_Int32 nLines = nHeight/nTextHeight; nFormatLines = aMultiLineInfo.Count(); if ( !nLines ) diff --git a/vcl/source/gdi/textlayout.cxx b/vcl/source/gdi/textlayout.cxx index dd559ac470dc..2eaf5746f30b 100644 --- a/vcl/source/gdi/textlayout.cxx +++ b/vcl/source/gdi/textlayout.cxx @@ -20,23 +20,409 @@ #include <vcl/ctrl.hxx> #include <vcl/kernarray.hxx> #include <vcl/outdev.hxx> +#include <vcl/svapp.hxx> +#include <vcl/unohelp.hxx> #include <textlayout.hxx> +#include <textlineinfo.hxx> #include <osl/diagnose.h> +#include <osl/file.h> +#include <rtl/ustrbuf.hxx> #include <tools/fract.hxx> #include <sal/log.hxx> - -#if OSL_DEBUG_LEVEL > 1 -#include <rtl/strbuf.hxx> -#endif +#include <comphelper/processfactory.hxx> +#include <i18nlangtag/languagetag.hxx> #include <memory> #include <iterator> +static bool ImplIsCharIn(sal_Unicode c, const char* pStr) +{ + while ( *pStr ) + { + if ( *pStr == c ) + return true; + pStr++; + } + + return false; +} + +ImplMultiTextLineInfo::ImplMultiTextLineInfo() +{ +} + +ImplMultiTextLineInfo::~ImplMultiTextLineInfo() +{ +} + +void ImplMultiTextLineInfo::AddLine( const ImplTextLineInfo& rLine ) +{ + mvLines.push_back(rLine); +} + +void ImplMultiTextLineInfo::Clear() +{ + mvLines.clear(); +} + namespace vcl { + OUString TextLayoutCommon::GetEllipsisString(OUString const& rOrigStr, tools::Long nMaxWidth, DrawTextFlags nStyle) + { + OUString aStr = rOrigStr; + sal_Int32 nIndex = GetTextBreak( aStr, nMaxWidth, 0, aStr.getLength() ); + + if ( nIndex != -1 ) + { + if( (nStyle & DrawTextFlags::CenterEllipsis) == DrawTextFlags::CenterEllipsis ) + { + OUStringBuffer aTmpStr( aStr ); + // speed it up by removing all but 1.33x as many as the break pos. + sal_Int32 nEraseChars = std::max<sal_Int32>(4, aStr.getLength() - (nIndex*4)/3); + while( nEraseChars < aStr.getLength() && GetTextWidth( aTmpStr.toString(), 0, aTmpStr.getLength() ) > nMaxWidth ) + { + aTmpStr = aStr; + sal_Int32 i = (aTmpStr.getLength() - nEraseChars)/2; + aTmpStr.remove(i, nEraseChars++); + aTmpStr.insert(i, "..."); + } + aStr = aTmpStr.makeStringAndClear(); + } + else if ( nStyle & DrawTextFlags::EndEllipsis ) + { + aStr = aStr.copy(0, nIndex); + if ( nIndex > 1 ) + { + aStr += "..."; + while ( !aStr.isEmpty() && ( GetTextWidth( aStr, 0, aStr.getLength() ) > nMaxWidth) ) + { + if ( (nIndex > 1) || (nIndex == aStr.getLength()) ) + nIndex--; + aStr = aStr.replaceAt( nIndex, 1, u""); + } + } + + if ( aStr.isEmpty() && (nStyle & DrawTextFlags::Clip) ) + aStr += OUStringChar(rOrigStr[ 0 ]); + } + else if ( nStyle & DrawTextFlags::PathEllipsis ) + { + OUString aPath( rOrigStr ); + OUString aAbbreviatedPath; + osl_abbreviateSystemPath( aPath.pData, &aAbbreviatedPath.pData, nIndex, nullptr ); + aStr = aAbbreviatedPath; + } + else if ( nStyle & DrawTextFlags::NewsEllipsis ) + { + static char const pSepChars[] = "."; + // Determine last section + sal_Int32 nLastContent = aStr.getLength(); + while ( nLastContent ) + { + nLastContent--; + if ( ImplIsCharIn( aStr[ nLastContent ], pSepChars ) ) + break; + } + while ( nLastContent && + ImplIsCharIn( aStr[ nLastContent-1 ], pSepChars ) ) + nLastContent--; + + OUString aLastStr = aStr.copy(nLastContent); + OUString aTempLastStr1 = "..." + aLastStr; + if ( GetTextWidth( aTempLastStr1, 0, aTempLastStr1.getLength() ) > nMaxWidth ) + aStr = GetEllipsisString( aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis ); + else + { + sal_Int32 nFirstContent = 0; + while ( nFirstContent < nLastContent ) + { + nFirstContent++; + if ( ImplIsCharIn( aStr[ nFirstContent ], pSepChars ) ) + break; + } + while ( (nFirstContent < nLastContent) && + ImplIsCharIn( aStr[ nFirstContent ], pSepChars ) ) + nFirstContent++; + // MEM continue here + if ( nFirstContent >= nLastContent ) + aStr = GetEllipsisString( aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis ); + else + { + if ( nFirstContent > 4 ) + nFirstContent = 4; + OUString aFirstStr = OUString::Concat(aStr.subView( 0, nFirstContent )) + "..."; + OUString aTempStr = aFirstStr + aLastStr; + if ( GetTextWidth( aTempStr, 0, aTempStr.getLength() ) > nMaxWidth ) + aStr = GetEllipsisString( aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis ); + else + { + do + { + aStr = aTempStr; + if( nLastContent > aStr.getLength() ) + nLastContent = aStr.getLength(); + while ( nFirstContent < nLastContent ) + { + nLastContent--; + if ( ImplIsCharIn( aStr[ nLastContent ], pSepChars ) ) + break; + + } + while ( (nFirstContent < nLastContent) && + ImplIsCharIn( aStr[ nLastContent-1 ], pSepChars ) ) + nLastContent--; + + if ( nFirstContent < nLastContent ) + { + std::u16string_view aTempLastStr = aStr.subView( nLastContent ); + aTempStr = aFirstStr + aTempLastStr; + + if ( GetTextWidth( aTempStr, 0, aTempStr.getLength() ) > nMaxWidth ) + break; + } + } + while ( nFirstContent < nLastContent ); + } + } + } + } + } + + return aStr; + } + + sal_Int32 TextLayoutCommon::BreakLinesWithIterator(const tools::Long nWidth, OUString const& rStr, + css::uno::Reference< css::linguistic2::XHyphenator > const& xHyph, + css::uno::Reference<css::i18n::XBreakIterator> const& xBI, + const bool bHyphenate, + const sal_Int32 nPos, sal_Int32 nBreakPos) + { + const css::lang::Locale& rDefLocale(Application::GetSettings().GetUILanguageTag().getLocale()); + sal_Int32 nSoftBreak = GetTextBreak( rStr, nWidth, nPos, nBreakPos - nPos ); + if (nSoftBreak == -1) + { + nSoftBreak = nPos; + } + SAL_WARN_IF( nSoftBreak >= nBreakPos, "vcl", "Break?!" ); + css::i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, css::uno::Sequence <css::beans::PropertyValue>(), 1 ); + css::i18n::LineBreakUserOptions aUserOptions; + css::i18n::LineBreakResults aLBR = xBI->getLineBreak( rStr, nSoftBreak, rDefLocale, nPos, aHyphOptions, aUserOptions ); + nBreakPos = aLBR.breakIndex; + if ( nBreakPos <= nPos ) + nBreakPos = nSoftBreak; + if ( !bHyphenate ) + return nBreakPos; + + // Whether hyphen or not: Put the word after the hyphen through + // word boundary. + + // nMaxBreakPos the last char that fits into the line + // nBreakPos is the word's start + + // We run into a problem if the doc is so narrow, that a word + // is broken into more than two lines ... + if ( !xHyph.is() ) + return nBreakPos; + + css::i18n::Boundary aBoundary = xBI->getWordBoundary( rStr, nBreakPos, rDefLocale, css::i18n::WordType::DICTIONARY_WORD, true ); + sal_Int32 nWordStart = nPos; + sal_Int32 nWordEnd = aBoundary.endPos; + SAL_WARN_IF( nWordEnd <= nWordStart, "vcl", "ImpBreakLine: Start >= End?" ); + + sal_Int32 nWordLen = nWordEnd - nWordStart; + if ( ( nWordEnd < nSoftBreak ) || ( nWordLen <= 3 ) ) + return nBreakPos; + + // #104415# May happen, because getLineBreak may differ from getWordBoundary with DICTIONARY_WORD + // SAL_WARN_IF( nWordEnd < nMaxBreakPos, "vcl", "Hyph: Break?" ); + OUString aWord = rStr.copy( nWordStart, nWordLen ); + sal_Int32 nMinTrail = nWordEnd-nSoftBreak+1; //+1: Before the "broken off" char + css::uno::Reference< css::linguistic2::XHyphenatedWord > xHyphWord; + if (xHyph.is()) + xHyphWord = xHyph->hyphenate( aWord, rDefLocale, aWord.getLength() - nMinTrail, css::uno::Sequence< css::beans::PropertyValue >() ); + if (!xHyphWord.is()) + return nBreakPos; + + bool bAlternate = xHyphWord->isAlternativeSpelling(); + sal_Int32 _nWordLen = 1 + xHyphWord->getHyphenPos(); + + if ( ( _nWordLen < 2 ) || ( (nWordStart+_nWordLen) < 2 ) ) + return nBreakPos; + + if ( bAlternate ) + { + nBreakPos = nWordStart + _nWordLen; + return nBreakPos; + } + + OUString aAlt( xHyphWord->getHyphenatedWord() ); + + // We can have two cases: + // 1) "packen" turns into "pak-ken" + // 2) "Schiffahrt" turns into "Schiff-fahrt" + + // In case 1 we need to replace a char + // In case 2 we add a char + + // Correct recognition is made harder by words such as + // "Schiffahrtsbrennesseln", as the Hyphenator splits all + // positions of the word and comes up with "Schifffahrtsbrennnesseln" + // Thus, we cannot infer the aWord from the AlternativeWord's + // index. + // TODO: The whole junk will be made easier by a function in + // the Hyphenator, as soon as AMA adds it. + sal_Int32 nAltStart = _nWordLen - 1; + sal_Int32 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength()); + sal_Int32 nTxtEnd = nTxtStart; + sal_Int32 nAltEnd = nAltStart; + + // The area between nStart and nEnd is the difference + // between AlternativeString and OriginalString + while( nTxtEnd < aWord.getLength() && nAltEnd < aAlt.getLength() && + aWord[nTxtEnd] != aAlt[nAltEnd] ) + { + ++nTxtEnd; + ++nAltEnd; + } + + // If a char was added, we notice it now: + if( nAltEnd > nTxtEnd && nAltStart == nAltEnd && + aWord[ nTxtEnd ] == aAlt[nAltEnd] ) + { + ++nAltEnd; + ++nTxtStart; + ++nTxtEnd; + } + + SAL_INFO_IF( ( nAltEnd - nAltStart ) != 1, "vcl", "Alternate: Wrong assumption!" ); + + sal_Unicode cAlternateReplChar = 0; + if ( nTxtEnd > nTxtStart ) + cAlternateReplChar = aAlt[ nAltStart ]; + + nBreakPos = nWordStart + nTxtStart; + if ( cAlternateReplChar ) + nBreakPos++; + return nBreakPos; + } + + sal_Int32 TextLayoutCommon::BreakLinesSimple(const tools::Long nWidth, OUString const& rStr, + const sal_Int32 nPos, sal_Int32 nBreakPos, tools::Long& nLineWidth) + { + sal_Int32 nSpacePos = rStr.getLength(); + tools::Long nW = 0; + do + { + nSpacePos = rStr.lastIndexOf( ' ', nSpacePos ); + if( nSpacePos != -1 ) + { + if( nSpacePos > nPos ) + nSpacePos--; + nW = GetTextWidth( rStr, nPos, nSpacePos-nPos ); + } + } while( nW > nWidth ); + + if( nSpacePos != -1 ) + { + nBreakPos = nSpacePos; + nLineWidth = GetTextWidth( rStr, nPos, nBreakPos-nPos ); + if( nBreakPos < rStr.getLength()-1 ) + nBreakPos++; + } + return nBreakPos; + } + + tools::Long TextLayoutCommon::GetTextLines(tools::Rectangle const& rRect, const tools::Long nTextHeight, + ImplMultiTextLineInfo& rLineInfo, + tools::Long nWidth, OUString const& rStr, + DrawTextFlags nStyle) + { + SAL_WARN_IF( nWidth <= 0, "vcl", "ImplGetTextLines: nWidth <= 0!" ); + + if ( nWidth <= 0 ) + nWidth = 1; + + rLineInfo.Clear(); + if (rStr.isEmpty()) + return 0; + + const bool bClipping = (nStyle & DrawTextFlags::Clip) && !(nStyle & DrawTextFlags::EndEllipsis); + + tools::Long nMaxLineWidth = 0; + const bool bHyphenate = (nStyle & DrawTextFlags::WordBreakHyphenation) == DrawTextFlags::WordBreakHyphenation; + css::uno::Reference< css::linguistic2::XHyphenator > xHyph; + if (bHyphenate) + { + // get service provider + css::uno::Reference<css::uno::XComponentContext> xContext(comphelper::getProcessComponentContext()); + css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLinguMgr = css::linguistic2::LinguServiceManager::create(xContext); + xHyph = xLinguMgr->getHyphenator(); + } + + css::uno::Reference<css::i18n::XBreakIterator> xBI; + sal_Int32 nPos = 0; + sal_Int32 nLen = rStr.getLength(); + sal_Int32 nCurrentTextY = 0; + while ( nPos < nLen ) + { + sal_Int32 nBreakPos = nPos; + + while ( ( nBreakPos < nLen ) && ( rStr[ nBreakPos ] != '\r' ) && ( rStr[ nBreakPos ] != '\n' ) ) + nBreakPos++; + + tools::Long nLineWidth = GetTextWidth( rStr, nPos, nBreakPos-nPos ); + if ( ( nLineWidth > nWidth ) && ( nStyle & DrawTextFlags::WordBreak ) ) + { + if ( !xBI.is() ) + xBI = vcl::unohelper::CreateBreakIterator(); + + if ( xBI.is() ) + { + nBreakPos = BreakLinesWithIterator(nWidth, rStr, xHyph, xBI, bHyphenate, nPos, nBreakPos); + nLineWidth = GetTextWidth(rStr, nPos, nBreakPos - nPos); + } + else + // fallback to something really simple + nBreakPos = BreakLinesSimple(nWidth, rStr, nPos, nBreakPos, nLineWidth); + } + + if ( nLineWidth > nMaxLineWidth ) + nMaxLineWidth = nLineWidth; + + rLineInfo.AddLine( ImplTextLineInfo( nLineWidth, nPos, nBreakPos-nPos ) ); + + if ( nBreakPos == nPos ) + nBreakPos++; + nPos = nBreakPos; + + if ( nPos < nLen && ( ( rStr[ nPos ] == '\r' ) || ( rStr[ nPos ] == '\n' ) ) ) + { + nPos++; + // CR/LF? + if ( ( nPos < nLen ) && ( rStr[ nPos ] == '\n' ) && ( rStr[ nPos-1 ] == '\r' ) ) + nPos++; + } + nCurrentTextY += nTextHeight; + if (bClipping && nCurrentTextY > rRect.GetHeight()) + break; + } + +#ifdef DBG_UTIL + for ( sal_Int32 nL = 0; nL < rLineInfo.Count(); nL++ ) + { + ImplTextLineInfo& rLine = rLineInfo.GetLine( nL ); + OUString aLine = rStr.copy( rLine.GetIndex(), rLine.GetLen() ); + SAL_WARN_IF( aLine.indexOf( '\r' ) != -1, "vcl", "ImplGetTextLines - Found CR!" ); + SAL_WARN_IF( aLine.indexOf( '\n' ) != -1, "vcl", "ImplGetTextLines - Found LF!" ); + } +#endif + + return nMaxLineWidth; + } + DefaultTextLayout::~DefaultTextLayout() { } @@ -68,7 +454,7 @@ namespace vcl return false; } - class ReferenceDeviceTextLayout : public ITextLayout + class ReferenceDeviceTextLayout : public TextLayoutCommon { public: ReferenceDeviceTextLayout( const Control& _rControl, OutputDevice& _rTargetDevice, OutputDevice& _rReferenceDevice ); diff --git a/vcl/source/outdev/text.cxx b/vcl/source/outdev/text.cxx index c2978d971659..1b40a1b2de28 100644 --- a/vcl/source/outdev/text.cxx +++ b/vcl/source/outdev/text.cxx @@ -19,11 +19,8 @@ #include <sal/config.h> -#include <osl/file.h> -#include <rtl/ustrbuf.hxx> #include <sal/log.hxx> #include <basegfx/matrix/b2dhommatrix.hxx> -#include <comphelper/processfactory.hxx> #include <tools/lineend.hxx> #include <tools/debug.hxx> #include <unotools/configmgr.hxx> @@ -35,7 +32,6 @@ #include <vcl/textrectinfo.hxx> #include <vcl/virdev.hxx> #include <vcl/sysdata.hxx> -#include <vcl/unohelp.hxx> #include <ImplLayoutArgs.hxx> #include <ImplOutDevData.hxx> @@ -48,10 +44,6 @@ #include <TextLayoutCache.hxx> #include <font/PhysicalFontFace.hxx> -#include <com/sun/star/i18n/WordType.hpp> -#include <com/sun/star/i18n/XBreakIterator.hpp> -#include <com/sun/star/linguistic2/LinguServiceManager.hpp> - #include <memory> #include <optional> @@ -79,24 +71,6 @@ void OutputDevice::SetDigitLanguage( LanguageType eTextLanguage ) mpAlphaVDev->SetDigitLanguage( eTextLanguage ); } -ImplMultiTextLineInfo::ImplMultiTextLineInfo() -{ -} - -ImplMultiTextLineInfo::~ImplMultiTextLineInfo() -{ -} - -void ImplMultiTextLineInfo::AddLine( const ImplTextLineInfo& rLine ) -{ - mvLines.push_back(rLine); -} - -void ImplMultiTextLineInfo::Clear() -{ - mvLines.clear(); -} - void OutputDevice::ImplInitTextColor() { DBG_TESTSOLARMUTEX(); @@ -108,6 +82,13 @@ void OutputDevice::ImplInitTextColor() } } +OUString OutputDevice::GetEllipsisString( const OUString& rOrigStr, tools::Long nMaxWidth, + DrawTextFlags nStyle ) const +{ + vcl::DefaultTextLayout aTextLayout(*const_cast< OutputDevice* >(this)); + return aTextLayout.GetEllipsisString(rOrigStr, nMaxWidth, nStyle); +} + void OutputDevice::ImplDrawTextRect( tools::Long nBaseX, tools::Long nBaseY, tools::Long nDistX, tools::Long nDistY, tools::Long nWidth, tools::Long nHeight ) { @@ -482,237 +463,6 @@ void OutputDevice::ImplDrawText( SalLayout& rSalLayout ) ImplDrawTextDirect( rSalLayout, mbTextLines ); } -tools::Long OutputDevice::ImplGetTextLines( const tools::Rectangle& rRect, const tools::Long nTextHeight, - ImplMultiTextLineInfo& rLineInfo, - tools::Long nWidth, const OUString& rStr, - DrawTextFlags nStyle, const vcl::ITextLayout& _rLayout ) -{ - SAL_WARN_IF( nWidth <= 0, "vcl", "ImplGetTextLines: nWidth <= 0!" ); - - if ( nWidth <= 0 ) - nWidth = 1; - - rLineInfo.Clear(); - if (rStr.isEmpty()) - return 0; - - const bool bClipping = (nStyle & DrawTextFlags::Clip) && !(nStyle & DrawTextFlags::EndEllipsis); - - tools::Long nMaxLineWidth = 0; - const bool bHyphenate = (nStyle & DrawTextFlags::WordBreakHyphenation) == DrawTextFlags::WordBreakHyphenation; - css::uno::Reference< css::linguistic2::XHyphenator > xHyph; - if (bHyphenate) - { - // get service provider - css::uno::Reference<css::uno::XComponentContext> xContext(comphelper::getProcessComponentContext()); - css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLinguMgr = css::linguistic2::LinguServiceManager::create(xContext); - xHyph = xLinguMgr->getHyphenator(); - } - - css::uno::Reference<css::i18n::XBreakIterator> xBI; - sal_Int32 nPos = 0; - sal_Int32 nLen = rStr.getLength(); - sal_Int32 nCurrentTextY = 0; - while ( nPos < nLen ) - { - sal_Int32 nBreakPos = nPos; - - while ( ( nBreakPos < nLen ) && ( rStr[ nBreakPos ] != '\r' ) && ( rStr[ nBreakPos ] != '\n' ) ) - nBreakPos++; - - tools::Long nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos ); - if ( ( nLineWidth > nWidth ) && ( nStyle & DrawTextFlags::WordBreak ) ) - { - if ( !xBI.is() ) - xBI = vcl::unohelper::CreateBreakIterator(); - - if ( xBI.is() ) - { - nBreakPos = ImplBreakLinesWithIterator(nWidth, rStr, _rLayout, xHyph, xBI, bHyphenate, nPos, nBreakPos); - nLineWidth = _rLayout.GetTextWidth(rStr, nPos, nBreakPos - nPos); - } - else - // fallback to something really simple - nBreakPos = ImplBreakLinesSimple(nWidth, rStr, _rLayout, nPos, nBreakPos, nLineWidth); - } - - if ( nLineWidth > nMaxLineWidth ) - nMaxLineWidth = nLineWidth; - - rLineInfo.AddLine( ImplTextLineInfo( nLineWidth, nPos, nBreakPos-nPos ) ); - - if ( nBreakPos == nPos ) - nBreakPos++; - nPos = nBreakPos; - - if ( nPos < nLen && ( ( rStr[ nPos ] == '\r' ) || ( rStr[ nPos ] == '\n' ) ) ) - { - nPos++; - // CR/LF? - if ( ( nPos < nLen ) && ( rStr[ nPos ] == '\n' ) && ( rStr[ nPos-1 ] == '\r' ) ) - nPos++; - } - nCurrentTextY += nTextHeight; - if (bClipping && nCurrentTextY > rRect.GetHeight()) - break; - } - -#ifdef DBG_UTIL - for ( sal_Int32 nL = 0; nL < rLineInfo.Count(); nL++ ) - { - ImplTextLineInfo& rLine = rLineInfo.GetLine( nL ); - OUString aLine = rStr.copy( rLine.GetIndex(), rLine.GetLen() ); - SAL_WARN_IF( aLine.indexOf( '\r' ) != -1, "vcl", "ImplGetTextLines - Found CR!" ); - SAL_WARN_IF( aLine.indexOf( '\n' ) != -1, "vcl", "ImplGetTextLines - Found LF!" ); - } -#endif - - return nMaxLineWidth; -} - -sal_Int32 OutputDevice::ImplBreakLinesWithIterator(const tools::Long nWidth, const OUString& rStr, const vcl::ITextLayout& _rLayout, - const css::uno::Reference< css::linguistic2::XHyphenator >& xHyph, - const css::uno::Reference<css::i18n::XBreakIterator>& xBI, - const bool bHyphenate, - const sal_Int32 nPos, sal_Int32 nBreakPos) -{ - const css::lang::Locale& rDefLocale(Application::GetSettings().GetUILanguageTag().getLocale()); - sal_Int32 nSoftBreak = _rLayout.GetTextBreak( rStr, nWidth, nPos, nBreakPos - nPos ); - if (nSoftBreak == -1) - { - nSoftBreak = nPos; - } - SAL_WARN_IF( nSoftBreak >= nBreakPos, "vcl", "Break?!" ); - css::i18n::LineBreakHyphenationOptions aHyphOptions( xHyph, css::uno::Sequence <css::beans::PropertyValue>(), 1 ); - css::i18n::LineBreakUserOptions aUserOptions; - css::i18n::LineBreakResults aLBR = xBI->getLineBreak( rStr, nSoftBreak, rDefLocale, nPos, aHyphOptions, aUserOptions ); - nBreakPos = aLBR.breakIndex; - if ( nBreakPos <= nPos ) - nBreakPos = nSoftBreak; - if ( !bHyphenate ) - return nBreakPos; - - // Whether hyphen or not: Put the word after the hyphen through - // word boundary. - - // nMaxBreakPos the last char that fits into the line - // nBreakPos is the word's start - - // We run into a problem if the doc is so narrow, that a word - // is broken into more than two lines ... - if ( !xHyph.is() ) - return nBreakPos; - - css::i18n::Boundary aBoundary = xBI->getWordBoundary( rStr, nBreakPos, rDefLocale, css::i18n::WordType::DICTIONARY_WORD, true ); - sal_Int32 nWordStart = nPos; - sal_Int32 nWordEnd = aBoundary.endPos; - SAL_WARN_IF( nWordEnd <= nWordStart, "vcl", "ImpBreakLine: Start >= End?" ); - - sal_Int32 nWordLen = nWordEnd - nWordStart; - if ( ( nWordEnd < nSoftBreak ) || ( nWordLen <= 3 ) ) - return nBreakPos; - - // #104415# May happen, because getLineBreak may differ from getWordBoundary with DICTIONARY_WORD - // SAL_WARN_IF( nWordEnd < nMaxBreakPos, "vcl", "Hyph: Break?" ); - OUString aWord = rStr.copy( nWordStart, nWordLen ); - sal_Int32 nMinTrail = nWordEnd-nSoftBreak+1; //+1: Before the "broken off" char - css::uno::Reference< css::linguistic2::XHyphenatedWord > xHyphWord; - if (xHyph.is()) - xHyphWord = xHyph->hyphenate( aWord, rDefLocale, aWord.getLength() - nMinTrail, css::uno::Sequence< css::beans::PropertyValue >() ); - if (!xHyphWord.is()) - return nBreakPos; - - bool bAlternate = xHyphWord->isAlternativeSpelling(); - sal_Int32 _nWordLen = 1 + xHyphWord->getHyphenPos(); - - if ( ( _nWordLen < 2 ) || ( (nWordStart+_nWordLen) < 2 ) ) - return nBreakPos; - - if ( bAlternate ) - { - nBreakPos = nWordStart + _nWordLen; - return nBreakPos; - } - - - OUString aAlt( xHyphWord->getHyphenatedWord() ); - - // We can have two cases: - // 1) "packen" turns into "pak-ken" - // 2) "Schiffahrt" turns into "Schiff-fahrt" - - // In case 1 we need to replace a char - // In case 2 we add a char - - // Correct recognition is made harder by words such as - // "Schiffahrtsbrennesseln", as the Hyphenator splits all - // positions of the word and comes up with "Schifffahrtsbrennnesseln" - // Thus, we cannot infer the aWord from the AlternativeWord's - // index. - // TODO: The whole junk will be made easier by a function in - // the Hyphenator, as soon as AMA adds it. - sal_Int32 nAltStart = _nWordLen - 1; - sal_Int32 nTxtStart = nAltStart - (aAlt.getLength() - aWord.getLength()); - sal_Int32 nTxtEnd = nTxtStart; - sal_Int32 nAltEnd = nAltStart; - - // The area between nStart and nEnd is the difference - // between AlternativeString and OriginalString - while( nTxtEnd < aWord.getLength() && nAltEnd < aAlt.getLength() && - aWord[nTxtEnd] != aAlt[nAltEnd] ) - { - ++nTxtEnd; - ++nAltEnd; - } - - // If a char was added, we notice it now: - if( nAltEnd > nTxtEnd && nAltStart == nAltEnd && - aWord[ nTxtEnd ] == aAlt[nAltEnd] ) - { - ++nAltEnd; - ++nTxtStart; - ++nTxtEnd; - } - - SAL_INFO_IF( ( nAltEnd - nAltStart ) != 1, "vcl", "Alternate: Wrong assumption!" ); - - sal_Unicode cAlternateReplChar = 0; - if ( nTxtEnd > nTxtStart ) - cAlternateReplChar = aAlt[ nAltStart ]; - - nBreakPos = nWordStart + nTxtStart; - if ( cAlternateReplChar ) - nBreakPos++; - return nBreakPos; -} - -sal_Int32 OutputDevice::ImplBreakLinesSimple( const tools::Long nWidth, const OUString& rStr, - const vcl::ITextLayout& _rLayout, const sal_Int32 nPos, sal_Int32 nBreakPos, tools::Long& nLineWidth ) -{ - sal_Int32 nSpacePos = rStr.getLength(); - tools::Long nW = 0; - do - { - nSpacePos = rStr.lastIndexOf( ' ', nSpacePos ); - if( nSpacePos != -1 ) - { - if( nSpacePos > nPos ) - nSpacePos--; - nW = _rLayout.GetTextWidth( rStr, nPos, nSpacePos-nPos ); - } - } while( nW > nWidth ); - - if( nSpacePos != -1 ) - { - nBreakPos = nSpacePos; - nLineWidth = _rLayout.GetTextWidth( rStr, nPos, nBreakPos-nPos ); - if( nBreakPos < rStr.getLength()-1 ) - nBreakPos++; - } - return nBreakPos; -} - - void OutputDevice::SetTextColor( const Color& rColor ) { @@ -1538,7 +1288,7 @@ sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, tools::Long nTextWid void OutputDevice::ImplDrawText( OutputDevice& rTargetDevice, const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle, std::vector< tools::Rectangle >* pVector, OUString* pDisplayText, - vcl::ITextLayout& _rLayout ) + vcl::TextLayoutCommon& _rLayout ) { Color aOldTextColor; @@ -1617,7 +1367,7 @@ void OutputDevice::ImplDrawText( OutputDevice& rTargetDevice, const tools::Recta if ( nTextHeight ) { - tools::Long nMaxTextWidth = ImplGetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle, _rLayout ); + tools::Long nMaxTextWidth = _rLayout.GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle); sal_Int32 nLines = static_cast<sal_Int32>(nHeight/nTextHeight); OUString aLastLine; nFormatLines = aMultiLineInfo.Count(); @@ -1641,7 +1391,7 @@ void OutputDevice::ImplDrawText( OutputDevice& rTargetDevice, const tools::Recta aLastLineBuffer[ i ] = ' '; } aLastLine = aLastLineBuffer.makeStringAndClear(); - aLastLine = ImplGetEllipsisString( aLastLine, nWidth, nStyle, _rLayout ); + aLastLine = _rLayout.GetEllipsisString(aLastLine, nWidth, nStyle); nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom); nStyle |= DrawTextFlags::Top; } @@ -1728,7 +1478,7 @@ void OutputDevice::ImplDrawText( OutputDevice& rTargetDevice, const tools::Recta { if ( nStyle & TEXT_DRAW_ELLIPSIS ) { - aStr = ImplGetEllipsisString( aStr, nWidth, nStyle, _rLayout ); + aStr = _rLayout.GetEllipsisString(aStr, nWidth, nStyle); nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right); nStyle |= DrawTextFlags::Left; nTextWidth = _rLayout.GetTextWidth( aStr, 0, aStr.getLength() ); @@ -1834,7 +1584,7 @@ void OutputDevice::AddTextRectActions( const tools::Rectangle& rRect, void OutputDevice::DrawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle, std::vector< tools::Rectangle >* pVector, OUString* pDisplayText, - vcl::ITextLayout* _pTextLayout ) + vcl::TextLayoutCommon* _pTextLayout ) { assert(!is_double_buffered_window()); @@ -1881,7 +1631,7 @@ void OutputDevice::DrawText( const tools::Rectangle& rRect, const OUString& rOri tools::Rectangle OutputDevice::GetTextRect( const tools::Rectangle& rRect, const OUString& rStr, DrawTextFlags nStyle, TextRectInfo* pInfo, - const vcl::ITextLayout* _pTextLayout ) const + const vcl::TextLayoutCommon* _pTextLayout ) const { tools::Rectangle aRect = rRect; @@ -1902,7 +1652,12 @@ tools::Rectangle OutputDevice::GetTextRect( const tools::Rectangle& rRect, nMaxWidth = 0; vcl::DefaultTextLayout aDefaultLayout( *const_cast< OutputDevice* >( this ) ); - ImplGetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle, _pTextLayout ? *_pTextLayout : aDefaultLayout ); + + if (_pTextLayout) + const_cast<vcl::TextLayoutCommon*>(_pTextLayout)->GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle); + else + aDefaultLayout.GetTextLines(rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle); + nFormatLines = aMultiLineInfo.Count(); if ( !nTextHeight ) nTextHeight = 1; @@ -2002,150 +1757,6 @@ tools::Rectangle OutputDevice::GetTextRect( const tools::Rectangle& rRect, return aRect; } -static bool ImplIsCharIn( sal_Unicode c, const char* pStr ) -{ - while ( *pStr ) - { - if ( *pStr == c ) - return true; - pStr++; - } - - return false; -} - -OUString OutputDevice::GetEllipsisString( const OUString& rOrigStr, tools::Long nMaxWidth, - DrawTextFlags nStyle ) const -{ - vcl::DefaultTextLayout aTextLayout( *const_cast< OutputDevice* >( this ) ); - return ImplGetEllipsisString( rOrigStr, nMaxWidth, nStyle, aTextLayout ); -} - -OUString OutputDevice::ImplGetEllipsisString( const OUString& rOrigStr, tools::Long nMaxWidth, - DrawTextFlags nStyle, const vcl::ITextLayout& _rLayout ) -{ - OUString aStr = rOrigStr; - sal_Int32 nIndex = _rLayout.GetTextBreak( aStr, nMaxWidth, 0, aStr.getLength() ); - - if ( nIndex != -1 ) - { - if( (nStyle & DrawTextFlags::CenterEllipsis) == DrawTextFlags::CenterEllipsis ) - { - OUStringBuffer aTmpStr( aStr ); - // speed it up by removing all but 1.33x as many as the break pos. - sal_Int32 nEraseChars = std::max<sal_Int32>(4, aStr.getLength() - (nIndex*4)/3); - while( nEraseChars < aStr.getLength() && _rLayout.GetTextWidth( aTmpStr.toString(), 0, aTmpStr.getLength() ) > nMaxWidth ) - { - aTmpStr = aStr; - sal_Int32 i = (aTmpStr.getLength() - nEraseChars)/2; - aTmpStr.remove(i, nEraseChars++); - aTmpStr.insert(i, "..."); - } - aStr = aTmpStr.makeStringAndClear(); - } - else if ( nStyle & DrawTextFlags::EndEllipsis ) - { - aStr = aStr.copy(0, nIndex); - if ( nIndex > 1 ) - { - aStr += "..."; - while ( !aStr.isEmpty() && (_rLayout.GetTextWidth( aStr, 0, aStr.getLength() ) > nMaxWidth) ) - { - if ( (nIndex > 1) || (nIndex == aStr.getLength()) ) - nIndex--; - aStr = aStr.replaceAt( nIndex, 1, u""); - } - } - - if ( aStr.isEmpty() && (nStyle & DrawTextFlags::Clip) ) - aStr += OUStringChar(rOrigStr[ 0 ]); - } - else if ( nStyle & DrawTextFlags::PathEllipsis ) - { - OUString aPath( rOrigStr ); - OUString aAbbreviatedPath; - osl_abbreviateSystemPath( aPath.pData, &aAbbreviatedPath.pData, nIndex, nullptr ); - aStr = aAbbreviatedPath; - } - else if ( nStyle & DrawTextFlags::NewsEllipsis ) - { - static char const pSepChars[] = "."; - // Determine last section - sal_Int32 nLastContent = aStr.getLength(); - while ( nLastContent ) - { - nLastContent--; - if ( ImplIsCharIn( aStr[ nLastContent ], pSepChars ) ) - break; - } - while ( nLastContent && - ImplIsCharIn( aStr[ nLastContent-1 ], pSepChars ) ) - nLastContent--; - - OUString aLastStr = aStr.copy(nLastContent); - OUString aTempLastStr1 = "..." + aLastStr; - if ( _rLayout.GetTextWidth( aTempLastStr1, 0, aTempLastStr1.getLength() ) > nMaxWidth ) - aStr = OutputDevice::ImplGetEllipsisString( aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout ); - else - { - sal_Int32 nFirstContent = 0; - while ( nFirstContent < nLastContent ) - { - nFirstContent++; - if ( ImplIsCharIn( aStr[ nFirstContent ], pSepChars ) ) - break; - } - while ( (nFirstContent < nLastContent) && - ImplIsCharIn( aStr[ nFirstContent ], pSepChars ) ) - nFirstContent++; - // MEM continue here - if ( nFirstContent >= nLastContent ) - aStr = OutputDevice::ImplGetEllipsisString( aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout ); - else - { - if ( nFirstContent > 4 ) - nFirstContent = 4; - OUString aFirstStr = OUString::Concat(aStr.subView( 0, nFirstContent )) + "..."; - OUString aTempStr = aFirstStr + aLastStr; - if ( _rLayout.GetTextWidth( aTempStr, 0, aTempStr.getLength() ) > nMaxWidth ) - aStr = OutputDevice::ImplGetEllipsisString( aStr, nMaxWidth, nStyle | DrawTextFlags::EndEllipsis, _rLayout ); - else - { - do - { - aStr = aTempStr; - if( nLastContent > aStr.getLength() ) - nLastContent = aStr.getLength(); - while ( nFirstContent < nLastContent ) - { - nLastContent--; - if ( ImplIsCharIn( aStr[ nLastContent ], pSepChars ) ) - break; - - } - while ( (nFirstContent < nLastContent) && - ImplIsCharIn( aStr[ nLastContent-1 ], pSepChars ) ) - nLastContent--; - - if ( nFirstContent < nLastContent ) - { - std::u16string_view aTempLastStr = aStr.subView( nLastContent ); - aTempStr = aFirstStr + aTempLastStr; - - if ( _rLayout.GetTextWidth( aTempStr, 0, aTempStr.getLength() ) > nMaxWidth ) - break; - } - } - while ( nFirstContent < nLastContent ); - } - } - } - } - } - - return aStr; -} - void OutputDevice::DrawCtrlText( const Point& rPos, const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen, DrawTextFlags nStyle, std::vector< tools::Rectangle >* pVector, OUString* pDisplayText, diff --git a/vcl/source/window/window3.cxx b/vcl/source/window/window3.cxx index 5b8dd5cff4ec..30c41f5e20f0 100644 --- a/vcl/source/window/window3.cxx +++ b/vcl/source/window/window3.cxx @@ -200,7 +200,7 @@ Size Window::LogicToLogic(const Size& rSzSource, const MapMode* pMapModeSource, tools::Rectangle Window::GetTextRect(const tools::Rectangle& rRect, const OUString& rStr, DrawTextFlags nStyle, TextRectInfo* pInfo, - const vcl::ITextLayout* _pTextLayout) const + const vcl::TextLayoutCommon* _pTextLayout) const { return GetOutDev()->GetTextRect(rRect, rStr, nStyle, pInfo, _pTextLayout); } |