/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define TEXT_DRAW_ELLIPSIS (DrawTextFlags::EndEllipsis | DrawTextFlags::PathEllipsis | DrawTextFlags::NewsEllipsis) void OutputDevice::SetLayoutMode( vcl::text::ComplexTextLayoutFlags nTextLayoutMode ) { if( mpMetaFile ) mpMetaFile->AddAction( new MetaLayoutModeAction( nTextLayoutMode ) ); mnTextLayoutMode = nTextLayoutMode; if( mpAlphaVDev ) mpAlphaVDev->SetLayoutMode( nTextLayoutMode ); } void OutputDevice::SetDigitLanguage( LanguageType eTextLanguage ) { if( mpMetaFile ) mpMetaFile->AddAction( new MetaTextLanguageAction( eTextLanguage ) ); meTextLanguage = eTextLanguage; if( mpAlphaVDev ) 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(); if ( mbInitTextColor ) { mpGraphics->SetTextColor( GetTextColor() ); mbInitTextColor = false; } } void OutputDevice::ImplDrawTextRect( tools::Long nBaseX, tools::Long nBaseY, tools::Long nDistX, tools::Long nDistY, tools::Long nWidth, tools::Long nHeight ) { tools::Long nX = nDistX; tools::Long nY = nDistY; Degree10 nOrientation = mpFontInstance->mnOrientation; if ( nOrientation ) { // Rotate rect without rounding problems for 90 degree rotations if ( !(nOrientation % 900_deg10) ) { if ( nOrientation == 900_deg10 ) { tools::Long nTemp = nX; nX = nY; nY = -nTemp; nTemp = nWidth; nWidth = nHeight; nHeight = nTemp; nY -= nHeight; } else if ( nOrientation == 1800_deg10 ) { nX = -nX; nY = -nY; nX -= nWidth; nY -= nHeight; } else /* ( nOrientation == 2700 ) */ { tools::Long nTemp = nX; nX = -nY; nY = nTemp; nTemp = nWidth; nWidth = nHeight; nHeight = nTemp; nX -= nWidth; } } else { nX += nBaseX; nY += nBaseY; // inflate because polygons are drawn smaller tools::Rectangle aRect( Point( nX, nY ), Size( nWidth+1, nHeight+1 ) ); tools::Polygon aPoly( aRect ); aPoly.Rotate( Point( nBaseX, nBaseY ), mpFontInstance->mnOrientation ); ImplDrawPolygon( aPoly ); return; } } nX += nBaseX; nY += nBaseY; mpGraphics->DrawRect( nX, nY, nWidth, nHeight, *this ); // original code } void OutputDevice::ImplDrawTextBackground( const SalLayout& rSalLayout ) { const tools::Long nWidth = rSalLayout.GetTextWidth(); const DevicePoint aBase = rSalLayout.DrawBase(); const tools::Long nX = aBase.getX(); const tools::Long nY = aBase.getY(); if ( mbLineColor || mbInitLineColor ) { mpGraphics->SetLineColor(); mbInitLineColor = true; } mpGraphics->SetFillColor( GetTextFillColor() ); mbInitFillColor = true; ImplDrawTextRect( nX, nY, 0, -(mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent), nWidth, mpFontInstance->mnLineHeight+mnEmphasisAscent+mnEmphasisDescent ); } tools::Rectangle OutputDevice::ImplGetTextBoundRect( const SalLayout& rSalLayout ) const { DevicePoint aPoint = rSalLayout.GetDrawPosition(); tools::Long nX = aPoint.getX(); tools::Long nY = aPoint.getY(); tools::Long nWidth = rSalLayout.GetTextWidth(); tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent; nY -= mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent; if ( mpFontInstance->mnOrientation ) { tools::Long nBaseX = nX, nBaseY = nY; if ( !(mpFontInstance->mnOrientation % 900_deg10) ) { tools::Long nX2 = nX+nWidth; tools::Long nY2 = nY+nHeight; Point aBasePt( nBaseX, nBaseY ); aBasePt.RotateAround( nX, nY, mpFontInstance->mnOrientation ); aBasePt.RotateAround( nX2, nY2, mpFontInstance->mnOrientation ); nWidth = nX2-nX; nHeight = nY2-nY; } else { // inflate by +1+1 because polygons are drawn smaller tools::Rectangle aRect( Point( nX, nY ), Size( nWidth+1, nHeight+1 ) ); tools::Polygon aPoly( aRect ); aPoly.Rotate( Point( nBaseX, nBaseY ), mpFontInstance->mnOrientation ); return aPoly.GetBoundRect(); } } return tools::Rectangle( Point( nX, nY ), Size( nWidth, nHeight ) ); } bool OutputDevice::ImplDrawRotateText( SalLayout& rSalLayout ) { tools::Long nX = rSalLayout.DrawBase().getX(); tools::Long nY = rSalLayout.DrawBase().getY(); tools::Rectangle aBoundRect; rSalLayout.DrawBase() = DevicePoint( 0, 0 ); rSalLayout.DrawOffset() = Point( 0, 0 ); if (!rSalLayout.GetBoundRect(aBoundRect)) { // guess vertical text extents if GetBoundRect failed tools::Long nRight = rSalLayout.GetTextWidth(); tools::Long nTop = mpFontInstance->mxFontMetric->GetAscent() + mnEmphasisAscent; tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent; aBoundRect = tools::Rectangle( 0, -nTop, nRight, nHeight - nTop ); } // cache virtual device for rotation if (!mpOutDevData->mpRotateDev) mpOutDevData->mpRotateDev = VclPtr::Create(*this); VirtualDevice* pVDev = mpOutDevData->mpRotateDev; // size it accordingly if( !pVDev->SetOutputSizePixel( aBoundRect.GetSize() ) ) return false; const vcl::font::FontSelectPattern& rPattern = mpFontInstance->GetFontSelectPattern(); vcl::Font aFont( GetFont() ); aFont.SetOrientation( 0_deg10 ); aFont.SetFontSize( Size( rPattern.mnWidth, rPattern.mnHeight ) ); pVDev->SetFont( aFont ); pVDev->SetTextColor( COL_BLACK ); pVDev->SetTextFillColor(); if (!pVDev->InitFont()) return false; pVDev->ImplInitTextColor(); // draw text into upper left corner rSalLayout.DrawBase().adjustX(-aBoundRect.Left()); rSalLayout.DrawBase().adjustY(-aBoundRect.Top()); rSalLayout.DrawText( *pVDev->mpGraphics ); Bitmap aBmp = pVDev->GetBitmap( Point(), aBoundRect.GetSize() ); if ( aBmp.IsEmpty() || !aBmp.Rotate( mpFontInstance->mnOwnOrientation, COL_WHITE ) ) return false; // calculate rotation offset tools::Polygon aPoly( aBoundRect ); aPoly.Rotate( Point(), mpFontInstance->mnOwnOrientation ); Point aPoint = aPoly.GetBoundRect().TopLeft(); aPoint += Point( nX, nY ); // mask output with text colored bitmap GDIMetaFile* pOldMetaFile = mpMetaFile; tools::Long nOldOffX = mnOutOffX; tools::Long nOldOffY = mnOutOffY; bool bOldMap = mbMap; mnOutOffX = 0; mnOutOffY = 0; mpMetaFile = nullptr; EnableMapMode( false ); DrawMask( aPoint, aBmp, GetTextColor() ); EnableMapMode( bOldMap ); mnOutOffX = nOldOffX; mnOutOffY = nOldOffY; mpMetaFile = pOldMetaFile; return true; } void OutputDevice::ImplDrawTextDirect( SalLayout& rSalLayout, bool bTextLines) { if( mpFontInstance->mnOwnOrientation ) if( ImplDrawRotateText( rSalLayout ) ) return; auto nOldX = rSalLayout.DrawBase().getX(); if( HasMirroredGraphics() ) { tools::Long w = IsVirtual() ? mnOutWidth : mpGraphics->GetGraphicsWidth(); auto x = rSalLayout.DrawBase().getX(); rSalLayout.DrawBase().setX( w - 1 - x ); if( !IsRTLEnabled() ) { OutputDevice *pOutDevRef = this; // mirror this window back tools::Long devX = w-pOutDevRef->mnOutWidth-pOutDevRef->mnOutOffX; // re-mirrored mnOutOffX rSalLayout.DrawBase().setX( devX + ( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().getX() - devX) ) ) ; } } else if( IsRTLEnabled() ) { OutputDevice *pOutDevRef = this; // mirror this window back tools::Long devX = pOutDevRef->mnOutOffX; // re-mirrored mnOutOffX rSalLayout.DrawBase().setX( pOutDevRef->mnOutWidth - 1 - (rSalLayout.DrawBase().getX() - devX) + devX ); } rSalLayout.DrawText( *mpGraphics ); rSalLayout.DrawBase().setX( nOldX ); if( bTextLines ) ImplDrawTextLines( rSalLayout, maFont.GetStrikeout(), maFont.GetUnderline(), maFont.GetOverline(), maFont.IsWordLineMode(), maFont.IsUnderlineAbove() ); // emphasis marks if( maFont.GetEmphasisMark() & FontEmphasisMark::Style ) ImplDrawEmphasisMarks( rSalLayout ); } void OutputDevice::ImplDrawSpecialText( SalLayout& rSalLayout ) { Color aOldColor = GetTextColor(); Color aOldTextLineColor = GetTextLineColor(); Color aOldOverlineColor = GetOverlineColor(); FontRelief eRelief = maFont.GetRelief(); DevicePoint aOrigPos = rSalLayout.DrawBase(); if ( eRelief != FontRelief::NONE ) { Color aReliefColor( COL_LIGHTGRAY ); Color aTextColor( aOldColor ); Color aTextLineColor( aOldTextLineColor ); Color aOverlineColor( aOldOverlineColor ); // we don't have an automatic color, so black is always drawn on white if ( aTextColor == COL_BLACK ) aTextColor = COL_WHITE; if ( aTextLineColor == COL_BLACK ) aTextLineColor = COL_WHITE; if ( aOverlineColor == COL_BLACK ) aOverlineColor = COL_WHITE; // relief-color is black for white text, in all other cases // we set this to LightGray // coverity[copy_paste_error: FALSE] - this is intentional if ( aTextColor == COL_WHITE ) aReliefColor = COL_BLACK; SetTextLineColor( aReliefColor ); SetOverlineColor( aReliefColor ); SetTextColor( aReliefColor ); ImplInitTextColor(); // calculate offset - for high resolution printers the offset // should be greater so that the effect is visible tools::Long nOff = 1; nOff += mnDPIX/300; if ( eRelief == FontRelief::Engraved ) nOff = -nOff; rSalLayout.DrawOffset() += Point( nOff, nOff); ImplDrawTextDirect( rSalLayout, mbTextLines ); rSalLayout.DrawOffset() -= Point( nOff, nOff); SetTextLineColor( aTextLineColor ); SetOverlineColor( aOverlineColor ); SetTextColor( aTextColor ); ImplInitTextColor(); ImplDrawTextDirect( rSalLayout, mbTextLines ); SetTextLineColor( aOldTextLineColor ); SetOverlineColor( aOldOverlineColor ); if ( aTextColor != aOldColor ) { SetTextColor( aOldColor ); ImplInitTextColor(); } } else { if ( maFont.IsShadow() ) { tools::Long nOff = 1 + ((mpFontInstance->mnLineHeight-24)/24); if ( maFont.IsOutline() ) nOff++; SetTextLineColor(); SetOverlineColor(); if ( (GetTextColor() == COL_BLACK) || (GetTextColor().GetLuminance() < 8) ) SetTextColor( COL_LIGHTGRAY ); else SetTextColor( COL_BLACK ); ImplInitTextColor(); rSalLayout.DrawBase() += DevicePoint( nOff, nOff ); ImplDrawTextDirect( rSalLayout, mbTextLines ); rSalLayout.DrawBase() -= DevicePoint( nOff, nOff ); SetTextColor( aOldColor ); SetTextLineColor( aOldTextLineColor ); SetOverlineColor( aOldOverlineColor ); ImplInitTextColor(); if ( !maFont.IsOutline() ) ImplDrawTextDirect( rSalLayout, mbTextLines ); } if ( maFont.IsOutline() ) { rSalLayout.DrawBase() = aOrigPos + DevicePoint(-1,-1); ImplDrawTextDirect( rSalLayout, mbTextLines ); rSalLayout.DrawBase() = aOrigPos + DevicePoint(+1,+1); ImplDrawTextDirect( rSalLayout, mbTextLines ); rSalLayout.DrawBase() = aOrigPos + DevicePoint(-1,+0); ImplDrawTextDirect( rSalLayout, mbTextLines ); rSalLayout.DrawBase() = aOrigPos + DevicePoint(-1,+1); ImplDrawTextDirect( rSalLayout, mbTextLines ); rSalLayout.DrawBase() = aOrigPos + DevicePoint(+0,+1); ImplDrawTextDirect( rSalLayout, mbTextLines ); rSalLayout.DrawBase() = aOrigPos + DevicePoint(+0,-1); ImplDrawTextDirect( rSalLayout, mbTextLines ); rSalLayout.DrawBase() = aOrigPos + DevicePoint(+1,-1); ImplDrawTextDirect( rSalLayout, mbTextLines ); rSalLayout.DrawBase() = aOrigPos + DevicePoint(+1,+0); ImplDrawTextDirect( rSalLayout, mbTextLines ); rSalLayout.DrawBase() = aOrigPos; SetTextColor( COL_WHITE ); SetTextLineColor( COL_WHITE ); SetOverlineColor( COL_WHITE ); ImplInitTextColor(); ImplDrawTextDirect( rSalLayout, mbTextLines ); SetTextColor( aOldColor ); SetTextLineColor( aOldTextLineColor ); SetOverlineColor( aOldOverlineColor ); ImplInitTextColor(); } } } void OutputDevice::ImplDrawText( SalLayout& rSalLayout ) { if( mbInitClipRegion ) InitClipRegion(); if( mbOutputClipped ) return; if( mbInitTextColor ) ImplInitTextColor(); rSalLayout.DrawBase() += DevicePoint(mnTextOffX, mnTextOffY); if( IsTextFillColor() ) ImplDrawTextBackground( rSalLayout ); if( mbTextSpecial ) ImplDrawSpecialText( rSalLayout ); else 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 xContext(comphelper::getProcessComponentContext()); css::uno::Reference xLinguMgr = css::linguistic2::LinguServiceManager::create(xContext); xHyph = xLinguMgr->getHyphenator(); } css::uno::Reference 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& 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 (), 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_WARN_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 ) { Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings())); if ( mpMetaFile ) mpMetaFile->AddAction( new MetaTextColorAction( aColor ) ); if ( maTextColor != aColor ) { maTextColor = aColor; mbInitTextColor = true; } if( mpAlphaVDev ) mpAlphaVDev->SetTextColor( COL_BLACK ); } void OutputDevice::SetTextFillColor() { if ( mpMetaFile ) mpMetaFile->AddAction( new MetaTextFillColorAction( Color(), false ) ); if ( maFont.GetColor() != COL_TRANSPARENT ) { maFont.SetFillColor( COL_TRANSPARENT ); } if ( !maFont.IsTransparent() ) maFont.SetTransparent( true ); if( mpAlphaVDev ) mpAlphaVDev->SetTextFillColor(); } void OutputDevice::SetTextFillColor( const Color& rColor ) { Color aColor(vcl::drawmode::GetFillColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings())); if ( mpMetaFile ) mpMetaFile->AddAction( new MetaTextFillColorAction( aColor, true ) ); if ( maFont.GetFillColor() != aColor ) maFont.SetFillColor( aColor ); if ( maFont.IsTransparent() != rColor.IsTransparent() ) maFont.SetTransparent( rColor.IsTransparent() ); if( mpAlphaVDev ) mpAlphaVDev->SetTextFillColor( COL_BLACK ); } Color OutputDevice::GetTextFillColor() const { if ( maFont.IsTransparent() ) return COL_TRANSPARENT; else return maFont.GetFillColor(); } void OutputDevice::SetTextAlign( TextAlign eAlign ) { if ( mpMetaFile ) mpMetaFile->AddAction( new MetaTextAlignAction( eAlign ) ); if ( maFont.GetAlignment() != eAlign ) { maFont.SetAlignment( eAlign ); mbNewFont = true; } if( mpAlphaVDev ) mpAlphaVDev->SetTextAlign( eAlign ); } vcl::Region OutputDevice::GetOutputBoundsClipRegion() const { return GetClipRegion(); } const SalLayoutFlags eDefaultLayout = SalLayoutFlags::NONE; void OutputDevice::DrawText( const Point& rStartPt, const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen, std::vector< tools::Rectangle >* pVector, OUString* pDisplayText, const SalLayoutGlyphs* pLayoutCache ) { assert(!is_double_buffered_window()); if( (nLen < 0) || (nIndex + nLen >= rStr.getLength())) { nLen = rStr.getLength() - nIndex; } if (mpOutDevData->mpRecordLayout) { pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects; pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText; } #if OSL_DEBUG_LEVEL > 2 SAL_INFO("vcl.gdi", "OutputDevice::DrawText(\"" << rStr << "\")"); #endif if ( mpMetaFile ) mpMetaFile->AddAction( new MetaTextAction( rStartPt, rStr, nIndex, nLen ) ); if( pVector ) { vcl::Region aClip(GetOutputBoundsClipRegion()); if (mpOutDevData->mpRecordLayout) { mpOutDevData->mpRecordLayout->m_aLineIndices.push_back( mpOutDevData->mpRecordLayout->m_aDisplayText.getLength() ); aClip.Intersect( mpOutDevData->maRecordRect ); } if( ! aClip.IsNull() ) { std::vector< tools::Rectangle > aTmp; GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, aTmp ); bool bInserted = false; for( std::vector< tools::Rectangle >::const_iterator it = aTmp.begin(); it != aTmp.end(); ++it, nIndex++ ) { bool bAppend = false; if( aClip.Overlaps( *it ) ) bAppend = true; else if( rStr[ nIndex ] == ' ' && bInserted ) { std::vector< tools::Rectangle >::const_iterator next = it; ++next; if( next != aTmp.end() && aClip.Overlaps( *next ) ) bAppend = true; } if( bAppend ) { pVector->push_back( *it ); if( pDisplayText ) *pDisplayText += OUStringChar(rStr[ nIndex ]); bInserted = true; } } } else { GetGlyphBoundRects( rStartPt, rStr, nIndex, nLen, *pVector ); if( pDisplayText ) *pDisplayText += rStr.subView( nIndex, nLen ); } } if ( !IsDeviceOutputNecessary() || pVector ) return; if(mpFontInstance) // do not use cache with modified string if(mpFontInstance->mpConversion) pLayoutCache = nullptr; std::unique_ptr pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, {}, {}, eDefaultLayout, nullptr, pLayoutCache); if(pSalLayout) { ImplDrawText( *pSalLayout ); } if( mpAlphaVDev ) mpAlphaVDev->DrawText( rStartPt, rStr, nIndex, nLen, pVector, pDisplayText ); } tools::Long OutputDevice::GetTextWidth( const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen, vcl::text::TextLayoutCache const*const pLayoutCache, SalLayoutGlyphs const*const pSalLayoutCache) const { tools::Long nWidth = GetTextArray( rStr, nullptr, nIndex, nLen, false, pLayoutCache, pSalLayoutCache ); return nWidth; } tools::Long OutputDevice::GetTextHeight() const { if (!InitFont()) return 0; tools::Long nHeight = mpFontInstance->mnLineHeight + mnEmphasisAscent + mnEmphasisDescent; if ( mbMap ) nHeight = ImplDevicePixelToLogicHeight( nHeight ); return nHeight; } float OutputDevice::approximate_char_width() const { //note pango uses "The quick brown fox jumps over the lazy dog." for english //and has a bunch of per-language strings which corresponds somewhat with //makeRepresentativeText in include/svtools/sampletext.hxx return GetTextWidth("aemnnxEM") / 8.0; } float OutputDevice::approximate_digit_width() const { return GetTextWidth("0123456789") / 10.0; } void OutputDevice::DrawTextArray( const Point& rStartPt, const OUString& rStr, KernArraySpan pDXAry, o3tl::span pKashidaAry, sal_Int32 nIndex, sal_Int32 nLen, SalLayoutFlags flags, const SalLayoutGlyphs* pSalLayoutCache ) { assert(!is_double_buffered_window()); if( nLen < 0 || nIndex + nLen >= rStr.getLength() ) { nLen = rStr.getLength() - nIndex; } if ( mpMetaFile ) mpMetaFile->AddAction( new MetaTextArrayAction( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen ) ); if ( !IsDeviceOutputNecessary() ) return; if( !mpGraphics && !AcquireGraphics() ) return; assert(mpGraphics); if( mbInitClipRegion ) InitClipRegion(); if( mbOutputClipped ) return; std::unique_ptr pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, 0, pDXAry, pKashidaAry, flags, nullptr, pSalLayoutCache); if( pSalLayout ) { ImplDrawText( *pSalLayout ); } if( mpAlphaVDev ) mpAlphaVDev->DrawTextArray( rStartPt, rStr, pDXAry, pKashidaAry, nIndex, nLen, flags ); } tools::Long OutputDevice::GetTextArray( const OUString& rStr, KernArray* pKernArray, sal_Int32 nIndex, sal_Int32 nLen, bool bCaret, vcl::text::TextLayoutCache const*const pLayoutCache, SalLayoutGlyphs const*const pSalLayoutCache) const { if( nIndex >= rStr.getLength() ) return 0; // TODO: this looks like a buggy caller? if( nLen < 0 || nIndex + nLen >= rStr.getLength() ) { nLen = rStr.getLength() - nIndex; } std::vector* pDXAry = pKernArray ? &pKernArray->get_subunit_array() : nullptr; // do layout std::unique_ptr pSalLayout = ImplLayout(rStr, nIndex, nLen, Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pSalLayoutCache); if( !pSalLayout ) { // The caller expects this to init the elements of pDXAry. // Adapting all the callers to check that GetTextArray succeeded seems // too much work. // Init here to 0 only in the (rare) error case, so that any missing // element init in the happy case will still be found by tools, // and hope that is sufficient. if (pDXAry) { pDXAry->resize(nLen); std::fill(pDXAry->begin(), pDXAry->end(), 0); } return 0; } #if VCL_FLOAT_DEVICE_PIXEL std::unique_ptr> xDXPixelArray; if(pDXAry) { xDXPixelArray.reset(new std::vector(nLen)); } std::vector* pDXPixelArray = xDXPixelArray.get(); DeviceCoordinate nWidth = pSalLayout->FillDXArray(pDXPixelArray, bCaret ? rStr : OUString()); // convert virtual char widths to virtual absolute positions if( pDXPixelArray ) { for( int i = 1; i < nLen; ++i ) { (*pDXPixelArray)[i] += (*pDXPixelArray)[i - 1]; } } if( mbMap ) { if( pDXPixelArray ) { for( int i = 0; i < nLen; ++i ) { (*pDXPixelArray)[i] = ImplDevicePixelToLogicWidth((*pDXPixelArray)[i]); } } nWidth = ImplDevicePixelToLogicWidth( nWidth ); } if(pDXAry) { pDXAry->resize(nLen); for( int i = 0; i < nLen; ++i ) { (*pDXAry)[i] = basegfx::fround((*pDXPixelArray)[i]); } } return basegfx::fround(nWidth); #else /* ! VCL_FLOAT_DEVICE_PIXEL */ tools::Long nWidth = pSalLayout->FillDXArray( pDXAry, bCaret ? rStr : OUString() ); // convert virtual char widths to virtual absolute positions if( pDXAry ) for( int i = 1; i < nLen; ++i ) (*pDXAry)[ i ] += (*pDXAry)[ i-1 ]; // convert from font units to logical units if (pDXAry) { int nSubPixelFactor = pKernArray->get_factor(); if (mbMap) { for (int i = 0; i < nLen; ++i) (*pDXAry)[i] = ImplDevicePixelToLogicWidth( (*pDXAry)[i] * nSubPixelFactor ); } else if (nSubPixelFactor) { for (int i = 0; i < nLen; ++i) (*pDXAry)[i] *= nSubPixelFactor; } } if (mbMap) nWidth = ImplDevicePixelToLogicWidth( nWidth ); return nWidth; #endif /* VCL_FLOAT_DEVICE_PIXEL */ } void OutputDevice::GetCaretPositions( const OUString& rStr, sal_Int32* pCaretXArray, sal_Int32 nIndex, sal_Int32 nLen, const SalLayoutGlyphs* pGlyphs ) const { if( nIndex >= rStr.getLength() ) return; if( nIndex+nLen >= rStr.getLength() ) nLen = rStr.getLength() - nIndex; // layout complex text std::unique_ptr pSalLayout = ImplLayout(rStr, nIndex, nLen, Point(0, 0), 0, {}, {}, eDefaultLayout, nullptr, pGlyphs); if( !pSalLayout ) { std::fill(pCaretXArray, pCaretXArray + nLen * 2, -1); return; } pSalLayout->GetCaretPositions( 2*nLen, pCaretXArray ); tools::Long nWidth = pSalLayout->GetTextWidth(); // fixup unknown caret positions int i; for( i = 0; i < 2 * nLen; ++i ) if( pCaretXArray[ i ] >= 0 ) break; tools::Long nXPos = (i < 2 * nLen) ? pCaretXArray[i] : -1; for( i = 0; i < 2 * nLen; ++i ) { if( pCaretXArray[ i ] >= 0 ) nXPos = pCaretXArray[ i ]; else pCaretXArray[ i ] = nXPos; } // handle window mirroring if( IsRTLEnabled() ) { for( i = 0; i < 2 * nLen; ++i ) pCaretXArray[i] = nWidth - pCaretXArray[i] - 1; } // convert from font units to logical units if( mbMap ) { for( i = 0; i < 2*nLen; ++i ) pCaretXArray[i] = ImplDevicePixelToLogicWidth( pCaretXArray[i] ); } } void OutputDevice::DrawStretchText( const Point& rStartPt, sal_Int32 nWidth, const OUString& rStr, sal_Int32 nIndex, sal_Int32 nLen) { assert(!is_double_buffered_window()); if( (nLen < 0) || (nIndex + nLen >= rStr.getLength())) { nLen = rStr.getLength() - nIndex; } if ( mpMetaFile ) mpMetaFile->AddAction( new MetaStretchTextAction( rStartPt, nWidth, rStr, nIndex, nLen ) ); if ( !IsDeviceOutputNecessary() ) return; std::unique_ptr pSalLayout = ImplLayout(rStr, nIndex, nLen, rStartPt, nWidth); if( pSalLayout ) { ImplDrawText( *pSalLayout ); } if( mpAlphaVDev ) mpAlphaVDev->DrawStretchText( rStartPt, nWidth, rStr, nIndex, nLen ); } vcl::text::ImplLayoutArgs OutputDevice::ImplPrepareLayoutArgs( OUString& rStr, const sal_Int32 nMinIndex, const sal_Int32 nLen, DeviceCoordinate nPixelWidth, SalLayoutFlags nLayoutFlags, vcl::text::TextLayoutCache const*const pLayoutCache) const { assert(nMinIndex >= 0); assert(nLen >= 0); // get string length for calculating extents sal_Int32 nEndIndex = rStr.getLength(); if( nMinIndex + nLen < nEndIndex ) nEndIndex = nMinIndex + nLen; // don't bother if there is nothing to do if( nEndIndex < nMinIndex ) nEndIndex = nMinIndex; nLayoutFlags |= GetBiDiLayoutFlags( rStr, nMinIndex, nEndIndex ); if( !maFont.IsKerning() ) nLayoutFlags |= SalLayoutFlags::DisableKerning; if( maFont.GetKerning() & FontKerning::Asian ) nLayoutFlags |= SalLayoutFlags::KerningAsian; if( maFont.IsVertical() ) nLayoutFlags |= SalLayoutFlags::Vertical; if( maFont.IsFixKerning() || ( mpFontInstance && mpFontInstance->GetFontSelectPattern().GetPitch() == PITCH_FIXED ) ) nLayoutFlags |= SalLayoutFlags::DisableLigatures; if( meTextLanguage ) //TODO: (mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::SubstituteDigits) { // disable character localization when no digits used const sal_Unicode* pBase = rStr.getStr(); const sal_Unicode* pStr = pBase + nMinIndex; const sal_Unicode* pEnd = pBase + nEndIndex; std::optional xTmpStr; for( ; pStr < pEnd; ++pStr ) { // TODO: are there non-digit localizations? if( (*pStr >= '0') && (*pStr <= '9') ) { // translate characters to local preference sal_UCS4 cChar = GetLocalizedChar( *pStr, meTextLanguage ); if( cChar != *pStr ) { if (!xTmpStr) xTmpStr = OUStringBuffer(rStr); // TODO: are the localized digit surrogates? (*xTmpStr)[pStr - pBase] = cChar; } } } if (xTmpStr) rStr = (*xTmpStr).makeStringAndClear(); } // right align for RTL text, DRAWPOS_REVERSED, RTL window style bool bRightAlign = bool(mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl); if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginLeft ) bRightAlign = false; else if ( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::TextOriginRight ) bRightAlign = true; // SSA: hack for western office, ie text get right aligned // for debugging purposes of mirrored UI bool bRTLWindow = IsRTLEnabled(); bRightAlign ^= bRTLWindow; if( bRightAlign ) nLayoutFlags |= SalLayoutFlags::RightAlign; // set layout options vcl::text::ImplLayoutArgs aLayoutArgs(rStr, nMinIndex, nEndIndex, nLayoutFlags, maFont.GetLanguageTag(), pLayoutCache); Degree10 nOrientation = mpFontInstance ? mpFontInstance->mnOrientation : 0_deg10; aLayoutArgs.SetOrientation( nOrientation ); aLayoutArgs.SetLayoutWidth( nPixelWidth ); return aLayoutArgs; } SalLayoutFlags OutputDevice::GetBiDiLayoutFlags( std::u16string_view rStr, const sal_Int32 nMinIndex, const sal_Int32 nEndIndex ) const { SalLayoutFlags nLayoutFlags = SalLayoutFlags::NONE; if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl ) nLayoutFlags |= SalLayoutFlags::BiDiRtl; if( mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiStrong ) nLayoutFlags |= SalLayoutFlags::BiDiStrong; else if( !(mnTextLayoutMode & vcl::text::ComplexTextLayoutFlags::BiDiRtl) ) { // Disable Bidi if no RTL hint and only known LTR codes used. bool bAllLtr = true; for (sal_Int32 i = nMinIndex; i < nEndIndex; i++) { // [0x0000, 0x052F] are Latin, Greek and Cyrillic. // [0x0370, 0x03FF] has a few holes as if Unicode 10.0.0, but // hopefully no RTL character will be encoded there. if (rStr[i] > 0x052F) { bAllLtr = false; break; } } if (bAllLtr) nLayoutFlags |= SalLayoutFlags::BiDiStrong; } return nLayoutFlags; } static OutputDevice::FontMappingUseData* fontMappingUseData = nullptr; static inline bool IsTrackingFontMappingUse() { return fontMappingUseData != nullptr; } static void TrackFontMappingUse( const vcl::Font& originalFont, const SalLayout* salLayout) { assert(fontMappingUseData); OUString originalName = originalFont.GetStyleName().isEmpty() ? originalFont.GetFamilyName() : originalFont.GetFamilyName() + "/" + originalFont.GetStyleName(); std::vector usedFontNames; SalLayoutGlyphs glyphs = salLayout->GetGlyphs(); // includes all font fallbacks int level = 0; while( const SalLayoutGlyphsImpl* impl = glyphs.Impl(level++)) { const vcl::font::PhysicalFontFace* face = impl->GetFont()->GetFontFace(); OUString name = face->GetStyleName().isEmpty() ? face->GetFamilyName() : face->GetFamilyName() + "/" + face->GetStyleName(); usedFontNames.push_back( name ); } for( OutputDevice::FontMappingUseItem& item : *fontMappingUseData ) { if( item.mOriginalFont == originalName && item.mUsedFonts == usedFontNames ) { ++item.mCount; return; } } fontMappingUseData->push_back( { originalName, usedFontNames, 1 } ); } void OutputDevice::StartTrackingFontMappingUse() { delete fontMappingUseData; fontMappingUseData = new FontMappingUseData; } OutputDevice::FontMappingUseData OutputDevice::FinishTrackingFontMappingUse() { if(!fontMappingUseData) return {}; FontMappingUseData ret = std::move( *fontMappingUseData ); delete fontMappingUseData; fontMappingUseData = nullptr; return ret; } std::unique_ptr OutputDevice::ImplLayout(const OUString& rOrigStr, sal_Int32 nMinIndex, sal_Int32 nLen, const Point& rLogicalPos, tools::Long nLogicalWidth, KernArraySpan pDXArray, o3tl::span pKashidaArray, SalLayoutFlags flags, vcl::text::TextLayoutCache const* pLayoutCache, const SalLayoutGlyphs* pGlyphs) const { if (pGlyphs && !pGlyphs->IsValid()) { SAL_WARN("vcl", "Trying to setup invalid cached glyphs - falling back to relayout!"); pGlyphs = nullptr; } #ifdef DBG_UTIL if (pGlyphs) { for( int level = 0;; ++level ) { SalLayoutGlyphsImpl* glyphsImpl = pGlyphs->Impl(level); if(glyphsImpl == nullptr) break; // It is allowed to reuse only glyphs created with SalLayoutFlags::GlyphItemsOnly. // If the glyphs have already been used, the AdjustLayout() call below might have // altered them (MultiSalLayout::ImplAdjustMultiLayout() drops glyphs that need // fallback from the base layout, but then GenericSalLayout::LayoutText() // would not know to call SetNeedFallback()). assert(glyphsImpl->GetFlags() & SalLayoutFlags::GlyphItemsOnly); } } #endif if (!InitFont()) return nullptr; // check string index and length if( -1 == nLen || nMinIndex + nLen > rOrigStr.getLength() ) { const sal_Int32 nNewLen = rOrigStr.getLength() - nMinIndex; if( nNewLen <= 0 ) return nullptr; nLen = nNewLen; } OUString aStr = rOrigStr; // recode string if needed if( mpFontInstance->mpConversion ) { mpFontInstance->mpConversion->RecodeString( aStr, 0, aStr.getLength() ); pLayoutCache = nullptr; // don't use cache with modified string! pGlyphs = nullptr; } DeviceCoordinate nPixelWidth = static_cast(nLogicalWidth); if( nLogicalWidth && mbMap ) { // convert from logical units to physical units nPixelWidth = LogicWidthToDeviceCoordinate( nLogicalWidth ); } vcl::text::ImplLayoutArgs aLayoutArgs = ImplPrepareLayoutArgs( aStr, nMinIndex, nLen, nPixelWidth, flags, pLayoutCache); DeviceCoordinate nEndGlyphCoord(0); std::unique_ptr xNaturalDXPixelArray; if( !pDXArray.empty() ) { xNaturalDXPixelArray.reset(new double[nLen]); if (mbMap) { // convert from logical units to font units without rounding, // keeping accuracy for lower levels int nSubPixels = pDXArray.get_factor(); for (int i = 0; i < nLen; ++i) xNaturalDXPixelArray[i] = ImplLogicWidthToDeviceSubPixel(pDXArray.get_subunit(i)) / nSubPixels; } else { for(int i = 0; i < nLen; ++i) xNaturalDXPixelArray[i] = pDXArray.get(i); } aLayoutArgs.SetNaturalDXArray(xNaturalDXPixelArray.get()); nEndGlyphCoord = std::lround(xNaturalDXPixelArray[nLen - 1]); } if (!pKashidaArray.empty()) aLayoutArgs.SetKashidaArray(pKashidaArray.data()); // get matching layout object for base font std::unique_ptr pSalLayout = mpGraphics->GetTextLayout(0); // layout text if( pSalLayout && !pSalLayout->LayoutText( aLayoutArgs, pGlyphs ? pGlyphs->Impl(0) : nullptr ) ) { pSalLayout.reset(); } if( !pSalLayout ) return nullptr; pSalLayout->SetTextRenderModeForResolutionIndependentLayout(mbMap); // do glyph fallback if needed // #105768# avoid fallback for very small font sizes if (aLayoutArgs.HasFallbackRun() && mpFontInstance->GetFontSelectPattern().mnHeight >= 3) pSalLayout = ImplGlyphFallbackLayout(std::move(pSalLayout), aLayoutArgs, pGlyphs); if (flags & SalLayoutFlags::GlyphItemsOnly) // Return glyph items only after fallback handling. Otherwise they may // contain invalid glyph IDs. return pSalLayout; // position, justify, etc. the layout pSalLayout->AdjustLayout( aLayoutArgs ); // default to on for pdf export, which uses SubPixelToLogic to convert back to // the logical coord space, of if we are scaling/mapping if (mbMap || meOutDevType == OUTDEV_PDF) pSalLayout->DrawBase() = ImplLogicToDeviceSubPixel(rLogicalPos); else { Point aDevicePos = ImplLogicToDevicePixel(rLogicalPos); pSalLayout->DrawBase() = DevicePoint(aDevicePos.X(), aDevicePos.Y()); } // adjust to right alignment if necessary if( aLayoutArgs.mnFlags & SalLayoutFlags::RightAlign ) { DeviceCoordinate nRTLOffset; if (!pDXArray.empty()) nRTLOffset = nEndGlyphCoord; else if( nPixelWidth ) nRTLOffset = nPixelWidth; else nRTLOffset = pSalLayout->GetTextWidth(); pSalLayout->DrawOffset().setX( 1 - nRTLOffset ); } if(IsTrackingFontMappingUse()) TrackFontMappingUse(GetFont(), pSalLayout.get()); return pSalLayout; } std::shared_ptr OutputDevice::CreateTextLayoutCache( OUString const& rString) { return vcl::text::TextLayoutCache::Create(rString); } bool OutputDevice::GetTextIsRTL( const OUString& rString, sal_Int32 nIndex, sal_Int32 nLen ) const { OUString aStr( rString ); vcl::text::ImplLayoutArgs aArgs = ImplPrepareLayoutArgs(aStr, nIndex, nLen, 0); bool bRTL = false; int nCharPos = -1; if (!aArgs.GetNextPos(&nCharPos, &bRTL)) return false; return (nCharPos != nIndex); } sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, tools::Long nTextWidth, sal_Int32 nIndex, sal_Int32 nLen, tools::Long nCharExtra, vcl::text::TextLayoutCache const*const pLayoutCache, const SalLayoutGlyphs* pGlyphs) const { std::unique_ptr pSalLayout = ImplLayout( rStr, nIndex, nLen, Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pGlyphs); sal_Int32 nRetVal = -1; if( pSalLayout ) { // convert logical widths into layout units // NOTE: be very careful to avoid rounding errors for nCharExtra case // problem with rounding errors especially for small nCharExtras // TODO: remove when layout units have subpixel granularity tools::Long nSubPixelFactor = 64; nTextWidth *= nSubPixelFactor; DeviceCoordinate nTextPixelWidth = LogicWidthToDeviceCoordinate( nTextWidth ); DeviceCoordinate nExtraPixelWidth = 0; if( nCharExtra != 0 ) { nCharExtra *= nSubPixelFactor; nExtraPixelWidth = LogicWidthToDeviceCoordinate( nCharExtra ); } nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor ); } return nRetVal; } sal_Int32 OutputDevice::GetTextBreak( const OUString& rStr, tools::Long nTextWidth, sal_Unicode nHyphenChar, sal_Int32& rHyphenPos, sal_Int32 nIndex, sal_Int32 nLen, tools::Long nCharExtra, vcl::text::TextLayoutCache const*const pLayoutCache, const SalLayoutGlyphs* pGlyphs) const { rHyphenPos = -1; std::unique_ptr pSalLayout = ImplLayout( rStr, nIndex, nLen, Point(0,0), 0, {}, {}, eDefaultLayout, pLayoutCache, pGlyphs); sal_Int32 nRetVal = -1; if( pSalLayout ) { // convert logical widths into layout units // NOTE: be very careful to avoid rounding errors for nCharExtra case // problem with rounding errors especially for small nCharExtras // TODO: remove when layout units have subpixel granularity tools::Long nSubPixelFactor = 64; nTextWidth *= nSubPixelFactor; DeviceCoordinate nTextPixelWidth = LogicWidthToDeviceCoordinate( nTextWidth ); DeviceCoordinate nExtraPixelWidth = 0; if( nCharExtra != 0 ) { nCharExtra *= nSubPixelFactor; nExtraPixelWidth = LogicWidthToDeviceCoordinate( nCharExtra ); } // calculate un-hyphenated break position nRetVal = pSalLayout->GetTextBreak( nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor ); // calculate hyphenated break position OUString aHyphenStr(nHyphenChar); std::unique_ptr pHyphenLayout = ImplLayout( aHyphenStr, 0, 1 ); if( pHyphenLayout ) { // calculate subpixel width of hyphenation character tools::Long nHyphenPixelWidth = pHyphenLayout->GetTextWidth() * nSubPixelFactor; // calculate hyphenated break position nTextPixelWidth -= nHyphenPixelWidth; if( nExtraPixelWidth > 0 ) nTextPixelWidth -= nExtraPixelWidth; rHyphenPos = pSalLayout->GetTextBreak(nTextPixelWidth, nExtraPixelWidth, nSubPixelFactor); if( rHyphenPos > nRetVal ) rHyphenPos = nRetVal; } } return nRetVal; } void OutputDevice::ImplDrawText( OutputDevice& rTargetDevice, const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle, std::vector< tools::Rectangle >* pVector, OUString* pDisplayText, vcl::ITextLayout& _rLayout ) { Color aOldTextColor; Color aOldTextFillColor; bool bRestoreFillColor = false; if ( (nStyle & DrawTextFlags::Disable) && ! pVector ) { bool bHighContrastBlack = false; bool bHighContrastWhite = false; const StyleSettings& rStyleSettings( rTargetDevice.GetSettings().GetStyleSettings() ); if( rStyleSettings.GetHighContrastMode() ) { Color aCol; if( rTargetDevice.IsBackground() ) aCol = rTargetDevice.GetBackground().GetColor(); else // best guess is the face color here // but it may be totally wrong. the background color // was typically already reset aCol = rStyleSettings.GetFaceColor(); bHighContrastBlack = aCol.IsDark(); bHighContrastWhite = aCol.IsBright(); } aOldTextColor = rTargetDevice.GetTextColor(); if ( rTargetDevice.IsTextFillColor() ) { bRestoreFillColor = true; aOldTextFillColor = rTargetDevice.GetTextFillColor(); } if( bHighContrastBlack ) rTargetDevice.SetTextColor( COL_GREEN ); else if( bHighContrastWhite ) rTargetDevice.SetTextColor( COL_LIGHTGREEN ); else { // draw disabled text always without shadow // as it fits better with native look rTargetDevice.SetTextColor( rTargetDevice.GetSettings().GetStyleSettings().GetDisableColor() ); } } tools::Long nWidth = rRect.GetWidth(); tools::Long nHeight = rRect.GetHeight(); if (nWidth <= 0 || nHeight <= 0) { if (nStyle & DrawTextFlags::Clip) return; static bool bFuzzing = utl::ConfigManager::IsFuzzing(); SAL_WARN_IF(bFuzzing, "vcl", "skipping negative rectangle of: " << nWidth << " x " << nHeight); if (bFuzzing) return; } Point aPos = rRect.TopLeft(); tools::Long nTextHeight = rTargetDevice.GetTextHeight(); TextAlign eAlign = rTargetDevice.GetTextAlign(); sal_Int32 nMnemonicPos = -1; OUString aStr = rOrigStr; if ( nStyle & DrawTextFlags::Mnemonic ) aStr = removeMnemonicFromString( aStr, nMnemonicPos ); const bool bDrawMnemonics = !(rTargetDevice.GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector; // We treat multiline text differently if ( nStyle & DrawTextFlags::MultiLine ) { ImplMultiTextLineInfo aMultiLineInfo; sal_Int32 i; sal_Int32 nFormatLines; if ( nTextHeight ) { tools::Long nMaxTextWidth = ImplGetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle, _rLayout ); sal_Int32 nLines = static_cast(nHeight/nTextHeight); OUString aLastLine; nFormatLines = aMultiLineInfo.Count(); if (nLines <= 0) nLines = 1; if ( nFormatLines > nLines ) { if ( nStyle & DrawTextFlags::EndEllipsis ) { // Create last line and shorten it nFormatLines = nLines-1; ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( nFormatLines ); aLastLine = convertLineEnd(aStr.copy(rLineInfo.GetIndex()), LINEEND_LF); // Replace all LineFeeds with Spaces OUStringBuffer aLastLineBuffer(aLastLine); sal_Int32 nLastLineLen = aLastLineBuffer.getLength(); for ( i = 0; i < nLastLineLen; i++ ) { if ( aLastLineBuffer[ i ] == '\n' ) aLastLineBuffer[ i ] = ' '; } aLastLine = aLastLineBuffer.makeStringAndClear(); aLastLine = ImplGetEllipsisString( rTargetDevice, aLastLine, nWidth, nStyle, _rLayout ); nStyle &= ~DrawTextFlags(DrawTextFlags::VCenter | DrawTextFlags::Bottom); nStyle |= DrawTextFlags::Top; } } else { if ( nMaxTextWidth <= nWidth ) nStyle &= ~DrawTextFlags::Clip; } // Do we need to clip the height? if ( nFormatLines*nTextHeight > nHeight ) nStyle |= DrawTextFlags::Clip; // Set clipping if ( nStyle & DrawTextFlags::Clip ) { rTargetDevice.Push( vcl::PushFlags::CLIPREGION ); rTargetDevice.IntersectClipRegion( rRect ); } // Vertical alignment if ( nStyle & DrawTextFlags::Bottom ) aPos.AdjustY(nHeight-(nFormatLines*nTextHeight) ); else if ( nStyle & DrawTextFlags::VCenter ) aPos.AdjustY((nHeight-(nFormatLines*nTextHeight))/2 ); // Font alignment if ( eAlign == ALIGN_BOTTOM ) aPos.AdjustY(nTextHeight ); else if ( eAlign == ALIGN_BASELINE ) aPos.AdjustY(rTargetDevice.GetFontMetric().GetAscent() ); // Output all lines except for the last one for ( i = 0; i < nFormatLines; i++ ) { ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i ); if ( nStyle & DrawTextFlags::Right ) aPos.AdjustX(nWidth-rLineInfo.GetWidth() ); else if ( nStyle & DrawTextFlags::Center ) aPos.AdjustX((nWidth-rLineInfo.GetWidth())/2 ); sal_Int32 nIndex = rLineInfo.GetIndex(); sal_Int32 nLineLen = rLineInfo.GetLen(); _rLayout.DrawText( aPos, aStr, nIndex, nLineLen, pVector, pDisplayText ); if ( bDrawMnemonics ) { if ( (nMnemonicPos >= nIndex) && (nMnemonicPos < nIndex+nLineLen) ) { tools::Long nMnemonicX; tools::Long nMnemonicY; DeviceCoordinate nMnemonicWidth; std::unique_ptr const pCaretXArray(new sal_Int32[2 * nLineLen]); /*sal_Bool bRet =*/ _rLayout.GetCaretPositions( aStr, pCaretXArray.get(), nIndex, nLineLen ); sal_Int32 lc_x1 = pCaretXArray[2*(nMnemonicPos - nIndex)]; sal_Int32 lc_x2 = pCaretXArray[2*(nMnemonicPos - nIndex)+1]; nMnemonicWidth = rTargetDevice.LogicWidthToDeviceCoordinate( std::abs(lc_x1 - lc_x2) ); Point aTempPos = rTargetDevice.LogicToPixel( aPos ); nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( std::min( lc_x1, lc_x2 ) ); nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() ); rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); } } aPos.AdjustY(nTextHeight ); aPos.setX( rRect.Left() ); } // If there still is a last line, we output it left-aligned as the line would be clipped if ( !aLastLine.isEmpty() ) _rLayout.DrawText( aPos, aLastLine, 0, aLastLine.getLength(), pVector, pDisplayText ); // Reset clipping if ( nStyle & DrawTextFlags::Clip ) rTargetDevice.Pop(); } } else { tools::Long nTextWidth = _rLayout.GetTextWidth( aStr, 0, -1 ); // Clip text if needed if ( nTextWidth > nWidth ) { if ( nStyle & TEXT_DRAW_ELLIPSIS ) { aStr = ImplGetEllipsisString( rTargetDevice, aStr, nWidth, nStyle, _rLayout ); nStyle &= ~DrawTextFlags(DrawTextFlags::Center | DrawTextFlags::Right); nStyle |= DrawTextFlags::Left; nTextWidth = _rLayout.GetTextWidth( aStr, 0, aStr.getLength() ); } } else { if ( nTextHeight <= nHeight ) nStyle &= ~DrawTextFlags::Clip; } // horizontal text alignment if ( nStyle & DrawTextFlags::Right ) aPos.AdjustX(nWidth-nTextWidth ); else if ( nStyle & DrawTextFlags::Center ) aPos.AdjustX((nWidth-nTextWidth)/2 ); // vertical font alignment if ( eAlign == ALIGN_BOTTOM ) aPos.AdjustY(nTextHeight ); else if ( eAlign == ALIGN_BASELINE ) aPos.AdjustY(rTargetDevice.GetFontMetric().GetAscent() ); if ( nStyle & DrawTextFlags::Bottom ) aPos.AdjustY(nHeight-nTextHeight ); else if ( nStyle & DrawTextFlags::VCenter ) aPos.AdjustY((nHeight-nTextHeight)/2 ); tools::Long nMnemonicX = 0; tools::Long nMnemonicY = 0; DeviceCoordinate nMnemonicWidth = 0; if (nMnemonicPos != -1 && nMnemonicPos < aStr.getLength()) { std::unique_ptr const pCaretXArray(new sal_Int32[2 * aStr.getLength()]); /*sal_Bool bRet =*/ _rLayout.GetCaretPositions( aStr, pCaretXArray.get(), 0, aStr.getLength() ); tools::Long lc_x1 = pCaretXArray[2*nMnemonicPos]; tools::Long lc_x2 = pCaretXArray[2*nMnemonicPos+1]; nMnemonicWidth = rTargetDevice.LogicWidthToDeviceCoordinate( std::abs(lc_x1 - lc_x2) ); Point aTempPos = rTargetDevice.LogicToPixel( aPos ); nMnemonicX = rTargetDevice.GetOutOffXPixel() + aTempPos.X() + rTargetDevice.ImplLogicWidthToDevicePixel( std::min(lc_x1, lc_x2) ); nMnemonicY = rTargetDevice.GetOutOffYPixel() + aTempPos.Y() + rTargetDevice.ImplLogicWidthToDevicePixel( rTargetDevice.GetFontMetric().GetAscent() ); } if ( nStyle & DrawTextFlags::Clip ) { rTargetDevice.Push( vcl::PushFlags::CLIPREGION ); rTargetDevice.IntersectClipRegion( rRect ); _rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), pVector, pDisplayText ); if ( bDrawMnemonics && nMnemonicPos != -1 ) rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); rTargetDevice.Pop(); } else { _rLayout.DrawText( aPos, aStr, 0, aStr.getLength(), pVector, pDisplayText ); if ( bDrawMnemonics && nMnemonicPos != -1 ) rTargetDevice.ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); } } if ( nStyle & DrawTextFlags::Disable && !pVector ) { rTargetDevice.SetTextColor( aOldTextColor ); if ( bRestoreFillColor ) rTargetDevice.SetTextFillColor( aOldTextFillColor ); } } void OutputDevice::AddTextRectActions( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle, GDIMetaFile& rMtf ) { if ( rOrigStr.isEmpty() || rRect.IsEmpty() ) return; // we need a graphics if( !mpGraphics && !AcquireGraphics() ) return; assert(mpGraphics); if( mbInitClipRegion ) InitClipRegion(); // temporarily swap in passed mtf for action generation, and // disable output generation. const bool bOutputEnabled( IsOutputEnabled() ); GDIMetaFile* pMtf = mpMetaFile; mpMetaFile = &rMtf; EnableOutput( false ); // #i47157# Factored out to ImplDrawTextRect(), to be shared // between us and DrawText() vcl::DefaultTextLayout aLayout( *this ); ImplDrawText( *this, rRect, rOrigStr, nStyle, nullptr, nullptr, aLayout ); // and restore again EnableOutput( bOutputEnabled ); mpMetaFile = pMtf; } void OutputDevice::DrawText( const tools::Rectangle& rRect, const OUString& rOrigStr, DrawTextFlags nStyle, std::vector< tools::Rectangle >* pVector, OUString* pDisplayText, vcl::ITextLayout* _pTextLayout ) { assert(!is_double_buffered_window()); if (mpOutDevData->mpRecordLayout) { pVector = &mpOutDevData->mpRecordLayout->m_aUnicodeBoundRects; pDisplayText = &mpOutDevData->mpRecordLayout->m_aDisplayText; } bool bDecomposeTextRectAction = ( _pTextLayout != nullptr ) && _pTextLayout->DecomposeTextRectAction(); if ( mpMetaFile && !bDecomposeTextRectAction ) mpMetaFile->AddAction( new MetaTextRectAction( rRect, rOrigStr, nStyle ) ); if ( ( !IsDeviceOutputNecessary() && !pVector && !bDecomposeTextRectAction ) || rOrigStr.isEmpty() || rRect.IsEmpty() ) return; // we need a graphics if( !mpGraphics && !AcquireGraphics() ) return; assert(mpGraphics); if( mbInitClipRegion ) InitClipRegion(); if (mbOutputClipped && !bDecomposeTextRectAction && !pDisplayText) return; // temporarily disable mtf action generation (ImplDrawText _does_ // create MetaActionType::TEXTs otherwise) GDIMetaFile* pMtf = mpMetaFile; if ( !bDecomposeTextRectAction ) mpMetaFile = nullptr; // #i47157# Factored out to ImplDrawText(), to be used also // from AddTextRectActions() vcl::DefaultTextLayout aDefaultLayout( *this ); ImplDrawText( *this, rRect, rOrigStr, nStyle, pVector, pDisplayText, _pTextLayout ? *_pTextLayout : aDefaultLayout ); // and enable again mpMetaFile = pMtf; if( mpAlphaVDev ) mpAlphaVDev->DrawText( rRect, rOrigStr, nStyle, pVector, pDisplayText ); } tools::Rectangle OutputDevice::GetTextRect( const tools::Rectangle& rRect, const OUString& rStr, DrawTextFlags nStyle, TextRectInfo* pInfo, const vcl::ITextLayout* _pTextLayout ) const { tools::Rectangle aRect = rRect; sal_Int32 nLines; tools::Long nWidth = rRect.GetWidth(); tools::Long nMaxWidth; tools::Long nTextHeight = GetTextHeight(); OUString aStr = rStr; if ( nStyle & DrawTextFlags::Mnemonic ) aStr = removeMnemonicFromString( aStr ); if ( nStyle & DrawTextFlags::MultiLine ) { ImplMultiTextLineInfo aMultiLineInfo; sal_Int32 nFormatLines; sal_Int32 i; nMaxWidth = 0; vcl::DefaultTextLayout aDefaultLayout( *const_cast< OutputDevice* >( this ) ); ImplGetTextLines( rRect, nTextHeight, aMultiLineInfo, nWidth, aStr, nStyle, _pTextLayout ? *_pTextLayout : aDefaultLayout ); nFormatLines = aMultiLineInfo.Count(); if ( !nTextHeight ) nTextHeight = 1; nLines = static_cast(aRect.GetHeight()/nTextHeight); if ( pInfo ) pInfo->mnLineCount = nFormatLines; if ( !nLines ) nLines = 1; if ( nFormatLines <= nLines ) nLines = nFormatLines; else { if ( !(nStyle & DrawTextFlags::EndEllipsis) ) nLines = nFormatLines; else { if ( pInfo ) pInfo->mbEllipsis = true; nMaxWidth = nWidth; } } if ( pInfo ) { bool bMaxWidth = nMaxWidth == 0; pInfo->mnMaxWidth = 0; for ( i = 0; i < nLines; i++ ) { ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i ); if ( bMaxWidth && (rLineInfo.GetWidth() > nMaxWidth) ) nMaxWidth = rLineInfo.GetWidth(); if ( rLineInfo.GetWidth() > pInfo->mnMaxWidth ) pInfo->mnMaxWidth = rLineInfo.GetWidth(); } } else if ( !nMaxWidth ) { for ( i = 0; i < nLines; i++ ) { ImplTextLineInfo& rLineInfo = aMultiLineInfo.GetLine( i ); if ( rLineInfo.GetWidth() > nMaxWidth ) nMaxWidth = rLineInfo.GetWidth(); } } } else { nLines = 1; nMaxWidth = _pTextLayout ? _pTextLayout->GetTextWidth( aStr, 0, aStr.getLength() ) : GetTextWidth( aStr ); if ( pInfo ) { pInfo->mnLineCount = 1; pInfo->mnMaxWidth = nMaxWidth; } if ( (nMaxWidth > nWidth) && (nStyle & TEXT_DRAW_ELLIPSIS) ) { if ( pInfo ) pInfo->mbEllipsis = true; nMaxWidth = nWidth; } } if ( nStyle & DrawTextFlags::Right ) aRect.SetLeft( aRect.Right()-nMaxWidth+1 ); else if ( nStyle & DrawTextFlags::Center ) { aRect.AdjustLeft((nWidth-nMaxWidth)/2 ); aRect.SetRight( aRect.Left()+nMaxWidth-1 ); } else aRect.SetRight( aRect.Left()+nMaxWidth-1 ); if ( nStyle & DrawTextFlags::Bottom ) aRect.SetTop( aRect.Bottom()-(nTextHeight*nLines)+1 ); else if ( nStyle & DrawTextFlags::VCenter ) { aRect.AdjustTop((aRect.GetHeight()-(nTextHeight*nLines))/2 ); aRect.SetBottom( aRect.Top()+(nTextHeight*nLines)-1 ); } else aRect.SetBottom( aRect.Top()+(nTextHeight*nLines)-1 ); // #99188# get rid of rounding problems when using this rect later if (nStyle & DrawTextFlags::Right) aRect.AdjustLeft( -1 ); else aRect.AdjustRight( 1 ); if (maFont.GetOrientation() != 0_deg10) { tools::Polygon aRotatedPolygon(aRect); aRotatedPolygon.Rotate(Point(aRect.GetWidth() / 2, aRect.GetHeight() / 2), maFont.GetOrientation()); return aRotatedPolygon.GetBoundRect(); } 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( *this, rOrigStr, nMaxWidth, nStyle, aTextLayout ); } OUString OutputDevice::ImplGetEllipsisString( const OutputDevice& rTargetDevice, 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(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( rTargetDevice, 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( rTargetDevice, 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( rTargetDevice, 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, const SalLayoutGlyphs* pGlyphs ) { assert(!is_double_buffered_window()); if( (nLen < 0) || (nIndex + nLen >= rStr.getLength())) { nLen = rStr.getLength() - nIndex; } if ( !IsDeviceOutputNecessary() || (nIndex >= rStr.getLength()) ) return; // better get graphics here because ImplDrawMnemonicLine() will not // we need a graphics if( !mpGraphics && !AcquireGraphics() ) return; assert(mpGraphics); if( mbInitClipRegion ) InitClipRegion(); if ( mbOutputClipped ) return; if( nIndex >= rStr.getLength() ) return; if( (nLen < 0) || (nIndex + nLen >= rStr.getLength())) { nLen = rStr.getLength() - nIndex; } OUString aStr = rStr; sal_Int32 nMnemonicPos = -1; tools::Long nMnemonicX = 0; tools::Long nMnemonicY = 0; tools::Long nMnemonicWidth = 0; if ( (nStyle & DrawTextFlags::Mnemonic) && nLen > 1 ) { aStr = removeMnemonicFromString( aStr, nMnemonicPos ); if ( nMnemonicPos != -1 ) { if( nMnemonicPos < nIndex ) { --nIndex; } else { if( nMnemonicPos < (nIndex+nLen) ) --nLen; SAL_WARN_IF( nMnemonicPos >= (nIndex+nLen), "vcl", "Mnemonic underline marker after last character" ); } bool bInvalidPos = false; if( nMnemonicPos >= nLen ) { // may occur in BiDi-Strings: the '~' is sometimes found behind the last char // due to some strange BiDi text editors // -> place the underline behind the string to indicate a failure bInvalidPos = true; nMnemonicPos = nLen-1; } std::unique_ptr const pCaretXArray(new sal_Int32[2 * nLen]); /*sal_Bool bRet =*/ GetCaretPositions( aStr, pCaretXArray.get(), nIndex, nLen, pGlyphs ); sal_Int32 lc_x1 = pCaretXArray[ 2*(nMnemonicPos - nIndex) ]; sal_Int32 lc_x2 = pCaretXArray[ 2*(nMnemonicPos - nIndex)+1 ]; nMnemonicWidth = ::abs(static_cast(lc_x1 - lc_x2)); Point aTempPos( std::min(lc_x1,lc_x2), GetFontMetric().GetAscent() ); if( bInvalidPos ) // #106952#, place behind the (last) character aTempPos = Point( std::max(lc_x1,lc_x2), GetFontMetric().GetAscent() ); aTempPos += rPos; aTempPos = LogicToPixel( aTempPos ); nMnemonicX = mnOutOffX + aTempPos.X(); nMnemonicY = mnOutOffY + aTempPos.Y(); } } bool autoacc = ImplGetSVData()->maNWFData.mbAutoAccel; if ( nStyle & DrawTextFlags::Disable && ! pVector ) { Color aOldTextColor; Color aOldTextFillColor; bool bRestoreFillColor; bool bHighContrastBlack = false; bool bHighContrastWhite = false; const StyleSettings& rStyleSettings( GetSettings().GetStyleSettings() ); if( rStyleSettings.GetHighContrastMode() ) { if( IsBackground() ) { Wallpaper aWall = GetBackground(); Color aCol = aWall.GetColor(); bHighContrastBlack = aCol.IsDark(); bHighContrastWhite = aCol.IsBright(); } } aOldTextColor = GetTextColor(); if ( IsTextFillColor() ) { bRestoreFillColor = true; aOldTextFillColor = GetTextFillColor(); } else bRestoreFillColor = false; if( bHighContrastBlack ) SetTextColor( COL_GREEN ); else if( bHighContrastWhite ) SetTextColor( COL_LIGHTGREEN ); else SetTextColor( GetSettings().GetStyleSettings().GetDisableColor() ); DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText ); if (!(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && (!autoacc || !(nStyle & DrawTextFlags::HideMnemonic)) ) { if ( nMnemonicPos != -1 ) ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); } SetTextColor( aOldTextColor ); if ( bRestoreFillColor ) SetTextFillColor( aOldTextFillColor ); } else { DrawText( rPos, aStr, nIndex, nLen, pVector, pDisplayText, pGlyphs ); if ( !(GetSettings().GetStyleSettings().GetOptions() & StyleSettingsOptions::NoMnemonics) && !pVector && (!autoacc || !(nStyle & DrawTextFlags::HideMnemonic)) ) { if ( nMnemonicPos != -1 ) ImplDrawMnemonicLine( nMnemonicX, nMnemonicY, nMnemonicWidth ); } } if( mpAlphaVDev ) mpAlphaVDev->DrawCtrlText( rPos, rStr, nIndex, nLen, nStyle, pVector, pDisplayText ); } tools::Long OutputDevice::GetCtrlTextWidth( const OUString& rStr, const SalLayoutGlyphs* pGlyphs ) const { sal_Int32 nLen = rStr.getLength(); sal_Int32 nIndex = 0; sal_Int32 nMnemonicPos; OUString aStr = removeMnemonicFromString( rStr, nMnemonicPos ); if ( nMnemonicPos != -1 ) { if ( nMnemonicPos < nIndex ) nIndex--; else if (static_cast(nMnemonicPos) < static_cast(nIndex+nLen)) nLen--; } return GetTextWidth( aStr, nIndex, nLen, nullptr, pGlyphs ); } bool OutputDevice::GetTextBoundRect( tools::Rectangle& rRect, const OUString& rStr, sal_Int32 nBase, sal_Int32 nIndex, sal_Int32 nLen, sal_uLong nLayoutWidth, KernArraySpan pDXAry, o3tl::span pKashidaAry, const SalLayoutGlyphs* pGlyphs ) const { bool bRet = false; rRect.SetEmpty(); std::unique_ptr pSalLayout; const Point aPoint; // calculate offset when nBase!=nIndex tools::Long nXOffset = 0; if( nBase != nIndex ) { sal_Int32 nStart = std::min( nBase, nIndex ); sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart; pSalLayout = ImplLayout( rStr, nStart, nOfsLen, aPoint, nLayoutWidth, pDXAry, pKashidaAry ); if( pSalLayout ) { nXOffset = pSalLayout->GetTextWidth(); // TODO: fix offset calculation for Bidi case if( nBase < nIndex) nXOffset = -nXOffset; } } pSalLayout = ImplLayout(rStr, nIndex, nLen, aPoint, nLayoutWidth, pDXAry, pKashidaAry, eDefaultLayout, nullptr, pGlyphs); if( pSalLayout ) { tools::Rectangle aPixelRect; bRet = pSalLayout->GetBoundRect(aPixelRect); if( bRet ) { Point aRotatedOfs( mnTextOffX, mnTextOffY ); DevicePoint aPos = pSalLayout->GetDrawPosition(DevicePoint(nXOffset, 0)); aRotatedOfs -= Point(aPos.getX(), aPos.getY()); aPixelRect += aRotatedOfs; rRect = PixelToLogic( aPixelRect ); if( mbMap ) rRect += Point( maMapRes.mnMapOfsX, maMapRes.mnMapOfsY ); } } return bRet; } bool OutputDevice::GetTextOutlines( basegfx::B2DPolyPolygonVector& rVector, const OUString& rStr, sal_Int32 nBase, sal_Int32 nIndex, sal_Int32 nLen, sal_uLong nLayoutWidth, KernArraySpan pDXArray, o3tl::span pKashidaArray ) const { if (!InitFont()) return false; bool bRet = false; rVector.clear(); if( nLen < 0 ) { nLen = rStr.getLength() - nIndex; } rVector.reserve( nLen ); // we want to get the Rectangle in logical units, so to // avoid rounding errors we just size the font in logical units bool bOldMap = mbMap; if( bOldMap ) { const_cast(*this).mbMap = false; const_cast(*this).mbNewFont = true; } std::unique_ptr pSalLayout; // calculate offset when nBase!=nIndex tools::Long nXOffset = 0; if( nBase != nIndex ) { sal_Int32 nStart = std::min( nBase, nIndex ); sal_Int32 nOfsLen = std::max( nBase, nIndex ) - nStart; pSalLayout = ImplLayout( rStr, nStart, nOfsLen, Point(0,0), nLayoutWidth, pDXArray, pKashidaArray); if( pSalLayout ) { nXOffset = pSalLayout->GetTextWidth(); pSalLayout.reset(); // TODO: fix offset calculation for Bidi case if( nBase > nIndex) nXOffset = -nXOffset; } } pSalLayout = ImplLayout( rStr, nIndex, nLen, Point(0,0), nLayoutWidth, pDXArray, pKashidaArray ); if( pSalLayout ) { bRet = pSalLayout->GetOutline(rVector); if( bRet ) { // transform polygon to pixel units basegfx::B2DHomMatrix aMatrix; if( nXOffset | mnTextOffX | mnTextOffY ) { DevicePoint aRotatedOfs(mnTextOffX, mnTextOffY); aRotatedOfs -= pSalLayout->GetDrawPosition(DevicePoint(nXOffset, 0)); aMatrix.translate( aRotatedOfs.getX(), aRotatedOfs.getY() ); } if( !aMatrix.isIdentity() ) { for (auto & elem : rVector) elem.transform( aMatrix ); } } pSalLayout.reset(); } if( bOldMap ) { // restore original font size and map mode const_cast(*this).mbMap = bOldMap; const_cast(*this).mbNewFont = true; } return bRet; } bool OutputDevice::GetTextOutlines( PolyPolyVector& rResultVector, const OUString& rStr, sal_Int32 nBase, sal_Int32 nIndex, sal_Int32 nLen, sal_uLong nLayoutWidth, KernArraySpan pDXArray, o3tl::span pKashidaArray ) const { rResultVector.clear(); // get the basegfx polypolygon vector basegfx::B2DPolyPolygonVector aB2DPolyPolyVector; if( !GetTextOutlines( aB2DPolyPolyVector, rStr, nBase, nIndex, nLen, nLayoutWidth, pDXArray, pKashidaArray ) ) return false; // convert to a tool polypolygon vector rResultVector.reserve( aB2DPolyPolyVector.size() ); for (auto const& elem : aB2DPolyPolyVector) rResultVector.emplace_back(elem); // #i76339# return true; } bool OutputDevice::GetTextOutline( tools::PolyPolygon& rPolyPoly, const OUString& rStr ) const { rPolyPoly.Clear(); // get the basegfx polypolygon vector basegfx::B2DPolyPolygonVector aB2DPolyPolyVector; if( !GetTextOutlines( aB2DPolyPolyVector, rStr, 0/*nBase*/, 0/*nIndex*/, /*nLen*/-1, /*nLayoutWidth*/0, /*pDXArray*/{} ) ) return false; // convert and merge into a tool polypolygon for (auto const& elem : aB2DPolyPolyVector) for(auto const& rB2DPolygon : elem) rPolyPoly.Insert(tools::Polygon(rB2DPolygon)); // #i76339# return true; } void OutputDevice::SetSystemTextColor(SystemTextColorFlags nFlags, bool bEnabled) { if (nFlags & SystemTextColorFlags::Mono) { SetTextColor(COL_BLACK); } else { if (!bEnabled) { const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); SetTextColor(rStyleSettings.GetDisableColor()); } } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */