diff options
Diffstat (limited to 'cppcanvas/source/mtfrenderer/mtftools.cxx')
-rw-r--r-- | cppcanvas/source/mtfrenderer/mtftools.cxx | 671 |
1 files changed, 671 insertions, 0 deletions
diff --git a/cppcanvas/source/mtfrenderer/mtftools.cxx b/cppcanvas/source/mtfrenderer/mtftools.cxx new file mode 100644 index 0000000000..94a83da70c --- /dev/null +++ b/cppcanvas/source/mtfrenderer/mtftools.cxx @@ -0,0 +1,671 @@ +/* -*- 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 <comphelper/diagnose_ex.hxx> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <canvas/canvastools.hxx> +#include <rtl/math.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/virdev.hxx> +#include <vcl/metric.hxx> +#include "mtftools.hxx" +#include <outdevstate.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + + +using namespace ::com::sun::star; + +namespace cppcanvas::tools +{ + void initRenderState( rendering::RenderState& renderState, + const ::cppcanvas::internal::OutDevState& outdevState ) + { + ::canvas::tools::initRenderState( renderState ); + ::canvas::tools::setRenderStateTransform( renderState, + outdevState.transform ); + renderState.Clip = outdevState.xClipPoly; + } + + ::Size getBaselineOffset( const ::cppcanvas::internal::OutDevState& outdevState, + const VirtualDevice& rVDev ) + { + const ::FontMetric& aMetric = rVDev.GetFontMetric(); + + // calc offset for text output, the XCanvas always renders + // baseline offset. + switch( outdevState.textReferencePoint ) + { + case ALIGN_TOP: + return ::Size( 0, + aMetric.GetInternalLeading() + aMetric.GetAscent() ); + + case ALIGN_BASELINE: + return ::Size( 0, 0 ); + + case ALIGN_BOTTOM: + return ::Size( 0, + -aMetric.GetDescent() ); + + default: + throw css::uno::RuntimeException( + "tools::getBaselineOffset(): Unexpected TextAlign value" ); + } + } + + ::basegfx::B2DHomMatrix& calcLogic2PixelLinearTransform( ::basegfx::B2DHomMatrix& o_rMatrix, + const VirtualDevice& rVDev ) + { + // select size value in the middle of the available range, + // to have headroom both when map mode scales up, and when + // it scales down. + const ::Size aSizeLogic( 0x00010000L, + 0x00010000L ); + + const ::Size aSizePixel( rVDev.LogicToPixel( aSizeLogic ) ); + + o_rMatrix = basegfx::utils::createScaleB2DHomMatrix( + aSizePixel.Width() / static_cast<double>(aSizeLogic.Width()), + aSizePixel.Height() / static_cast<double>(aSizeLogic.Height()) ); + + return o_rMatrix; + } + + ::basegfx::B2DHomMatrix& calcLogic2PixelAffineTransform( ::basegfx::B2DHomMatrix& o_rMatrix, + const VirtualDevice& rVDev ) + { + // retrieves scale + calcLogic2PixelLinearTransform(o_rMatrix, rVDev); + + // translate according to curr map mode/pref map mode offset + const ::Point aEmptyPoint; + const ::Point& rTranslatedPoint( + rVDev.LogicToPixel( aEmptyPoint )); + + o_rMatrix.translate(rTranslatedPoint.X(), + rTranslatedPoint.Y()); + + return o_rMatrix; + } + + bool modifyClip( rendering::RenderState& o_rRenderState, + const struct ::cppcanvas::internal::OutDevState& rOutdevState, + const CanvasSharedPtr& rCanvas, + const ::basegfx::B2DPoint& rOffset, + const ::basegfx::B2DVector* pScaling, + const double* pRotation ) + { + const bool bOffsetting( !rOffset.equalZero() ); + const bool bScaling( pScaling && + !rtl::math::approxEqual(pScaling->getX(), 1.0) && + !rtl::math::approxEqual(pScaling->getY(), 1.0) ); + const bool bRotation( pRotation && + *pRotation != 0.0 ); + + if( !bOffsetting && !bScaling && !bRotation ) + return false; // nothing to do + + if( rOutdevState.clip.count() ) + { + // general polygon case + + ::basegfx::B2DPolyPolygon aLocalClip( rOutdevState.clip ); + ::basegfx::B2DHomMatrix aTransform; + + if( bOffsetting ) + aTransform.translate( -rOffset.getX(), + -rOffset.getY() ); + if( bScaling ) + aTransform.scale( 1.0/pScaling->getX(), 1.0/pScaling->getY() ); + + if( bRotation ) + aTransform.rotate( - *pRotation ); + + aLocalClip.transform( aTransform ); + + o_rRenderState.Clip = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( + rCanvas->getUNOCanvas()->getDevice(), + aLocalClip ); + + return true; + } + else if( !rOutdevState.clipRect.IsEmpty() ) + { + // simple rect case + + const ::tools::Rectangle aLocalClipRect( rOutdevState.clipRect ); + + if( bRotation ) + { + // rotation involved - convert to polygon first, + // then transform that + ::basegfx::B2DPolygon aLocalClip( + ::basegfx::utils::createPolygonFromRect( + vcl::unotools::b2DRectangleFromRectangle(aLocalClipRect) ) ); + ::basegfx::B2DHomMatrix aTransform; + + if( bOffsetting ) + aTransform.translate( -rOffset.getX(), + -rOffset.getY() ); + if( bScaling ) + aTransform.scale( 1.0/pScaling->getX(), 1.0/pScaling->getY() ); + + aTransform.rotate( - *pRotation ); + + aLocalClip.transform( aTransform ); + + o_rRenderState.Clip = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( + rCanvas->getUNOCanvas()->getDevice(), + ::basegfx::B2DPolyPolygon( aLocalClip ) ); + } + else if( bScaling ) + { + // scale and offset - do it on the fly, have to + // convert to float anyway. + o_rRenderState.Clip = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( + rCanvas->getUNOCanvas()->getDevice(), + ::basegfx::B2DPolyPolygon( + ::basegfx::utils::createPolygonFromRect( + ::basegfx::B2DRectangle( + (aLocalClipRect.Left() - rOffset.getX())/pScaling->getX(), + (aLocalClipRect.Top() - rOffset.getY())/pScaling->getY(), + (aLocalClipRect.Right() - rOffset.getX())/pScaling->getX(), + (aLocalClipRect.Bottom() - rOffset.getY())/pScaling->getY() ) ) ) ); + } + else + { + // offset only - do it on the fly, have to convert + // to float anyway. + o_rRenderState.Clip = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( + rCanvas->getUNOCanvas()->getDevice(), + ::basegfx::B2DPolyPolygon( + ::basegfx::utils::createPolygonFromRect( + ::basegfx::B2DRectangle( aLocalClipRect.Left() - rOffset.getX(), + aLocalClipRect.Top() - rOffset.getY(), + aLocalClipRect.Right() - rOffset.getX(), + aLocalClipRect.Bottom() - rOffset.getY() ) ) ) ); + } + + return true; + } + + // empty clip, nothing to do + return false; + } + + // create overline/underline/strikeout line info struct + TextLineInfo createTextLineInfo( const ::VirtualDevice& rVDev, + const ::cppcanvas::internal::OutDevState& rState ) + { + const bool bOldMode( rVDev.IsMapModeEnabled() ); + + // #i68512# Force metric regeneration with mapmode enabled + // (prolly OutDev bug) + rVDev.GetFontMetric(); + + // will restore map mode below + const_cast< ::VirtualDevice& >(rVDev).EnableMapMode( false ); + + const ::FontMetric aMetric = rVDev.GetFontMetric(); + + TextLineInfo aTextInfo( + (aMetric.GetDescent() + 2) / 4.0, + ((aMetric.GetInternalLeading() + 1.5) / 3.0), + (aMetric.GetInternalLeading() / 2.0) - aMetric.GetAscent(), + aMetric.GetDescent() / 2.0, + (aMetric.GetInternalLeading() - aMetric.GetAscent()) / 3.0, + rState.textOverlineStyle, + rState.textUnderlineStyle, + rState.textStrikeoutStyle ); + + const_cast< ::VirtualDevice& >(rVDev).EnableMapMode( bOldMode ); + + return aTextInfo; + } + + namespace + { + void appendWaveline( ::basegfx::B2DPolyPolygon& o_rPoly, + const ::basegfx::B2DPoint& rStartPos, + const double nStartOffset, + const double nWidth, + const double nHeight, + sal_Int8 nLineStyle) + { + const double x(rStartPos.getX()); + const double y(rStartPos.getY() + nStartOffset + nHeight); + double nWaveWidth = nHeight * 10.6 * 0.25; + // Offset for the double line. + double nOffset = 0.0; + + if (nLineStyle == LINESTYLE_DOUBLEWAVE) + nOffset = -nHeight * 0.5; + else + nWaveWidth *= 2.0; + + basegfx::B2DPolygon aLine; + aLine.append(basegfx::B2DPoint(x, y + nOffset)); + aLine.append(basegfx::B2DPoint(x + nWidth, y + nOffset)); + + o_rPoly.append(::basegfx::utils::createWaveline(aLine, nWaveWidth, nWaveWidth * 0.5)); + + if (nLineStyle == LINESTYLE_DOUBLEWAVE) + { + nOffset = nHeight * 1.2; + + basegfx::B2DPolygon aLine2; + aLine2.append(basegfx::B2DPoint(x, y + nOffset)); + aLine2.append(basegfx::B2DPoint(x + nWidth, y + nOffset)); + o_rPoly.append(::basegfx::utils::createWaveline(aLine2, nWaveWidth, nWaveWidth * 0.5)); + } + } + + void appendRect( ::basegfx::B2DPolyPolygon& o_rPoly, + const ::basegfx::B2DPoint& rStartPos, + const double nX1, + const double nY1, + const double nX2, + const double nY2 ) + { + const double x( rStartPos.getX() ); + const double y( rStartPos.getY() ); + + o_rPoly.append( + ::basegfx::utils::createPolygonFromRect( + ::basegfx::B2DRectangle( x + nX1, y + nY1, x + nX2, y + nY2 ) ) ); + } + + void appendRect( ::basegfx::B2DPolyPolygon& o_rPoly, + const double nX1, + const double nY1, + const double nX2, + const double nY2 ) + { + o_rPoly.append( + ::basegfx::utils::createPolygonFromRect( + ::basegfx::B2DRectangle( nX1, nY1, nX2, nY2 ) ) ); + } + + bool appendDashes( ::basegfx::B2DPolyPolygon& o_rPoly, + const double nX, + double nY, + const double nLineWidth, + double nLineHeight, + sal_Int8 nLineStyle, + bool bIsOverline) + { + static const int aDottedArray[] = { 1, 1, 0}; // DOTTED LINE + static const int aDotDashArray[] = { 1, 1, 4, 1, 0}; // DASHDOT + static const int aDashDotDotArray[] = { 1, 1, 1, 1, 4, 1, 0}; // DASHDOTDOT + static const int aDashedArray[] = { 5, 2, 0}; // DASHED LINE + static const int aLongDashArray[] = { 7, 2, 0}; // LONGDASH + const int *pArray = nullptr; + bool bIsBold = false; + + switch(nLineStyle) + { + case LINESTYLE_BOLDDOTTED: + bIsBold = true; + [[fallthrough]]; + case LINESTYLE_DOTTED: + pArray = aDottedArray; + break; + + case LINESTYLE_BOLDDASH: + bIsBold = true; + [[fallthrough]]; + case LINESTYLE_DASH: + pArray = aDashedArray; + break; + + case LINESTYLE_BOLDLONGDASH: + bIsBold = true; + [[fallthrough]]; + case LINESTYLE_LONGDASH: + pArray = aLongDashArray; + break; + + case LINESTYLE_BOLDDASHDOT: + bIsBold = true; + [[fallthrough]]; + case LINESTYLE_DASHDOT: + pArray = aDotDashArray; + break; + case LINESTYLE_BOLDDASHDOTDOT: + bIsBold = true; + [[fallthrough]]; + case LINESTYLE_DASHDOTDOT: + pArray = aDashDotDotArray; + break; + } + + if (!pArray) + return false; + + if (bIsBold) + { + if (bIsOverline) + nY -= nLineHeight; + + nLineHeight *= 2; + } + + const double nEnd = nX + nLineWidth; + sal_Int32 nIndex = 0; + bool bAppend = true; + double nX1 = nX; + + while(nX1 < nEnd) + { + if (pArray[nIndex] == 0) + nIndex = 0; + + const double nX2 = std::min(nEnd, nX1 + pArray[nIndex] * nLineHeight); + + if (bAppend) + appendRect(o_rPoly, nX1, nY, nX2, nY + nLineHeight); + + nX1 = nX2; + + ++nIndex; + + bAppend = !bAppend; + } + return true; + } + + // create line actions for text such as underline and + // strikeout + void createOverlinePolyPolygon(::basegfx::B2DPolyPolygon& rTextLinesPolyPoly, + const ::basegfx::B2DPoint& rStartPos, + const double& rLineWidth, + const TextLineInfo& rTextLineInfo) + { + switch( rTextLineInfo.mnOverlineStyle ) + { + case LINESTYLE_NONE: // nothing to do + case LINESTYLE_DONTKNOW: + break; + + case LINESTYLE_DOUBLEWAVE: + case LINESTYLE_SMALLWAVE: + case LINESTYLE_BOLDWAVE: + case LINESTYLE_WAVE: + appendWaveline( + rTextLinesPolyPoly, + rStartPos, + rTextLineInfo.mnOverlineOffset, + rLineWidth, + rTextLineInfo.mnOverlineHeight, + rTextLineInfo.mnOverlineStyle); + + break; + case LINESTYLE_SINGLE: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnOverlineOffset, + rLineWidth, + rTextLineInfo.mnOverlineOffset + rTextLineInfo.mnOverlineHeight ); + break; + case LINESTYLE_BOLD: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnOverlineOffset - rTextLineInfo.mnOverlineHeight, + rLineWidth, + rTextLineInfo.mnOverlineOffset + rTextLineInfo.mnOverlineHeight ); + break; + + case LINESTYLE_DOUBLE: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnOverlineOffset - rTextLineInfo.mnOverlineHeight * 2.0 , + rLineWidth, + rTextLineInfo.mnOverlineOffset - rTextLineInfo.mnOverlineHeight ); + + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnOverlineOffset + rTextLineInfo.mnOverlineHeight, + rLineWidth, + rTextLineInfo.mnOverlineOffset + rTextLineInfo.mnOverlineHeight * 2.0 ); + break; + + default: + if (!appendDashes( + rTextLinesPolyPoly, + rStartPos.getX(), + rStartPos.getY() + rTextLineInfo.mnOverlineOffset, + rLineWidth, + rTextLineInfo.mnOverlineHeight, + rTextLineInfo.mnOverlineStyle, + true)) + { + ENSURE_OR_THROW( false, + "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected overline case" ); + } + } + } + + void createUnderlinePolyPolygon(::basegfx::B2DPolyPolygon& rTextLinesPolyPoly, + const ::basegfx::B2DPoint& rStartPos, + const double& rLineWidth, + const TextLineInfo& rTextLineInfo ) + { + + switch( rTextLineInfo.mnUnderlineStyle ) + { + case LINESTYLE_NONE: // nothing to do + case LINESTYLE_DONTKNOW: + break; + + case LINESTYLE_DOUBLEWAVE: + case LINESTYLE_SMALLWAVE: + case LINESTYLE_BOLDWAVE: + case LINESTYLE_WAVE: + appendWaveline( + rTextLinesPolyPoly, + rStartPos, + rTextLineInfo.mnUnderlineOffset, + rLineWidth, + rTextLineInfo.mnLineHeight, + rTextLineInfo.mnUnderlineStyle); + break; + case LINESTYLE_SINGLE: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnUnderlineOffset, + rLineWidth, + rTextLineInfo.mnUnderlineOffset + rTextLineInfo.mnLineHeight ); + break; + + case LINESTYLE_BOLD: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnUnderlineOffset, + rLineWidth, + rTextLineInfo.mnUnderlineOffset + 2*rTextLineInfo.mnLineHeight ); + break; + + case LINESTYLE_DOUBLE: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnUnderlineOffset - rTextLineInfo.mnLineHeight, + rLineWidth, + rTextLineInfo.mnUnderlineOffset ); + + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnUnderlineOffset + 2*rTextLineInfo.mnLineHeight, + rLineWidth, + rTextLineInfo.mnUnderlineOffset + 3*rTextLineInfo.mnLineHeight ); + break; + + default: + if (!appendDashes( + rTextLinesPolyPoly, + rStartPos.getX(), + rStartPos.getY() + rTextLineInfo.mnUnderlineOffset, + rLineWidth, + rTextLineInfo.mnLineHeight, + rTextLineInfo.mnUnderlineStyle, + false)) + { + ENSURE_OR_THROW( false, + "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected underline case" ); + } + } + } + + void createStrikeoutPolyPolygon(::basegfx::B2DPolyPolygon& rTextLinesPolyPoly, + const ::basegfx::B2DPoint& rStartPos, + const double& rLineWidth, + const TextLineInfo& rTextLineInfo) + { + switch( rTextLineInfo.mnStrikeoutStyle ) + { + case STRIKEOUT_NONE: // nothing to do + case STRIKEOUT_DONTKNOW: + break; + + case STRIKEOUT_SLASH: // TODO(Q1): we should handle this in the text layer + case STRIKEOUT_X: + break; + + case STRIKEOUT_SINGLE: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnStrikeoutOffset, + rLineWidth, + rTextLineInfo.mnStrikeoutOffset + rTextLineInfo.mnLineHeight ); + break; + + case STRIKEOUT_BOLD: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnStrikeoutOffset, + rLineWidth, + rTextLineInfo.mnStrikeoutOffset + 2*rTextLineInfo.mnLineHeight ); + break; + + case STRIKEOUT_DOUBLE: + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnStrikeoutOffset - rTextLineInfo.mnLineHeight, + rLineWidth, + rTextLineInfo.mnStrikeoutOffset ); + + appendRect( + rTextLinesPolyPoly, + rStartPos, + 0, + rTextLineInfo.mnStrikeoutOffset + 2*rTextLineInfo.mnLineHeight, + rLineWidth, + rTextLineInfo.mnStrikeoutOffset + 3*rTextLineInfo.mnLineHeight ); + break; + + default: + ENSURE_OR_THROW( false, + "::cppcanvas::internal::createTextLinesPolyPolygon(): Unexpected strikeout case" ); + } + } + } + + ::basegfx::B2DPolyPolygon createTextLinesPolyPolygon( const ::basegfx::B2DPoint& rStartPos, + const double& rLineWidth, + const TextLineInfo& rTextLineInfo ) + { + // fill the polypolygon with all text lines + ::basegfx::B2DPolyPolygon aTextLinesPolyPoly; + + createOverlinePolyPolygon(aTextLinesPolyPoly, rStartPos, rLineWidth, rTextLineInfo); + createUnderlinePolyPolygon(aTextLinesPolyPoly, rStartPos, rLineWidth, rTextLineInfo); + createStrikeoutPolyPolygon(aTextLinesPolyPoly, rStartPos, rLineWidth, rTextLineInfo); + return aTextLinesPolyPoly; + } + + ::basegfx::B2DRange calcDevicePixelBounds( const ::basegfx::B2DRange& rBounds, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ::basegfx::B2DHomMatrix aTransform; + ::canvas::tools::mergeViewAndRenderTransform( aTransform, + viewState, + renderState ); + + ::basegfx::B2DRange aTransformedBounds; + return ::canvas::tools::calcTransformedRectBounds( aTransformedBounds, + rBounds, + aTransform ); + } + + // create line actions for text such as underline and + // strikeout + ::basegfx::B2DPolyPolygon createTextLinesPolyPolygon( const double& rStartOffset, + const double& rLineWidth, + const TextLineInfo& rTextLineInfo ) + { + return createTextLinesPolyPolygon( + ::basegfx::B2DPoint( rStartOffset, + 0.0 ), + rLineWidth, + rTextLineInfo ); + } + + void createTextLinesPolyPolygon( const double& rStartOffset, + const double& rLineWidth, + const TextLineInfo& rTextLineInfo, + ::basegfx::B2DPolyPolygon& rOverlinePolyPoly, + ::basegfx::B2DPolyPolygon& rUnderlinePolyPoly, + ::basegfx::B2DPolyPolygon& rStrikeoutPolyPoly ) + { + ::basegfx::B2DPoint aStartPos(rStartOffset, 0.0); + + createOverlinePolyPolygon(rOverlinePolyPoly, aStartPos, rLineWidth, rTextLineInfo); + createUnderlinePolyPolygon(rUnderlinePolyPoly, aStartPos, rLineWidth, rTextLineInfo); + createStrikeoutPolyPolygon(rStrikeoutPolyPoly, aStartPos, rLineWidth, rTextLineInfo); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |