Files
odfstudio/vcl/source/outdev/textline.cxx
T
Noel Grandin 088a7c7c45 remove alpha device from OutputDevice
and render to a combined color+alpha bitmap

The strategy is to remove the mpAlphaVDev from OutputDevice,
and have the OutputDevice create 32-bit SalBitmap's
and then have the various backends correctly render to
those 32-bit bitmaps.

However, whenever we extract a BitmapEx from OutputDevice
(e.g. via VirtualDevice) and hand it to the rest of the
system, we split the color and alpha information.
So the rest of the codebase doesn't have to deal with
this yet.

We have some major problems to overcome here:
(*) Cairo uses 32-bit data, even if we ask for 24-bit color with no alpha.
    This is because the current version of cairo does not support RGB 24-bit data.
(*) Sometimes we want the bitmap to be initialised with an opaque white background,
    sometimes we want it to be transparent.
(*) Sometimes when we extract a bitmap from a device, we want the alpha channel,
    sometimes not.
(*) There are lots of left-over bits of previous attempts of this change lying around,
    e.g. in skia.

Notes
(*) we allow Bitmap (ie. SalBitmap) to hold color+alpha
(*) The BitmapEx(Bitmap) constructor checks if the incoming
Bitmap is color+alpha, and if so, splits into a color bitmap and an alpha bitmap
(*) Because the underlying image data is stored pre-multipled, if
we write a transparent pixel with color, and then read the pixel value back,
we might lose the color information.

Change-Id: I0fc0042d6e5be5edd99b320892fb00a8eb2842fe
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/173937
Tested-by: Jenkins
Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
2025-07-21 10:49:25 +02:00

1138 lines
39 KiB
C++

/* -*- 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 <sal/types.h>
#include <basegfx/matrix/b2dhommatrixtools.hxx>
#include <basegfx/polygon/WaveLine.hxx>
#include <tools/helpers.hxx>
#include <o3tl/hash_combine.hxx>
#include <o3tl/lru_map.hxx>
#include <comphelper/configuration.hxx>
#include <tools/lazydelete.hxx>
#include <vcl/dropcache.hxx>
#include <vcl/metaact.hxx>
#include <vcl/settings.hxx>
#include <vcl/virdev.hxx>
#include <vcl/skia/SkiaHelper.hxx>
#include <drawmode.hxx>
#include <salgdi.hxx>
#include <impglyphitem.hxx>
#include <cassert>
#define UNDERLINE_LAST LINESTYLE_BOLDWAVE
#define STRIKEOUT_LAST STRIKEOUT_X
namespace {
struct WavyLineCache final : public CacheOwner
{
WavyLineCache()
#if defined __cpp_lib_memory_resource
: m_aItems(10, &GetMemoryResource())
#else
: m_aItems(10)
#endif
{
}
bool find( Color aLineColor, size_t nLineWidth, size_t nWaveHeight, size_t nWordWidth, BitmapEx& rOutput )
{
Key aKey = { nWaveHeight, sal_uInt32(aLineColor) };
auto item = m_aItems.find( aKey );
if ( item == m_aItems.end() )
return false;
// needs update
if ( item->second.m_aLineWidth != nLineWidth || item->second.m_aWordWidth < nWordWidth )
{
return false;
}
rOutput = item->second.m_Bitmap;
return true;
}
void insert( const BitmapEx& aBitmap, const Color& aLineColor, const size_t nLineWidth, const size_t nWaveHeight, const size_t nWordWidth, BitmapEx& rOutput )
{
Key aKey = { nWaveHeight, sal_uInt32(aLineColor) };
m_aItems.insert( std::pair< Key, WavyLineCacheItem>( aKey, { nLineWidth, nWordWidth, aBitmap } ) );
rOutput = aBitmap;
}
virtual OUString getCacheName() const override { return "WavyLineCache"; }
virtual bool dropCaches() override
{
m_aItems.clear();
return true;
}
virtual void dumpState(rtl::OStringBuffer& rState) override
{
rState.append("\nWavyLineCache:\t");
rState.append(static_cast<sal_Int32>(m_aItems.size()));
}
private:
struct WavyLineCacheItem
{
size_t m_aLineWidth;
size_t m_aWordWidth;
BitmapEx m_Bitmap;
};
struct Key
{
size_t m_aFirst;
size_t m_aSecond;
bool operator ==( const Key& rOther ) const
{
return ( m_aFirst == rOther.m_aFirst && m_aSecond == rOther.m_aSecond );
}
};
struct Hash
{
size_t operator() ( const Key& rKey ) const
{
size_t aSeed = 0;
o3tl::hash_combine(aSeed, rKey.m_aFirst);
o3tl::hash_combine(aSeed, rKey.m_aSecond);
return aSeed;
}
};
o3tl::lru_map< Key, WavyLineCacheItem, Hash > m_aItems;
};
}
void OutputDevice::ImplInitTextLineSize()
{
mpFontInstance->mxFontMetric->ImplInitTextLineSize( this );
}
void OutputDevice::ImplInitAboveTextLineSize()
{
mpFontInstance->mxFontMetric->ImplInitAboveTextLineSize( this );
}
void OutputDevice::ImplDrawWavePixel( tools::Long nOriginX, tools::Long nOriginY,
tools::Long nCurX, tools::Long nCurY,
tools::Long nWidth,
Degree10 nOrientation,
SalGraphics* pGraphics,
const OutputDevice& rOutDev,
tools::Long nPixWidth, tools::Long nPixHeight )
{
if (nOrientation)
{
Point aPoint( nOriginX, nOriginY );
aPoint.RotateAround( nCurX, nCurY, nOrientation );
}
if (shouldDrawWavePixelAsRect(nWidth))
{
pGraphics->DrawRect( nCurX, nCurY, nPixWidth, nPixHeight, rOutDev );
}
else
{
pGraphics->DrawPixel( nCurX, nCurY, rOutDev );
}
}
bool OutputDevice::shouldDrawWavePixelAsRect(tools::Long nLineWidth) const
{
if (nLineWidth > 1)
return true;
return false;
}
void OutputDevice::SetWaveLineColors(Color const& rColor, tools::Long nLineWidth)
{
// On printers that output pixel via DrawRect()
if (nLineWidth > 1)
{
if (mbLineColor || mbInitLineColor)
{
mpGraphics->SetLineColor();
mbInitLineColor = true;
}
mpGraphics->SetFillColor( rColor );
mbInitFillColor = true;
}
else
{
mpGraphics->SetLineColor( rColor );
mbInitLineColor = true;
}
}
Size OutputDevice::GetWaveLineSize(tools::Long nLineWidth) const
{
if (nLineWidth > 1)
return Size(nLineWidth, ((nLineWidth*mnDPIX)+(mnDPIY/2))/mnDPIY);
return Size(1, 1);
}
void OutputDevice::ImplDrawWaveLine( tools::Long nBaseX, tools::Long nBaseY,
tools::Long nDistX, tools::Long nDistY,
tools::Long nWidth, tools::Long nHeight,
tools::Long nLineWidth, Degree10 nOrientation,
const Color& rColor )
{
if ( !nHeight )
return;
tools::Long nStartX = nBaseX + nDistX;
tools::Long nStartY = nBaseY + nDistY;
// If the height is 1 pixel, it's enough output a line
if ( (nLineWidth == 1) && (nHeight == 1) )
{
mpGraphics->SetLineColor( rColor );
mbInitLineColor = true;
tools::Long nEndX = nStartX+nWidth;
tools::Long nEndY = nStartY;
if ( nOrientation )
{
Point aOriginPt( nBaseX, nBaseY );
aOriginPt.RotateAround( nStartX, nStartY, nOrientation );
aOriginPt.RotateAround( nEndX, nEndY, nOrientation );
}
mpGraphics->DrawLine( nStartX, nStartY, nEndX, nEndY, *this );
}
else
{
tools::Long nCurX = nStartX;
tools::Long nCurY = nStartY;
tools::Long nDiffX = 2;
tools::Long nDiffY = nHeight-1;
tools::Long nCount = nWidth;
tools::Long nOffY = -1;
SetWaveLineColors(rColor, nLineWidth);
Size aSize(GetWaveLineSize(nLineWidth));
tools::Long nPixWidth = aSize.Width();
tools::Long nPixHeight = aSize.Height();
if ( !nDiffY )
{
while ( nWidth )
{
ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
mpGraphics, *this,
nPixWidth, nPixHeight );
nCurX++;
nWidth--;
}
}
else
{
nCurY += nDiffY;
tools::Long nFreq = nCount / (nDiffX+nDiffY);
while ( nFreq-- )
{
for( tools::Long i = nDiffY; i; --i )
{
ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
mpGraphics, *this,
nPixWidth, nPixHeight );
nCurX++;
nCurY += nOffY;
}
for( tools::Long i = nDiffX; i; --i )
{
ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
mpGraphics, *this,
nPixWidth, nPixHeight );
nCurX++;
}
nOffY = -nOffY;
}
nFreq = nCount % (nDiffX+nDiffY);
if ( nFreq )
{
for( tools::Long i = nDiffY; i && nFreq; --i, --nFreq )
{
ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
mpGraphics, *this,
nPixWidth, nPixHeight );
nCurX++;
nCurY += nOffY;
}
for( tools::Long i = nDiffX; i && nFreq; --i, --nFreq )
{
ImplDrawWavePixel( nBaseX, nBaseY, nCurX, nCurY, nLineWidth, nOrientation,
mpGraphics, *this,
nPixWidth, nPixHeight );
nCurX++;
}
}
}
}
}
void OutputDevice::ImplDrawWaveTextLine( tools::Long nBaseX, tools::Long nBaseY,
tools::Long nDistX, tools::Long nDistY,
tools::Long nWidth, tools::Long nLayoutWidth,
FontLineStyle eTextLine,
Color aColor,
bool bIsAbove )
{
static bool bFuzzing = comphelper::IsFuzzing();
if (bFuzzing && nLayoutWidth > 10000)
{
SAL_WARN("vcl.gdi", "drawLine, skipping suspicious WaveTextLine of length: "
<< nLayoutWidth << " for fuzzing performance");
return;
}
LogicalFontInstance* pFontInstance = mpFontInstance.get();
tools::Long nLineHeight;
tools::Long nLinePos;
if ( bIsAbove )
{
nLineHeight = pFontInstance->mxFontMetric->GetAboveWavelineUnderlineSize();
nLinePos = pFontInstance->mxFontMetric->GetAboveWavelineUnderlineOffset();
}
else
{
nLineHeight = pFontInstance->mxFontMetric->GetWavelineUnderlineSize();
nLinePos = pFontInstance->mxFontMetric->GetWavelineUnderlineOffset();
}
if ( (eTextLine == LINESTYLE_SMALLWAVE) && (nLineHeight > 3) )
nLineHeight = 3;
tools::Long nLineWidth = mnDPIX / 300;
if ( !nLineWidth )
nLineWidth = 1;
if ( eTextLine == LINESTYLE_BOLDWAVE )
nLineWidth *= 2;
nLinePos += nDistY - (nLineHeight / 2);
tools::Long nLineWidthHeight = ((nLineWidth * mnDPIX) + (mnDPIY / 2)) / mnDPIY;
if ( eTextLine == LINESTYLE_DOUBLEWAVE )
{
tools::Long nOrgLineHeight = nLineHeight;
nLineHeight /= 3;
if ( nLineHeight < 2 )
{
if ( nOrgLineHeight > 1 )
nLineHeight = 2;
else
nLineHeight = 1;
}
tools::Long nLineDY = nOrgLineHeight-(nLineHeight*2);
if ( nLineDY < nLineWidthHeight )
nLineDY = nLineWidthHeight;
tools::Long nLineDY2 = nLineDY/2;
if ( !nLineDY2 )
nLineDY2 = 1;
nLinePos -= nLineWidthHeight-nLineDY2;
ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight,
nLineWidth, mpFontInstance->mnOrientation, aColor );
nLinePos += nLineWidthHeight+nLineDY;
ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight,
nLineWidth, mpFontInstance->mnOrientation, aColor );
}
else
{
nLinePos -= nLineWidthHeight/2;
ImplDrawWaveLine( nBaseX, nBaseY, nDistX, nLinePos, nWidth, nLineHeight,
nLineWidth, mpFontInstance->mnOrientation, aColor );
}
}
void OutputDevice::ImplDrawStraightTextLine( tools::Long nBaseX, tools::Long nBaseY,
tools::Long nDistX, tools::Long nDistY, tools::Long nWidth,
FontLineStyle eTextLine,
Color aColor,
bool bIsAbove )
{
static bool bFuzzing = comphelper::IsFuzzing();
if (bFuzzing && nWidth > 25000)
{
SAL_WARN("vcl.gdi", "drawLine, skipping suspicious TextLine of length: "
<< nWidth << " for fuzzing performance");
return;
}
LogicalFontInstance* pFontInstance = mpFontInstance.get();
tools::Long nLineHeight = 0;
tools::Long nLinePos = 0;
tools::Long nLinePos2 = 0;
const tools::Long nY = nDistY;
if ( eTextLine > UNDERLINE_LAST )
eTextLine = LINESTYLE_SINGLE;
switch ( eTextLine )
{
case LINESTYLE_SINGLE:
case LINESTYLE_DOTTED:
case LINESTYLE_DASH:
case LINESTYLE_LONGDASH:
case LINESTYLE_DASHDOT:
case LINESTYLE_DASHDOTDOT:
if ( bIsAbove )
{
nLineHeight = pFontInstance->mxFontMetric->GetAboveUnderlineSize();
nLinePos = nY + pFontInstance->mxFontMetric->GetAboveUnderlineOffset();
}
else
{
nLineHeight = pFontInstance->mxFontMetric->GetUnderlineSize();
nLinePos = nY + pFontInstance->mxFontMetric->GetUnderlineOffset();
}
break;
case LINESTYLE_BOLD:
case LINESTYLE_BOLDDOTTED:
case LINESTYLE_BOLDDASH:
case LINESTYLE_BOLDLONGDASH:
case LINESTYLE_BOLDDASHDOT:
case LINESTYLE_BOLDDASHDOTDOT:
if ( bIsAbove )
{
nLineHeight = pFontInstance->mxFontMetric->GetAboveBoldUnderlineSize();
nLinePos = nY + pFontInstance->mxFontMetric->GetAboveBoldUnderlineOffset();
}
else
{
nLineHeight = pFontInstance->mxFontMetric->GetBoldUnderlineSize();
nLinePos = nY + pFontInstance->mxFontMetric->GetBoldUnderlineOffset();
}
break;
case LINESTYLE_DOUBLE:
if ( bIsAbove )
{
nLineHeight = pFontInstance->mxFontMetric->GetAboveDoubleUnderlineSize();
nLinePos = nY + pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset1();
nLinePos2 = nY + pFontInstance->mxFontMetric->GetAboveDoubleUnderlineOffset2();
}
else
{
nLineHeight = pFontInstance->mxFontMetric->GetDoubleUnderlineSize();
nLinePos = nY + pFontInstance->mxFontMetric->GetDoubleUnderlineOffset1();
nLinePos2 = nY + pFontInstance->mxFontMetric->GetDoubleUnderlineOffset2();
}
break;
default:
break;
}
if ( !nLineHeight )
return;
if ( mbLineColor || mbInitLineColor )
{
mpGraphics->SetLineColor();
mbInitLineColor = true;
}
mpGraphics->SetFillColor( aColor );
mbInitFillColor = true;
tools::Long nLeft = nDistX;
switch ( eTextLine )
{
case LINESTYLE_SINGLE:
case LINESTYLE_BOLD:
ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight );
break;
case LINESTYLE_DOUBLE:
ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight );
ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos2, nWidth, nLineHeight );
break;
case LINESTYLE_DOTTED:
case LINESTYLE_BOLDDOTTED:
{
tools::Long nDotWidth = nLineHeight*mnDPIY;
nDotWidth += mnDPIY/2;
nDotWidth /= mnDPIY;
tools::Long nTempWidth = nDotWidth;
tools::Long nEnd = nLeft+nWidth;
while ( nLeft < nEnd )
{
if ( nLeft+nTempWidth > nEnd )
nTempWidth = nEnd-nLeft;
ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempWidth, nLineHeight );
nLeft += nDotWidth*2;
}
}
break;
case LINESTYLE_DASH:
case LINESTYLE_LONGDASH:
case LINESTYLE_BOLDDASH:
case LINESTYLE_BOLDLONGDASH:
{
tools::Long nDotWidth = nLineHeight*mnDPIY;
nDotWidth += mnDPIY/2;
nDotWidth /= mnDPIY;
tools::Long nMinDashWidth;
tools::Long nMinSpaceWidth;
tools::Long nSpaceWidth;
tools::Long nDashWidth;
if ( (eTextLine == LINESTYLE_LONGDASH) ||
(eTextLine == LINESTYLE_BOLDLONGDASH) )
{
nMinDashWidth = nDotWidth*6;
nMinSpaceWidth = nDotWidth*2;
nDashWidth = 200;
nSpaceWidth = 100;
}
else
{
nMinDashWidth = nDotWidth*4;
nMinSpaceWidth = (nDotWidth*150)/100;
nDashWidth = 100;
nSpaceWidth = 50;
}
nDashWidth = o3tl::convert(nDashWidth * mnDPIX, o3tl::Length::mm100, o3tl::Length::in);
nSpaceWidth = o3tl::convert(nSpaceWidth * mnDPIX, o3tl::Length::mm100, o3tl::Length::in);
// DashWidth will be increased if the line is getting too thick
// in proportion to the line's length
if ( nDashWidth < nMinDashWidth )
nDashWidth = nMinDashWidth;
if ( nSpaceWidth < nMinSpaceWidth )
nSpaceWidth = nMinSpaceWidth;
tools::Long nTempWidth = nDashWidth;
tools::Long nEnd = nLeft+nWidth;
while ( nLeft < nEnd )
{
if ( nLeft+nTempWidth > nEnd )
nTempWidth = nEnd-nLeft;
ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempWidth, nLineHeight );
nLeft += nDashWidth+nSpaceWidth;
}
}
break;
case LINESTYLE_DASHDOT:
case LINESTYLE_BOLDDASHDOT:
{
tools::Long nDotWidth = nLineHeight*mnDPIY;
nDotWidth += mnDPIY/2;
nDotWidth /= mnDPIY;
tools::Long nDashWidth = o3tl::convert(100 * mnDPIX, o3tl::Length::mm100, o3tl::Length::in);
tools::Long nMinDashWidth = nDotWidth*4;
// DashWidth will be increased if the line is getting too thick
// in proportion to the line's length
if ( nDashWidth < nMinDashWidth )
nDashWidth = nMinDashWidth;
tools::Long nTempDotWidth = nDotWidth;
tools::Long nTempDashWidth = nDashWidth;
tools::Long nEnd = nLeft+nWidth;
while ( nLeft < nEnd )
{
if ( nLeft+nTempDotWidth > nEnd )
nTempDotWidth = nEnd-nLeft;
ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight );
nLeft += nDotWidth*2;
if ( nLeft > nEnd )
break;
if ( nLeft+nTempDashWidth > nEnd )
nTempDashWidth = nEnd-nLeft;
ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDashWidth, nLineHeight );
nLeft += nDashWidth+nDotWidth;
}
}
break;
case LINESTYLE_DASHDOTDOT:
case LINESTYLE_BOLDDASHDOTDOT:
{
tools::Long nDotWidth = nLineHeight*mnDPIY;
nDotWidth += mnDPIY/2;
nDotWidth /= mnDPIY;
tools::Long nDashWidth = o3tl::convert(100 * mnDPIX, o3tl::Length::mm100, o3tl::Length::in);
tools::Long nMinDashWidth = nDotWidth*4;
// DashWidth will be increased if the line is getting too thick
// in proportion to the line's length
if ( nDashWidth < nMinDashWidth )
nDashWidth = nMinDashWidth;
tools::Long nTempDotWidth = nDotWidth;
tools::Long nTempDashWidth = nDashWidth;
tools::Long nEnd = nLeft+nWidth;
while ( nLeft < nEnd )
{
if ( nLeft+nTempDotWidth > nEnd )
nTempDotWidth = nEnd-nLeft;
ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight );
nLeft += nDotWidth*2;
if ( nLeft > nEnd )
break;
if ( nLeft+nTempDotWidth > nEnd )
nTempDotWidth = nEnd-nLeft;
ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDotWidth, nLineHeight );
nLeft += nDotWidth*2;
if ( nLeft > nEnd )
break;
if ( nLeft+nTempDashWidth > nEnd )
nTempDashWidth = nEnd-nLeft;
ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nTempDashWidth, nLineHeight );
nLeft += nDashWidth+nDotWidth;
}
}
break;
default:
break;
}
}
void OutputDevice::ImplDrawStrikeoutLine( tools::Long nBaseX, tools::Long nBaseY,
tools::Long nDistX, tools::Long nDistY, tools::Long nWidth,
FontStrikeout eStrikeout,
Color aColor )
{
LogicalFontInstance* pFontInstance = mpFontInstance.get();
tools::Long nLineHeight = 0;
tools::Long nLinePos = 0;
tools::Long nLinePos2 = 0;
tools::Long nY = nDistY;
if ( eStrikeout > STRIKEOUT_LAST )
eStrikeout = STRIKEOUT_SINGLE;
switch ( eStrikeout )
{
case STRIKEOUT_SINGLE:
nLineHeight = pFontInstance->mxFontMetric->GetStrikeoutSize();
nLinePos = nY + pFontInstance->mxFontMetric->GetStrikeoutOffset();
break;
case STRIKEOUT_BOLD:
nLineHeight = pFontInstance->mxFontMetric->GetBoldStrikeoutSize();
nLinePos = nY + pFontInstance->mxFontMetric->GetBoldStrikeoutOffset();
break;
case STRIKEOUT_DOUBLE:
nLineHeight = pFontInstance->mxFontMetric->GetDoubleStrikeoutSize();
nLinePos = nY + pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset1();
nLinePos2 = nY + pFontInstance->mxFontMetric->GetDoubleStrikeoutOffset2();
break;
default:
break;
}
if ( !nLineHeight )
return;
if ( mbLineColor || mbInitLineColor )
{
mpGraphics->SetLineColor();
mbInitLineColor = true;
}
mpGraphics->SetFillColor( aColor );
mbInitFillColor = true;
const tools::Long& nLeft = nDistX;
switch ( eStrikeout )
{
case STRIKEOUT_SINGLE:
case STRIKEOUT_BOLD:
ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight );
break;
case STRIKEOUT_DOUBLE:
ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos, nWidth, nLineHeight );
ImplDrawTextRect( nBaseX, nBaseY, nLeft, nLinePos2, nWidth, nLineHeight );
break;
default:
break;
}
}
void OutputDevice::ImplDrawStrikeoutChar( tools::Long nBaseX, tools::Long nBaseY,
tools::Long nDistX, tools::Long nDistY, tools::Long nWidth,
FontStrikeout eStrikeout,
Color aColor )
{
// See qadevOOo/testdocs/StrikeThrough.odt for examples if you need
// to tweak this
if (!nWidth)
return;
// prepare string for strikeout measurement
const char cStrikeoutChar = eStrikeout == STRIKEOUT_SLASH ? '/' : 'X';
static const int nTestStrLen = 4;
static const int nMaxStrikeStrLen = 2048;
sal_Unicode aChars[nMaxStrikeStrLen+1]; // +1 for valgrind...
for( int i = 0; i < nTestStrLen; ++i)
aChars[i] = cStrikeoutChar;
const OUString aStrikeoutTest(aChars, nTestStrLen);
// calculate approximation of strikeout atom size
tools::Long nStrikeoutWidth = 0;
std::unique_ptr<SalLayout> pLayout = ImplLayout( aStrikeoutTest, 0, nTestStrLen );
if( pLayout )
{
nStrikeoutWidth = pLayout->GetTextWidth() / nTestStrLen;
}
if( nStrikeoutWidth <= 0 ) // sanity check
return;
int nStrikeStrLen = (nWidth+(nStrikeoutWidth-1)) / nStrikeoutWidth;
if( nStrikeStrLen > nMaxStrikeStrLen )
nStrikeStrLen = nMaxStrikeStrLen;
else if (nStrikeStrLen < 0)
nStrikeStrLen = 0;
// build the strikeout string
for( int i = nTestStrLen; i < nStrikeStrLen; ++i)
aChars[i] = cStrikeoutChar;
const OUString aStrikeoutText(aChars, nStrikeStrLen);
if( mpFontInstance->mnOrientation )
{
Point aOriginPt(0, 0);
aOriginPt.RotateAround( nDistX, nDistY, mpFontInstance->mnOrientation );
}
nBaseX += nDistX;
nBaseY += nDistY;
// strikeout text has to be left aligned
vcl::text::ComplexTextLayoutFlags nOrigTLM = mnTextLayoutMode;
mnTextLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiStrong;
pLayout = ImplLayout( aStrikeoutText, 0, aStrikeoutText.getLength() );
mnTextLayoutMode = nOrigTLM;
if( !pLayout )
return;
// draw the strikeout text
const Color aOldColor = GetTextColor();
SetTextColor( aColor );
ImplInitTextColor();
pLayout->DrawBase() = basegfx::B2DPoint(nBaseX + mnTextOffX, nBaseY + mnTextOffY);
tools::Rectangle aPixelRect;
aPixelRect.SetLeft( nBaseX+mnTextOffX );
aPixelRect.SetRight( aPixelRect.Left()+nWidth );
aPixelRect.SetBottom( nBaseY+mpFontInstance->mxFontMetric->GetDescent() );
aPixelRect.SetTop( nBaseY-mpFontInstance->mxFontMetric->GetAscent() );
if (mpFontInstance->mnOrientation)
{
tools::Polygon aPoly( aPixelRect );
aPoly.Rotate( Point(nBaseX+mnTextOffX, nBaseY+mnTextOffY), mpFontInstance->mnOrientation);
aPixelRect = aPoly.GetBoundRect();
}
Push( vcl::PushFlags::CLIPREGION );
IntersectClipRegion( PixelToLogic(aPixelRect) );
if( mbInitClipRegion )
InitClipRegion();
pLayout->DrawText( *mpGraphics );
Pop();
SetTextColor( aOldColor );
ImplInitTextColor();
}
void OutputDevice::ImplDrawTextLine( tools::Long nX, tools::Long nY,
tools::Long nDistX, double nWidth,
double nLayoutWidth,
FontStrikeout eStrikeout,
FontLineStyle eUnderline,
FontLineStyle eOverline,
bool bUnderlineAbove )
{
if ( !nWidth )
return;
Color aStrikeoutColor = GetTextColor();
Color aUnderlineColor = GetTextLineColor();
Color aOverlineColor = GetOverlineColor();
bool bStrikeoutDone = false;
bool bUnderlineDone = false;
bool bOverlineDone = false;
if ( IsRTLEnabled() )
{
tools::Long nXAdd = nWidth - nDistX;
if( mpFontInstance->mnOrientation )
nXAdd = basegfx::fround<tools::Long>( nXAdd * cos( toRadians(mpFontInstance->mnOrientation) ) );
nX += nXAdd - 1;
}
if ( !IsTextLineColor() )
aUnderlineColor = GetTextColor();
if ( !IsOverlineColor() )
aOverlineColor = GetTextColor();
if ( (eUnderline == LINESTYLE_SMALLWAVE) ||
(eUnderline == LINESTYLE_WAVE) ||
(eUnderline == LINESTYLE_DOUBLEWAVE) ||
(eUnderline == LINESTYLE_BOLDWAVE) )
{
ImplDrawWaveTextLine( nX, nY, nDistX, 0, nWidth, nLayoutWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
bUnderlineDone = true;
}
if ( (eOverline == LINESTYLE_SMALLWAVE) ||
(eOverline == LINESTYLE_WAVE) ||
(eOverline == LINESTYLE_DOUBLEWAVE) ||
(eOverline == LINESTYLE_BOLDWAVE) )
{
ImplDrawWaveTextLine( nX, nY, nDistX, 0, nWidth, nLayoutWidth, eOverline, aOverlineColor, true );
bOverlineDone = true;
}
if ( (eStrikeout == STRIKEOUT_SLASH) ||
(eStrikeout == STRIKEOUT_X) )
{
ImplDrawStrikeoutChar( nX, nY, nDistX, 0, nWidth, eStrikeout, aStrikeoutColor );
bStrikeoutDone = true;
}
if ( !bUnderlineDone )
ImplDrawStraightTextLine( nX, nY, nDistX, 0, nWidth, eUnderline, aUnderlineColor, bUnderlineAbove );
if ( !bOverlineDone )
ImplDrawStraightTextLine( nX, nY, nDistX, 0, nWidth, eOverline, aOverlineColor, true );
if ( !bStrikeoutDone )
ImplDrawStrikeoutLine( nX, nY, nDistX, 0, nWidth, eStrikeout, aStrikeoutColor );
}
void OutputDevice::ImplDrawTextLines( SalLayout& rSalLayout, FontStrikeout eStrikeout,
FontLineStyle eUnderline, FontLineStyle eOverline,
bool bWordLine, bool bUnderlineAbove )
{
double nLayoutWidth = rSalLayout.GetTextWidth();
if( bWordLine )
{
// draw everything relative to the layout base point
const basegfx::B2DPoint aStartPt = rSalLayout.DrawBase();
// calculate distance of each word from the base point
basegfx::B2DPoint aPos;
double nDist = 0;
double nWidth = 0;
const GlyphItem* pGlyph;
int nStart = 0;
while (rSalLayout.GetNextGlyph(&pGlyph, aPos, nStart))
{
// calculate the boundaries of each word
if (!pGlyph->IsSpacing())
{
if( !nWidth )
{
// get the distance to the base point (as projected to baseline)
nDist = aPos.getX() - aStartPt.getX();
if( mpFontInstance->mnOrientation )
{
const double nDY = aPos.getY() - aStartPt.getY();
const double fRad = toRadians(mpFontInstance->mnOrientation);
nDist = basegfx::fround<tools::Long>(nDist * cos(fRad) - nDY * sin(fRad));
}
}
// update the length of the textline
nWidth += pGlyph->newWidth();
}
else if( nWidth > 0 )
{
// draw the textline for each word
ImplDrawTextLine( aStartPt.getX(), aStartPt.getY(), nDist, nWidth, nLayoutWidth,
eStrikeout, eUnderline, eOverline, bUnderlineAbove );
nWidth = 0;
}
}
// draw textline for the last word
if( nWidth > 0 )
{
ImplDrawTextLine( aStartPt.getX(), aStartPt.getY(), nDist, nWidth, nLayoutWidth,
eStrikeout, eUnderline, eOverline, bUnderlineAbove );
}
}
else
{
basegfx::B2DPoint aStartPt = rSalLayout.GetDrawPosition();
ImplDrawTextLine( aStartPt.getX(), aStartPt.getY(), 0,
nLayoutWidth, nLayoutWidth,
eStrikeout, eUnderline, eOverline, bUnderlineAbove );
}
}
void OutputDevice::ImplDrawMnemonicLine( tools::Long nX, tools::Long nY, tools::Long nWidth )
{
tools::Long nBaseX = nX;
if( /*HasMirroredGraphics() &&*/ IsRTLEnabled() )
{
// revert the hack that will be done later in ImplDrawTextLine
nX = nBaseX - nWidth - (nX - nBaseX - 1);
}
ImplDrawTextLine( nX, nY, 0, nWidth, nWidth, STRIKEOUT_NONE, LINESTYLE_SINGLE, LINESTYLE_NONE, false );
}
void OutputDevice::SetTextLineColor()
{
if ( mpMetaFile )
mpMetaFile->AddAction( new MetaTextLineColorAction( Color(), false ) );
maTextLineColor = COL_TRANSPARENT;
}
void OutputDevice::SetTextLineColor( const Color& rColor )
{
Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
if ( mpMetaFile )
mpMetaFile->AddAction( new MetaTextLineColorAction( aColor, true ) );
maTextLineColor = aColor;
}
void OutputDevice::SetOverlineColor()
{
if ( mpMetaFile )
mpMetaFile->AddAction( new MetaOverlineColorAction( Color(), false ) );
maOverlineColor = COL_TRANSPARENT;
}
void OutputDevice::SetOverlineColor( const Color& rColor )
{
Color aColor(vcl::drawmode::GetTextColor(rColor, GetDrawMode(), GetSettings().GetStyleSettings()));
if ( mpMetaFile )
mpMetaFile->AddAction( new MetaOverlineColorAction( aColor, true ) );
maOverlineColor = aColor;
}
void OutputDevice::DrawTextLine( const Point& rPos, tools::Long nWidth,
FontStrikeout eStrikeout,
FontLineStyle eUnderline,
FontLineStyle eOverline,
bool bUnderlineAbove )
{
assert(!is_double_buffered_window());
if ( mpMetaFile )
mpMetaFile->AddAction( new MetaTextLineAction( rPos, nWidth, eStrikeout, eUnderline, eOverline ) );
if ( ((eUnderline == LINESTYLE_NONE) || (eUnderline == LINESTYLE_DONTKNOW)) &&
((eOverline == LINESTYLE_NONE) || (eOverline == LINESTYLE_DONTKNOW)) &&
((eStrikeout == STRIKEOUT_NONE) || (eStrikeout == STRIKEOUT_DONTKNOW)) )
{
return;
}
if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
return;
if( mbInitClipRegion )
InitClipRegion();
if( mbOutputClipped )
return;
// initialize font if needed to get text offsets
// TODO: only needed for mnTextOff!=(0,0)
if (!InitFont())
return;
Point aPos = ImplLogicToDevicePixel( rPos );
double fWidth = ImplLogicWidthToDeviceSubPixel(nWidth);
aPos += Point( mnTextOffX, mnTextOffY );
ImplDrawTextLine( aPos.X(), aPos.X(), 0, fWidth, fWidth, eStrikeout, eUnderline, eOverline, bUnderlineAbove );
}
void OutputDevice::DrawWaveLine(const Point& rStartPos, const Point& rEndPos, tools::Long nLineWidth, tools::Long nWaveHeight)
{
assert(!is_double_buffered_window());
if ( !IsDeviceOutputNecessary() || ImplIsRecordLayout() )
return;
// we need a graphics
if( !mpGraphics && !AcquireGraphics() )
return;
assert(mpGraphics);
if ( mbInitClipRegion )
InitClipRegion();
if ( mbOutputClipped )
return;
if (!InitFont())
return;
Point aStartPt = ImplLogicToDevicePixel(rStartPos);
Point aEndPt = ImplLogicToDevicePixel(rEndPos);
tools::Long nStartX = aStartPt.X();
tools::Long nStartY = aStartPt.Y();
tools::Long nEndX = aEndPt.X();
tools::Long nEndY = aEndPt.Y();
double fOrientation = 0.0;
// handle rotation
if (nStartY != nEndY || nStartX > nEndX)
{
fOrientation = basegfx::rad2deg(std::atan2(nStartY - nEndY, nEndX - nStartX));
// un-rotate the end point
aStartPt.RotateAround(nEndX, nEndY, Degree10(static_cast<sal_Int16>(-fOrientation * 10.0)));
}
// Handle HiDPI
float fScaleFactor = GetDPIScaleFactor();
if (fScaleFactor > 1.0f)
{
nWaveHeight *= fScaleFactor;
nStartY += fScaleFactor - 1; // Shift down additional pixel(s) to create more visual separation.
// odd heights look better than even
if (nWaveHeight % 2 == 0)
{
nWaveHeight--;
}
}
// #109280# make sure the waveline does not exceed the descent to avoid paint problems
LogicalFontInstance* pFontInstance = mpFontInstance.get();
if (nWaveHeight > pFontInstance->mxFontMetric->GetWavelineUnderlineSize()
// tdf#153223 polyline with lineheight >0 not drawn when skia is off
#ifdef MACOSX
|| !SkiaHelper::isVCLSkiaEnabled()
#endif
)
{
nWaveHeight = pFontInstance->mxFontMetric->GetWavelineUnderlineSize();
// tdf#124848 hairline
nLineWidth = 0;
}
if ( fOrientation == 0.0 )
{
static tools::DeleteOnDeinit< WavyLineCache > snLineCache {};
if ( !snLineCache.get() )
return;
WavyLineCache& rLineCache = *snLineCache.get();
BitmapEx aWavylinebmp;
if ( !rLineCache.find( GetLineColor(), nLineWidth, nWaveHeight, nEndX - nStartX, aWavylinebmp ) )
{
size_t nWordLength = nEndX - nStartX;
// start with something big to avoid updating it frequently
nWordLength = nWordLength < 1024 ? 1024 : nWordLength;
ScopedVclPtrInstance< VirtualDevice > pVirtDev( *this, DeviceFormat::WITH_ALPHA );
pVirtDev->SetOutputSizePixel( Size( nWordLength, nWaveHeight * 2 ), false );
pVirtDev->SetLineColor( GetLineColor() );
pVirtDev->SetBackground( Wallpaper( COL_TRANSPARENT ) );
pVirtDev->Erase();
pVirtDev->SetAntialiasing( AntialiasingFlags::Enable );
pVirtDev->ImplDrawWaveLineBezier( 0, 0, nWordLength, 0, nWaveHeight, fOrientation, nLineWidth );
BitmapEx aBitmapEx(pVirtDev->GetBitmapEx(Point(0, 0), pVirtDev->GetOutputSize()));
// Ideally we don't need this block, but in the split rgb surface + separate alpha surface
// with Antialiasing enabled and the svp/cairo backend we get both surfaces antialiased
// so their combination of aliases merge to overly wash-out the color. Hack it by taking just
// the alpha surface and use it to blend the original solid line color
Bitmap aSolidColor(aBitmapEx.GetBitmap());
aSolidColor.Erase(GetLineColor());
aBitmapEx = BitmapEx(aSolidColor, aBitmapEx.GetAlphaMask());
rLineCache.insert( aBitmapEx, GetLineColor(), nLineWidth, nWaveHeight, nWordLength, aWavylinebmp );
}
if ( aWavylinebmp.ImplGetBitmapSalBitmap() != nullptr )
{
Size _size( nEndX - nStartX, aWavylinebmp.GetSizePixel().Height() );
DrawBitmapEx(Point( rStartPos.X(), rStartPos.Y() ), PixelToLogic( _size ), Point(), _size, aWavylinebmp);
}
return;
}
ImplDrawWaveLineBezier( nStartX, nStartY, nEndX, nEndY, nWaveHeight, fOrientation, nLineWidth );
}
void OutputDevice::ImplDrawWaveLineBezier(tools::Long nStartX, tools::Long nStartY, tools::Long nEndX, tools::Long nEndY, tools::Long nWaveHeight, double fOrientation, tools::Long nLineWidth)
{
// we need a graphics
if( !mpGraphics && !AcquireGraphics() )
return;
assert(mpGraphics);
if ( mbInitClipRegion )
InitClipRegion();
if ( mbOutputClipped )
return;
if (!InitFont())
return;
const basegfx::B2DRectangle aWaveLineRectangle(nStartX, nStartY, nEndX, nEndY + nWaveHeight);
const basegfx::B2DPolygon aWaveLinePolygon = basegfx::createWaveLinePolygon(aWaveLineRectangle);
const basegfx::B2DHomMatrix aRotationMatrix = basegfx::utils::createRotateAroundPoint(nStartX, nStartY, basegfx::deg2rad(-fOrientation));
const bool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
mpGraphics->SetLineColor(GetLineColor());
mpGraphics->DrawPolyLine(
aRotationMatrix,
aWaveLinePolygon,
0.0,
nLineWidth,
nullptr, // MM01
basegfx::B2DLineJoin::NONE,
css::drawing::LineCap_BUTT,
basegfx::deg2rad(15.0),
bPixelSnapHairline,
*this);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */