diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /drawinglayer/source/processor2d | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drawinglayer/source/processor2d')
22 files changed, 11467 insertions, 0 deletions
diff --git a/drawinglayer/source/processor2d/SDPRProcessor2dTools.cxx b/drawinglayer/source/processor2d/SDPRProcessor2dTools.cxx new file mode 100644 index 0000000000..3d738cd09c --- /dev/null +++ b/drawinglayer/source/processor2d/SDPRProcessor2dTools.cxx @@ -0,0 +1,300 @@ +/* -*- 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 <drawinglayer/processor2d/SDPRProcessor2dTools.hxx> +#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/graph.hxx> +#include <basegfx/range/b2drange.hxx> + +#ifdef DBG_UTIL +#include <tools/stream.hxx> +#include <vcl/filter/PngImageWriter.hxx> +#endif + +namespace drawinglayer::processor2d +{ +void setOffsetXYCreatedBitmap( + drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D, + const BitmapEx& rBitmap) +{ + rFillGraphicPrimitive2D.impSetOffsetXYCreatedBitmap(rBitmap); +} + +void takeCareOfOffsetXY( + const drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D, + BitmapEx& rTarget, basegfx::B2DRange& rFillUnitRange) +{ + const attribute::FillGraphicAttribute& rFillGraphicAttribute( + rFillGraphicPrimitive2D.getFillGraphic()); + const bool bOffsetXIsUsed(rFillGraphicAttribute.getOffsetX() > 0.0 + && rFillGraphicAttribute.getOffsetX() < 1.0); + const bool bOffsetYIsUsed(rFillGraphicAttribute.getOffsetY() > 0.0 + && rFillGraphicAttribute.getOffsetY() < 1.0); + + if (bOffsetXIsUsed) + { + if (rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty()) + { + const Size& rSize(rTarget.GetSizePixel()); + const tools::Long w(rSize.Width()); + const tools::Long a(basegfx::fround(w * (1.0 - rFillGraphicAttribute.getOffsetX()))); + + if (0 != a && w != a) + { + const tools::Long h(rSize.Height()); + const tools::Long b(w - a); + BitmapEx aTarget(Size(w, h * 2), rTarget.getPixelFormat()); + + aTarget.SetPrefSize( + Size(rTarget.GetPrefSize().Width(), rTarget.GetPrefSize().Height() * 2)); + const tools::Rectangle aSrcDst(Point(), rSize); + aTarget.CopyPixel(aSrcDst, // Dst + aSrcDst, // Src + rTarget); + const Size aSizeA(b, h); + aTarget.CopyPixel(tools::Rectangle(Point(0, h), aSizeA), // Dst + tools::Rectangle(Point(a, 0), aSizeA), // Src + rTarget); + const Size aSizeB(a, h); + aTarget.CopyPixel(tools::Rectangle(Point(b, h), aSizeB), // Dst + tools::Rectangle(Point(), aSizeB), // Src + rTarget); + + setOffsetXYCreatedBitmap( + const_cast<drawinglayer::primitive2d::FillGraphicPrimitive2D&>( + rFillGraphicPrimitive2D), + aTarget); + } + } + + if (!rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty()) + { + rTarget = rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap(); + rFillUnitRange.expand(basegfx::B2DPoint( + rFillUnitRange.getMinX(), rFillUnitRange.getMaxY() + rFillUnitRange.getHeight())); + } + } + else if (bOffsetYIsUsed) + { + if (rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty()) + { + const Size& rSize(rTarget.GetSizePixel()); + const tools::Long h(rSize.Height()); + const tools::Long a(basegfx::fround(h * (1.0 - rFillGraphicAttribute.getOffsetY()))); + + if (0 != a && h != a) + { + const tools::Long w(rSize.Width()); + const tools::Long b(h - a); + BitmapEx aTarget(Size(w * 2, h), rTarget.getPixelFormat()); + + aTarget.SetPrefSize( + Size(rTarget.GetPrefSize().Width() * 2, rTarget.GetPrefSize().Height())); + const tools::Rectangle aSrcDst(Point(), rSize); + aTarget.CopyPixel(aSrcDst, // Dst + aSrcDst, // Src + rTarget); + const Size aSizeA(w, b); + aTarget.CopyPixel(tools::Rectangle(Point(w, 0), aSizeA), // Dst + tools::Rectangle(Point(0, a), aSizeA), // Src + rTarget); + const Size aSizeB(w, a); + aTarget.CopyPixel(tools::Rectangle(Point(w, b), aSizeB), // Dst + tools::Rectangle(Point(), aSizeB), // Src + rTarget); + + setOffsetXYCreatedBitmap( + const_cast<drawinglayer::primitive2d::FillGraphicPrimitive2D&>( + rFillGraphicPrimitive2D), + aTarget); + } + } + + if (!rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty()) + { + rTarget = rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap(); + rFillUnitRange.expand(basegfx::B2DPoint( + rFillUnitRange.getMaxX() + rFillUnitRange.getWidth(), rFillUnitRange.getMinY())); + } + } +} + +bool prepareBitmapForDirectRender( + const drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D, + const drawinglayer::geometry::ViewInformation2D& rViewInformation2D, BitmapEx& rTarget, + basegfx::B2DRange& rFillUnitRange, double fBigDiscreteArea) +{ + const attribute::FillGraphicAttribute& rFillGraphicAttribute( + rFillGraphicPrimitive2D.getFillGraphic()); + const Graphic& rGraphic(rFillGraphicAttribute.getGraphic()); + + if (rFillGraphicAttribute.isDefault() || rGraphic.IsNone()) + { + // default attributes or GraphicType::NONE, so no fill .-> done + return false; + } + + if (!rFillGraphicAttribute.getTiling()) + { + // If no tiling used, the Graphic will need to be painted just once. This + // is perfectly done using the decomposition, so use it. + // What we want to do here is to optimize tiled paint, for two reasons: + // (a) speed: draw one tile, repeat -> obvious + // (b) correctness: not so obvious, but since in AAed paint the same edge + // of touching polygons both AAed do *not* sum up, but get blended by + // multiplication (0.5 * 0.5 -> 0.25) the connection will stay visible, + // not only with filled polygons, but also with bitmaps + // Signal that paint is needed + return true; + } + + if (rFillUnitRange.isEmpty()) + { + // no fill range definition, no fill, done + return false; + } + + const basegfx::B2DHomMatrix aLocalTransform(rViewInformation2D.getObjectToViewTransformation() + * rFillGraphicPrimitive2D.getTransformation()); + const basegfx::B2DRange& rDiscreteViewPort(rViewInformation2D.getDiscreteViewport()); + + if (!rDiscreteViewPort.isEmpty()) + { + // calculate discrete covered pixel area + basegfx::B2DRange aDiscreteRange(basegfx::B2DRange::getUnitB2DRange()); + aDiscreteRange.transform(aLocalTransform); + + if (!rDiscreteViewPort.overlaps(rDiscreteViewPort)) + { + // we have a Viewport and visible range of geometry is outside -> not visible, done + return false; + } + } + + if (GraphicType::Bitmap == rGraphic.GetType() && rGraphic.IsAnimated()) + { + // Need to prepare specialized AnimatedGraphicPrimitive2D, + // cannot handle here. Signal that paint is needed + return true; + } + + if (GraphicType::Bitmap == rGraphic.GetType() && !rGraphic.getVectorGraphicData()) + { + // bitmap graphic, always handle locally, so get bitmap data independent + // if it'sie or it's discrete display size + rTarget = rGraphic.GetBitmapEx(); + } + else + { + // Vector Graphic Data fill, including metafile: + // We can know about discrete pixel size here, calculate and use it. + // To do so, using Vectors is sufficient to get the lengths. It is + // not necessary to transform the whole target coordinate system. + const basegfx::B2DVector aDiscreteXAxis( + aLocalTransform + * basegfx::B2DVector(rFillUnitRange.getMaxX() - rFillUnitRange.getMinX(), 0.0)); + const basegfx::B2DVector aDiscreteYAxis( + aLocalTransform + * basegfx::B2DVector(0.0, rFillUnitRange.getMaxY() - rFillUnitRange.getMinY())); + + // get and ensure minimal size + const double fDiscreteWidth(std::max(1.0, aDiscreteXAxis.getLength())); + const double fDiscreteHeight(std::max(1.0, aDiscreteYAxis.getLength())); + + // compare with a big visualization size in discrete pixels + const double fTargetDiscreteArea(fDiscreteWidth * fDiscreteHeight); + + if (fTargetDiscreteArea > fBigDiscreteArea) + { + // When the vector data is visualized big it is better to not handle here + // but use decomposition fallback which then will visualize the vector data + // directly -> better quality, acceptable number of tile repeat(s) + // signal that paint is needed + return true; + } + else + { + // If visualized small, the amount of repeated fills gets expensive, so + // in that case use a Bitmap and the Brush technique below. + // The Bitmap may be created here exactly for the needed target size + // (using local D2DBitmapPixelProcessor2D and the vector data), + // but since we have a HW renderer and re-use of system-dependent data + // at BitmapEx is possible, just get the default fallback Bitmap from the + // vector data to continue. Trust the existing converters for now to + // do something with good quality. + rTarget = rGraphic.GetBitmapEx(); + } + } + + if (rTarget.IsEmpty() || rTarget.GetSizePixel().IsEmpty()) + { + // no pixel data, done + return false; + } + + // react if OffsetX/OffsetY of the FillGraphicAttribute is used + takeCareOfOffsetXY(rFillGraphicPrimitive2D, rTarget, rFillUnitRange); + +#ifdef DBG_UTIL + // allow to check bitmap data, e.g. control OffsetX/OffsetY stuff + static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore + if (bDoSaveForVisualControl) + { + static const OUString sDumpPath( + OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH"))); + if (!sDumpPath.isEmpty()) + { + SvFileStream aNew(sDumpPath + "test_getreplacement.png", + StreamMode::WRITE | StreamMode::TRUNC); + vcl::PngImageWriter aPNGWriter(aNew); + aPNGWriter.write(rTarget); + } + } +#endif + + // signal to render it + return true; +} + +void calculateDiscreteVisibleRange( + basegfx::B2DRange& rDiscreteVisibleRange, const basegfx::B2DRange& rContentRange, + const drawinglayer::geometry::ViewInformation2D& rViewInformation2D) +{ + if (rContentRange.isEmpty()) + { + // no content, done + rDiscreteVisibleRange.reset(); + return; + } + + basegfx::B2DRange aDiscreteRange(rContentRange); + aDiscreteRange.transform(rViewInformation2D.getObjectToViewTransformation()); + const basegfx::B2DRange& rDiscreteViewPort(rViewInformation2D.getDiscreteViewport()); + rDiscreteVisibleRange = aDiscreteRange; + + if (!rDiscreteViewPort.isEmpty()) + { + rDiscreteVisibleRange.intersect(rDiscreteViewPort); + } +} +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/baseprocessor2d.cxx b/drawinglayer/source/processor2d/baseprocessor2d.cxx new file mode 100644 index 0000000000..f4aec90383 --- /dev/null +++ b/drawinglayer/source/processor2d/baseprocessor2d.cxx @@ -0,0 +1,74 @@ +/* -*- 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 <drawinglayer/primitive2d/Primitive2DContainer.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <utility> + + +using namespace com::sun::star; + + +namespace drawinglayer::processor2d +{ + void BaseProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& /*rCandidate*/) + { + } + + BaseProcessor2D::BaseProcessor2D(geometry::ViewInformation2D aViewInformation) + : maViewInformation2D(std::move(aViewInformation)) + { + } + + BaseProcessor2D::~BaseProcessor2D() + { + } + + void BaseProcessor2D::process(const primitive2d::BasePrimitive2D& rCandidate) + { + // use the visitor API to avoid the cost of constructing Primitive2DContainers + rCandidate.get2DDecomposition(*this, getViewInformation2D()); + } + + // Primitive2DDecompositionVisitor + void BaseProcessor2D::visit(const primitive2d::Primitive2DReference& rCandidate) + { + processBasePrimitive2D(*rCandidate); + } + void BaseProcessor2D::visit(const primitive2d::Primitive2DContainer& rContainer) + { + process(rContainer); + } + void BaseProcessor2D::visit(primitive2d::Primitive2DContainer&& rCandidate) + { + process(rCandidate); + } + + void BaseProcessor2D::process(const primitive2d::Primitive2DContainer& rSource) + { + for (const primitive2d::Primitive2DReference& rCandidate : rSource) + { + if (rCandidate) + processBasePrimitive2D(*rCandidate); + } + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx new file mode 100644 index 0000000000..89cb21ddcb --- /dev/null +++ b/drawinglayer/source/processor2d/cairopixelprocessor2d.cxx @@ -0,0 +1,975 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * 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/. + */ + +#include <sal/config.h> + +#include <drawinglayer/processor2d/cairopixelprocessor2d.hxx> +#include <sal/log.hxx> +#include <vcl/BitmapTools.hxx> +#include <vcl/cairo.hxx> +#include <vcl/outdev.hxx> +#include <vcl/svapp.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/backgroundcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/baseprimitive2d.hxx> +#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/Tools.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/converters.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/utils/systemdependentdata.hxx> +#include <vcl/BitmapReadAccess.hxx> + +using namespace com::sun::star; + +namespace +{ +basegfx::B2DPoint impPixelSnap(const basegfx::B2DPolygon& rPolygon, + const drawinglayer::geometry::ViewInformation2D& rViewInformation, + sal_uInt32 nIndex) +{ + const sal_uInt32 nCount(rPolygon.count()); + + // get the data + const basegfx::B2ITuple aPrevTuple( + basegfx::fround(rViewInformation.getObjectToViewTransformation() + * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount))); + const basegfx::B2DPoint aCurrPoint(rViewInformation.getObjectToViewTransformation() + * rPolygon.getB2DPoint(nIndex)); + const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint)); + const basegfx::B2ITuple aNextTuple( + basegfx::fround(rViewInformation.getObjectToViewTransformation() + * rPolygon.getB2DPoint((nIndex + 1) % nCount))); + + // get the states + const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX()); + const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX()); + const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY()); + const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY()); + const bool bSnapX(bPrevVertical || bNextVertical); + const bool bSnapY(bPrevHorizontal || bNextHorizontal); + + if (bSnapX || bSnapY) + { + basegfx::B2DPoint aSnappedPoint(bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(), + bSnapY ? aCurrTuple.getY() : aCurrPoint.getY()); + + aSnappedPoint *= rViewInformation.getInverseObjectToViewTransformation(); + + return aSnappedPoint; + } + + return rPolygon.getB2DPoint(nIndex); +} + +void addB2DPolygonToPathGeometry(cairo_t* cr, const basegfx::B2DPolygon& rPolygon, + const drawinglayer::geometry::ViewInformation2D* pViewInformation) +{ + // short circuit if there is nothing to do + const sal_uInt32 nPointCount(rPolygon.count()); + + const bool bHasCurves(rPolygon.areControlPointsUsed()); + const bool bClosePath(rPolygon.isClosed()); + basegfx::B2DPoint aLast; + + for (sal_uInt32 nPointIdx = 0, nPrevIdx = 0;; nPrevIdx = nPointIdx++) + { + int nClosedIdx = nPointIdx; + if (nPointIdx >= nPointCount) + { + // prepare to close last curve segment if needed + if (bClosePath && (nPointIdx == nPointCount)) + { + nClosedIdx = 0; + } + else + { + break; + } + } + + const basegfx::B2DPoint aPoint(nullptr == pViewInformation + ? rPolygon.getB2DPoint(nClosedIdx) + : impPixelSnap(rPolygon, *pViewInformation, nClosedIdx)); + + if (!nPointIdx) + { + // first point => just move there + cairo_move_to(cr, aPoint.getX(), aPoint.getY()); + aLast = aPoint; + continue; + } + + bool bPendingCurve(false); + + if (bHasCurves) + { + bPendingCurve = rPolygon.isNextControlPointUsed(nPrevIdx); + bPendingCurve |= rPolygon.isPrevControlPointUsed(nClosedIdx); + } + + if (!bPendingCurve) // line segment + { + cairo_line_to(cr, aPoint.getX(), aPoint.getY()); + } + else // cubic bezier segment + { + basegfx::B2DPoint aCP1 = rPolygon.getNextControlPoint(nPrevIdx); + basegfx::B2DPoint aCP2 = rPolygon.getPrevControlPoint(nClosedIdx); + + // tdf#99165 if the control points are 'empty', create the mathematical + // correct replacement ones to avoid problems with the graphical sub-system + // tdf#101026 The 1st attempt to create a mathematically correct replacement control + // vector was wrong. Best alternative is one as close as possible which means short. + if (aCP1.equal(aLast)) + { + aCP1 = aLast + ((aCP2 - aLast) * 0.0005); + } + + if (aCP2.equal(aPoint)) + { + aCP2 = aPoint + ((aCP1 - aPoint) * 0.0005); + } + + cairo_curve_to(cr, aCP1.getX(), aCP1.getY(), aCP2.getX(), aCP2.getY(), aPoint.getX(), + aPoint.getY()); + } + + aLast = aPoint; + } + + if (bClosePath) + { + cairo_close_path(cr); + } +} + +// split alpha remains as a constant irritant +std::vector<sal_uInt8> createBitmapData(const BitmapEx& rBitmapEx) +{ + const Size& rSizePixel(rBitmapEx.GetSizePixel()); + const bool bAlpha(rBitmapEx.IsAlpha()); + const sal_uInt32 nStride + = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, rSizePixel.Width()); + std::vector<sal_uInt8> aData(nStride * rSizePixel.Height()); + + if (bAlpha) + { + Bitmap aSrcAlpha(rBitmapEx.GetAlphaMask().GetBitmap()); + BitmapScopedReadAccess pReadAccess(rBitmapEx.GetBitmap()); + BitmapScopedReadAccess pAlphaReadAccess(aSrcAlpha); + const tools::Long nHeight(pReadAccess->Height()); + const tools::Long nWidth(pReadAccess->Width()); + + for (tools::Long y = 0; y < nHeight; ++y) + { + unsigned char* pPixelData = aData.data() + (nStride * y); + for (tools::Long x = 0; x < nWidth; ++x) + { + const BitmapColor aColor(pReadAccess->GetColor(y, x)); + const BitmapColor aAlpha(pAlphaReadAccess->GetColor(y, x)); + const sal_uInt16 nAlpha(255 - aAlpha.GetRed()); + + pPixelData[SVP_CAIRO_RED] = vcl::bitmap::premultiply(nAlpha, aColor.GetRed()); + pPixelData[SVP_CAIRO_GREEN] = vcl::bitmap::premultiply(nAlpha, aColor.GetGreen()); + pPixelData[SVP_CAIRO_BLUE] = vcl::bitmap::premultiply(nAlpha, aColor.GetBlue()); + pPixelData[SVP_CAIRO_ALPHA] = nAlpha; + pPixelData += 4; + } + } + } + else + { + BitmapScopedReadAccess pReadAccess(rBitmapEx.GetBitmap()); + const tools::Long nHeight(pReadAccess->Height()); + const tools::Long nWidth(pReadAccess->Width()); + + for (tools::Long y = 0; y < nHeight; ++y) + { + unsigned char* pPixelData = aData.data() + (nStride * y); + for (tools::Long x = 0; x < nWidth; ++x) + { + const BitmapColor aColor(pReadAccess->GetColor(y, x)); + pPixelData[SVP_CAIRO_RED] = aColor.GetRed(); + pPixelData[SVP_CAIRO_GREEN] = aColor.GetGreen(); + pPixelData[SVP_CAIRO_BLUE] = aColor.GetBlue(); + pPixelData[SVP_CAIRO_ALPHA] = 255; + pPixelData += 4; + } + } + } + + return aData; +} +} + +namespace drawinglayer::processor2d +{ +CairoPixelProcessor2D::CairoPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation) + : BaseProcessor2D(rViewInformation) + , maBColorModifierStack() + , mpRT(nullptr) +{ +} + +CairoPixelProcessor2D::CairoPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation, + cairo_surface_t* pTarget) + : BaseProcessor2D(rViewInformation) + , maBColorModifierStack() + , mpRT(nullptr) +{ + if (pTarget) + { + cairo_t* pRT = cairo_create(pTarget); + cairo_set_antialias(pRT, rViewInformation.getUseAntiAliasing() ? CAIRO_ANTIALIAS_DEFAULT + : CAIRO_ANTIALIAS_NONE); + setRenderTarget(pRT); + } +} + +CairoPixelProcessor2D::~CairoPixelProcessor2D() +{ + if (mpRT) + cairo_destroy(mpRT); +} + +void CairoPixelProcessor2D::processPolygonHairlinePrimitive2D( + const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D) +{ + const basegfx::B2DPolygon& rPolygon(rPolygonHairlinePrimitive2D.getB2DPolygon()); + + if (!rPolygon.count()) + return; + + cairo_save(mpRT); + + cairo_matrix_t aMatrix; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e() + fAAOffset, + rObjectToView.f() + fAAOffset); + + // set linear transformation + cairo_set_matrix(mpRT, &aMatrix); + + const basegfx::BColor aHairlineColor( + maBColorModifierStack.getModifiedColor(rPolygonHairlinePrimitive2D.getBColor())); + cairo_set_source_rgb(mpRT, aHairlineColor.getRed(), aHairlineColor.getGreen(), + aHairlineColor.getBlue()); + + // TODO: Unfortunately Direct2D paint of one pixel wide lines does not + // correctly and completely blend 100% over the background. Experimenting + // shows that a value around/slightly below 2.0 is needed which hints that + // alpha blending the half-shifted lines (see fAAOffset above) is involved. + // To get correct blending I try to use just wider hairlines for now. This + // may need to be improved - or balanced (trying sqrt(2) now...) + cairo_set_line_width(mpRT, 1.44f); + + addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D()); + + cairo_stroke(mpRT); + + cairo_restore(mpRT); +} + +void CairoPixelProcessor2D::processPolyPolygonColorPrimitive2D( + const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D) +{ + const basegfx::B2DPolyPolygon& rPolyPolygon(rPolyPolygonColorPrimitive2D.getB2DPolyPolygon()); + const sal_uInt32 nCount(rPolyPolygon.count()); + + if (!nCount) + return; + + cairo_save(mpRT); + + cairo_matrix_t aMatrix; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e() + fAAOffset, + rObjectToView.f() + fAAOffset); + + // set linear transformation + cairo_set_matrix(mpRT, &aMatrix); + + const basegfx::BColor aFillColor( + maBColorModifierStack.getModifiedColor(rPolyPolygonColorPrimitive2D.getBColor())); + cairo_set_source_rgb(mpRT, aFillColor.getRed(), aFillColor.getGreen(), aFillColor.getBlue()); + + for (const auto& rPolygon : rPolyPolygon) + addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D()); + + cairo_fill(mpRT); + + cairo_restore(mpRT); +} + +void CairoPixelProcessor2D::processBitmapPrimitive2D( + const primitive2d::BitmapPrimitive2D& rBitmapCandidate) +{ + // check if graphic content is inside discrete local ViewPort + const basegfx::B2DRange& rDiscreteViewPort(getViewInformation2D().getDiscreteViewport()); + const basegfx::B2DHomMatrix aLocalTransform( + getViewInformation2D().getObjectToViewTransformation() * rBitmapCandidate.getTransform()); + + if (!rDiscreteViewPort.isEmpty()) + { + basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0); + + aUnitRange.transform(aLocalTransform); + + if (!aUnitRange.overlaps(rDiscreteViewPort)) + { + // content is outside discrete local ViewPort + return; + } + } + + BitmapEx aBitmapEx(rBitmapCandidate.getBitmap()); + + if (aBitmapEx.IsEmpty() || aBitmapEx.GetSizePixel().IsEmpty()) + { + return; + } + + if (maBColorModifierStack.count()) + { + aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack); + + if (aBitmapEx.IsEmpty()) + { + // color gets completely replaced, get it + const basegfx::BColor aModifiedColor( + maBColorModifierStack.getModifiedColor(basegfx::BColor())); + + // use unit geometry as fallback object geometry. Do *not* + // transform, the below used method will use the already + // correctly initialized local ViewInformation + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + + rtl::Reference<primitive2d::PolyPolygonColorPrimitive2D> xTemp( + new primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon), + aModifiedColor)); + processPolyPolygonColorPrimitive2D(*xTemp); + return; + } + } + + // nasty copy of bitmap data + std::vector<sal_uInt8> aPixelData(createBitmapData(aBitmapEx)); + const Size& rSizePixel(aBitmapEx.GetSizePixel()); + cairo_surface_t* pBitmapSurface = cairo_image_surface_create_for_data( + aPixelData.data(), CAIRO_FORMAT_ARGB32, rSizePixel.Width(), rSizePixel.Height(), + cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, rSizePixel.Width())); + + cairo_save(mpRT); + + cairo_matrix_t aMatrix; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + cairo_matrix_init(&aMatrix, aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), + aLocalTransform.d(), aLocalTransform.e() + fAAOffset, + aLocalTransform.f() + fAAOffset); + + // set linear transformation + cairo_set_matrix(mpRT, &aMatrix); + + // destinationRectangle is part of transformation above, so use UnitRange + cairo_rectangle(mpRT, 0, 0, 1, 1); + cairo_clip(mpRT); + + cairo_set_source_surface(mpRT, pBitmapSurface, 0, 0); + // get the pattern created by cairo_set_source_surface + cairo_pattern_t* sourcepattern = cairo_get_source(mpRT); + cairo_pattern_get_matrix(sourcepattern, &aMatrix); + // scale to match the current transformation + cairo_matrix_scale(&aMatrix, rSizePixel.Width(), rSizePixel.Height()); + cairo_pattern_set_matrix(sourcepattern, &aMatrix); + + cairo_paint(mpRT); + + cairo_surface_destroy(pBitmapSurface); + + cairo_restore(mpRT); +} + +namespace +{ +// This bit-tweaking looping is unpleasant and unfortunate +void LuminanceToAlpha(cairo_surface_t* pMask) +{ + cairo_surface_flush(pMask); + + int nWidth = cairo_image_surface_get_width(pMask); + int nHeight = cairo_image_surface_get_height(pMask); + int nStride = cairo_image_surface_get_stride(pMask); + unsigned char* mask_surface_data = cairo_image_surface_get_data(pMask); + + // include/basegfx/color/bcolormodifier.hxx + const double nRedMul = 0.2125 / 255.0; + const double nGreenMul = 0.7154 / 255.0; + const double nBlueMul = 0.0721 / 255.0; + for (int y = 0; y < nHeight; ++y) + { + unsigned char* pMaskPixelData = mask_surface_data + (nStride * y); + for (int x = 0; x < nWidth; ++x) + { + double fLuminance = pMaskPixelData[SVP_CAIRO_RED] * nRedMul + + pMaskPixelData[SVP_CAIRO_GREEN] * nGreenMul + + pMaskPixelData[SVP_CAIRO_BLUE] * nBlueMul; + // Only this alpha channel is taken into account by cairo_mask_surface + // so reuse this surface for the alpha result + pMaskPixelData[SVP_CAIRO_ALPHA] = 255.0 * fLuminance; + pMaskPixelData += 4; + } + } + + cairo_surface_mark_dirty(pMask); +} +} + +void CairoPixelProcessor2D::processTransparencePrimitive2D( + const primitive2d::TransparencePrimitive2D& rTransCandidate) +{ + if (rTransCandidate.getChildren().empty()) + return; + + if (rTransCandidate.getTransparence().empty()) + return; + + cairo_surface_t* pTarget = cairo_get_target(mpRT); + + double clip_x1, clip_x2, clip_y1, clip_y2; + cairo_clip_extents(mpRT, &clip_x1, &clip_y1, &clip_x2, &clip_y2); + + // calculate visible range, create only for that range + basegfx::B2DRange aDiscreteRange( + rTransCandidate.getChildren().getB2DRange(getViewInformation2D())); + aDiscreteRange.transform(getViewInformation2D().getObjectToViewTransformation()); + const basegfx::B2DRange aViewRange(basegfx::B2DPoint(clip_x1, clip_y1), + basegfx::B2DPoint(clip_x2, clip_y2)); + basegfx::B2DRange aVisibleRange(aDiscreteRange); + aVisibleRange.intersect(aViewRange); + + if (aVisibleRange.isEmpty()) + { + // not visible, done + return; + } + + const basegfx::B2DHomMatrix aEmbedTransform(basegfx::utils::createTranslateB2DHomMatrix( + -aVisibleRange.getMinX(), -aVisibleRange.getMinY())); + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setViewTransformation(aEmbedTransform + * getViewInformation2D().getViewTransformation()); + // draw mask to temporary surface + cairo_surface_t* pMask = cairo_surface_create_similar_image(pTarget, CAIRO_FORMAT_ARGB32, + ceil(aVisibleRange.getWidth()), + ceil(aVisibleRange.getHeight())); + CairoPixelProcessor2D aMaskRenderer(aViewInformation2D, pMask); + aMaskRenderer.process(rTransCandidate.getTransparence()); + + // convert mask to something cairo can use + LuminanceToAlpha(pMask); + + // draw content to temporary surface + cairo_surface_t* pContent = cairo_surface_create_similar( + pTarget, cairo_surface_get_content(pTarget), ceil(aVisibleRange.getWidth()), + ceil(aVisibleRange.getHeight())); + CairoPixelProcessor2D aContent(aViewInformation2D, pContent); + aContent.process(rTransCandidate.getChildren()); + + // munge the temporary surfaces to our target surface + cairo_set_source_surface(mpRT, pContent, aVisibleRange.getMinX(), aVisibleRange.getMinY()); + cairo_mask_surface(mpRT, pMask, aVisibleRange.getMinX(), aVisibleRange.getMinY()); + + cairo_surface_destroy(pContent); + cairo_surface_destroy(pMask); +} + +void CairoPixelProcessor2D::processMaskPrimitive2DPixel( + const primitive2d::MaskPrimitive2D& rMaskCandidate) +{ + if (rMaskCandidate.getChildren().empty()) + return; + + basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask()); + + if (!aMask.count()) + return; + + double clip_x1, clip_x2, clip_y1, clip_y2; + cairo_clip_extents(mpRT, &clip_x1, &clip_y1, &clip_x2, &clip_y2); + + basegfx::B2DRange aMaskRange(aMask.getB2DRange()); + aMaskRange.transform(getViewInformation2D().getObjectToViewTransformation()); + const basegfx::B2DRange aViewRange(basegfx::B2DPoint(clip_x1, clip_y1), + basegfx::B2DPoint(clip_x2, clip_y2)); + + if (!aViewRange.overlaps(aMaskRange)) + return; + + cairo_save(mpRT); + + cairo_matrix_t aMatrix; + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e(), rObjectToView.f()); + + // set linear transformation + cairo_set_matrix(mpRT, &aMatrix); + + // put mask as path + for (const auto& rPolygon : aMask) + addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D()); + + // clip to this mask + cairo_clip(mpRT); + + process(rMaskCandidate.getChildren()); + + cairo_restore(mpRT); +} + +void CairoPixelProcessor2D::processPointArrayPrimitive2D( + const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate) +{ + const std::vector<basegfx::B2DPoint>& rPositions(rPointArrayCandidate.getPositions()); + if (rPositions.empty()) + return; + + const basegfx::BColor aPointColor( + maBColorModifierStack.getModifiedColor(rPointArrayCandidate.getRGBColor())); + cairo_set_source_rgb(mpRT, aPointColor.getRed(), aPointColor.getGreen(), aPointColor.getBlue()); + + // To really paint a single pixel I found nothing better than + // switch off AA and draw a pixel-aligned rectangle + const cairo_antialias_t eOldAAMode(cairo_get_antialias(mpRT)); + cairo_set_antialias(mpRT, CAIRO_ANTIALIAS_NONE); + + for (auto const& pos : rPositions) + { + const basegfx::B2DPoint aDiscretePos(getViewInformation2D().getObjectToViewTransformation() + * pos); + const double fX(ceil(aDiscretePos.getX())); + const double fY(ceil(aDiscretePos.getY())); + + cairo_rectangle(mpRT, fX, fY, 1, 1); + cairo_fill(mpRT); + } + + cairo_set_antialias(mpRT, eOldAAMode); +} + +void CairoPixelProcessor2D::processModifiedColorPrimitive2D( + const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate) +{ + if (!rModifiedCandidate.getChildren().empty()) + { + maBColorModifierStack.push(rModifiedCandidate.getColorModifier()); + process(rModifiedCandidate.getChildren()); + maBColorModifierStack.pop(); + } +} + +void CairoPixelProcessor2D::processTransformPrimitive2D( + const primitive2d::TransformPrimitive2D& rTransformCandidate) +{ + // remember current transformation and ViewInformation + const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); + + // create new transformations for local ViewInformation2D + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() + * rTransformCandidate.getTransformation()); + updateViewInformation(aViewInformation2D); + + // process content + process(rTransformCandidate.getChildren()); + + // restore transformations + updateViewInformation(aLastViewInformation2D); +} + +void CairoPixelProcessor2D::processPolygonStrokePrimitive2D( + const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate) +{ + const basegfx::B2DPolygon& rPolygon(rPolygonStrokeCandidate.getB2DPolygon()); + const attribute::LineAttribute& rLineAttribute(rPolygonStrokeCandidate.getLineAttribute()); + + if (!rPolygon.count() || rLineAttribute.getWidth() < 0.0) + { + // no geometry, done + return; + } + + // get some values early that might be used for decisions + const bool bHairline(0.0 == rLineAttribute.getWidth()); + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + const double fDiscreteLineWidth( + bHairline + ? 1.0 + : (rObjectToView * basegfx::B2DVector(rLineAttribute.getWidth(), 0.0)).getLength()); + + // Here for every combination which the system-specific implementation is not + // capable of visualizing, use the (for decomposable Primitives always possible) + // fallback to the decomposition. + if (basegfx::B2DLineJoin::NONE == rLineAttribute.getLineJoin() && fDiscreteLineWidth > 1.5) + { + // basegfx::B2DLineJoin::NONE is special for our office, no other GraphicSystem + // knows that (so far), so fallback to decomposition. This is only needed if + // LineJoin will be used, so also check for discrete LineWidth before falling back + process(rPolygonStrokeCandidate); + return; + } + + // This is a method every system-specific implementation of a decomposable Primitive + // can use to allow simple optical control of paint implementation: + // Create a copy, e.g. change color to 'red' as here and paint before the system + // paints it using the decomposition. That way you can - if active - directly + // optically compare if the system-specific solution is geometrically identical to + // the decomposition (which defines our interpretation that we need to visualize). + // Look below in the impl for bRenderDecomposeForCompareInRed to see that in that case + // we create a half-transparent paint to better support visual control + static bool bRenderDecomposeForCompareInRed(false); + + if (bRenderDecomposeForCompareInRed) + { + const attribute::LineAttribute aRed( + basegfx::BColor(1.0, 0.0, 0.0), rLineAttribute.getWidth(), rLineAttribute.getLineJoin(), + rLineAttribute.getLineCap(), rLineAttribute.getMiterMinimumAngle()); + rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xCopy( + new primitive2d::PolygonStrokePrimitive2D( + rPolygonStrokeCandidate.getB2DPolygon(), aRed, + rPolygonStrokeCandidate.getStrokeAttribute())); + process(*xCopy); + } + + cairo_save(mpRT); + + cairo_matrix_t aMatrix; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e() + fAAOffset, + rObjectToView.f() + fAAOffset); + + // set linear transformation + cairo_set_matrix(mpRT, &aMatrix); + + // setup line attributes + cairo_line_join_t eCairoLineJoin = CAIRO_LINE_JOIN_MITER; + switch (rLineAttribute.getLineJoin()) + { + case basegfx::B2DLineJoin::Bevel: + eCairoLineJoin = CAIRO_LINE_JOIN_BEVEL; + break; + case basegfx::B2DLineJoin::Round: + eCairoLineJoin = CAIRO_LINE_JOIN_ROUND; + break; + case basegfx::B2DLineJoin::NONE: + case basegfx::B2DLineJoin::Miter: + eCairoLineJoin = CAIRO_LINE_JOIN_MITER; + break; + } + + // convert miter minimum angle to miter limit + double fMiterLimit + = 1.0 / sin(std::max(rLineAttribute.getMiterMinimumAngle(), 0.01 * M_PI) / 2.0); + + // setup cap attribute + cairo_line_cap_t eCairoLineCap(CAIRO_LINE_CAP_BUTT); + + switch (rLineAttribute.getLineCap()) + { + default: // css::drawing::LineCap_BUTT: + { + eCairoLineCap = CAIRO_LINE_CAP_BUTT; + break; + } + case css::drawing::LineCap_ROUND: + { + eCairoLineCap = CAIRO_LINE_CAP_ROUND; + break; + } + case css::drawing::LineCap_SQUARE: + { + eCairoLineCap = CAIRO_LINE_CAP_SQUARE; + break; + } + } + + basegfx::BColor aLineColor(maBColorModifierStack.getModifiedColor(rLineAttribute.getColor())); + if (bRenderDecomposeForCompareInRed) + aLineColor.setRed(0.5); + + cairo_set_source_rgb(mpRT, aLineColor.getRed(), aLineColor.getGreen(), aLineColor.getBlue()); + + cairo_set_line_join(mpRT, eCairoLineJoin); + cairo_set_line_cap(mpRT, eCairoLineCap); + + // TODO: Hairline LineWidth, see comment at processPolygonHairlinePrimitive2D + cairo_set_line_width(mpRT, bHairline ? 1.44 : fDiscreteLineWidth); + cairo_set_miter_limit(mpRT, fMiterLimit); + + const attribute::StrokeAttribute& rStrokeAttribute( + rPolygonStrokeCandidate.getStrokeAttribute()); + const bool bDashUsed(!rStrokeAttribute.isDefault() + && !rStrokeAttribute.getDotDashArray().empty() + && 0.0 < rStrokeAttribute.getFullDotDashLen()); + if (bDashUsed) + { + const std::vector<double>& rStroke = rStrokeAttribute.getDotDashArray(); + cairo_set_dash(mpRT, rStroke.data(), rStroke.size(), 0.0); + } + + addB2DPolygonToPathGeometry(mpRT, rPolygon, &getViewInformation2D()); + + cairo_stroke(mpRT); + + cairo_restore(mpRT); +} + +void CairoPixelProcessor2D::processLineRectanglePrimitive2D( + const primitive2d::LineRectanglePrimitive2D& rLineRectanglePrimitive2D) +{ + if (rLineRectanglePrimitive2D.getB2DRange().isEmpty()) + { + // no geometry, done + return; + } + + cairo_save(mpRT); + + cairo_matrix_t aMatrix; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e() + fAAOffset, + rObjectToView.f() + fAAOffset); + + // set linear transformation + cairo_set_matrix(mpRT, &aMatrix); + + const basegfx::BColor aHairlineColor( + maBColorModifierStack.getModifiedColor(rLineRectanglePrimitive2D.getBColor())); + cairo_set_source_rgb(mpRT, aHairlineColor.getRed(), aHairlineColor.getGreen(), + aHairlineColor.getBlue()); + + const double fDiscreteLineWidth((getViewInformation2D().getInverseObjectToViewTransformation() + * basegfx::B2DVector(1.44, 0.0)) + .getLength()); + cairo_set_line_width(mpRT, fDiscreteLineWidth); + + const basegfx::B2DRange& rRange(rLineRectanglePrimitive2D.getB2DRange()); + cairo_rectangle(mpRT, rRange.getMinX(), rRange.getMinY(), rRange.getWidth(), + rRange.getHeight()); + cairo_stroke(mpRT); + + cairo_restore(mpRT); +} + +void CairoPixelProcessor2D::processFilledRectanglePrimitive2D( + const primitive2d::FilledRectanglePrimitive2D& rFilledRectanglePrimitive2D) +{ + if (rFilledRectanglePrimitive2D.getB2DRange().isEmpty()) + { + // no geometry, done + return; + } + + cairo_save(mpRT); + + cairo_matrix_t aMatrix; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + cairo_matrix_init(&aMatrix, rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e() + fAAOffset, + rObjectToView.f() + fAAOffset); + + // set linear transformation + cairo_set_matrix(mpRT, &aMatrix); + + const basegfx::BColor aFillColor( + maBColorModifierStack.getModifiedColor(rFilledRectanglePrimitive2D.getBColor())); + cairo_set_source_rgb(mpRT, aFillColor.getRed(), aFillColor.getGreen(), aFillColor.getBlue()); + + const basegfx::B2DRange& rRange(rFilledRectanglePrimitive2D.getB2DRange()); + cairo_rectangle(mpRT, rRange.getMinX(), rRange.getMinY(), rRange.getWidth(), + rRange.getHeight()); + cairo_fill(mpRT); + + cairo_restore(mpRT); +} + +void CairoPixelProcessor2D::processSingleLinePrimitive2D( + const primitive2d::SingleLinePrimitive2D& rSingleLinePrimitive2D) +{ + cairo_save(mpRT); + + const basegfx::BColor aLineColor( + maBColorModifierStack.getModifiedColor(rSingleLinePrimitive2D.getBColor())); + cairo_set_source_rgb(mpRT, aLineColor.getRed(), aLineColor.getGreen(), aLineColor.getBlue()); + + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + const basegfx::B2DPoint aStart(rObjectToView * rSingleLinePrimitive2D.getStart()); + const basegfx::B2DPoint aEnd(rObjectToView * rSingleLinePrimitive2D.getEnd()); + + cairo_set_line_width(mpRT, 1.44f); + + cairo_move_to(mpRT, aStart.getX() + fAAOffset, aStart.getY() + fAAOffset); + cairo_line_to(mpRT, aEnd.getX() + fAAOffset, aEnd.getY() + fAAOffset); + cairo_stroke(mpRT); + + cairo_restore(mpRT); +} + +void CairoPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) +{ + switch (rCandidate.getPrimitive2DID()) + { + // geometry that *has* to be processed + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D: + { + processBitmapPrimitive2D( + static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D: + { + processPointArrayPrimitive2D( + static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D: + { + processPolygonHairlinePrimitive2D( + static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D: + { + processPolyPolygonColorPrimitive2D( + static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate)); + break; + } + // embedding/groups that *have* to be processed + case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D: + { + processTransparencePrimitive2D( + static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_INVERTPRIMITIVE2D: + { + // TODO: fallback is at VclPixelProcessor2D::processInvertPrimitive2D, so + // not in reach. Ignore for now. + // processInvertPrimitive2D(rCandidate); + break; + } + case PRIMITIVE2D_ID_MASKPRIMITIVE2D: + { + processMaskPrimitive2DPixel( + static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D: + { + processModifiedColorPrimitive2D( + static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D: + { + processTransformPrimitive2D( + static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate)); + break; + } +#if 0 + // geometry that *may* be processed due to being able to do it better + // then using the decomposition + case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D: + { + processUnifiedTransparencePrimitive2D( + static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D: + { + processMarkerArrayPrimitive2D( + static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D: + { + processBackgroundColorPrimitive2D( + static_cast<const primitive2d::BackgroundColorPrimitive2D&>(rCandidate)); + break; + } +#endif + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D: + { + processPolygonStrokePrimitive2D( + static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_LINERECTANGLEPRIMITIVE2D: + { + processLineRectanglePrimitive2D( + static_cast<const primitive2d::LineRectanglePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_FILLEDRECTANGLEPRIMITIVE2D: + { + processFilledRectanglePrimitive2D( + static_cast<const primitive2d::FilledRectanglePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_SINGLELINEPRIMITIVE2D: + { + processSingleLinePrimitive2D( + static_cast<const primitive2d::SingleLinePrimitive2D&>(rCandidate)); + break; + } + + // continue with decompose + default: + { + SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString( + rCandidate.getPrimitive2DID())); + // process recursively + process(rCandidate); + break; + } + } +} + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/drawinglayer/source/processor2d/contourextractor2d.cxx b/drawinglayer/source/processor2d/contourextractor2d.cxx new file mode 100644 index 0000000000..65e8ef86a2 --- /dev/null +++ b/drawinglayer/source/processor2d/contourextractor2d.cxx @@ -0,0 +1,189 @@ +/* -*- 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 <drawinglayer/processor2d/contourextractor2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/primitive2d/metafileprimitive2d.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/sceneprimitive2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::processor2d +{ + ContourExtractor2D::ContourExtractor2D( + const geometry::ViewInformation2D& rViewInformation, + bool bExtractFillOnly) + : BaseProcessor2D(rViewInformation), + mbExtractFillOnly(bExtractFillOnly) + { + } + + ContourExtractor2D::~ContourExtractor2D() + { + } + + void ContourExtractor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) + { + switch(rCandidate.getPrimitive2DID()) + { + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D : + { + if(!mbExtractFillOnly) + { + // extract hairline in world coordinates + const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate)); + basegfx::B2DPolygon aLocalPolygon(rPolygonCandidate.getB2DPolygon()); + aLocalPolygon.transform(getViewInformation2D().getObjectTransformation()); + + if(aLocalPolygon.isClosed()) + { + // line polygons need to be represented as open polygons to differentiate them + // from filled polygons + basegfx::utils::openWithGeometryChange(aLocalPolygon); + } + + maExtractedContour.emplace_back(aLocalPolygon); + } + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D : + { + // extract fill in world coordinates + const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate)); + basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon()); + aLocalPolyPolygon.transform(getViewInformation2D().getObjectTransformation()); + maExtractedContour.push_back(aLocalPolyPolygon); + break; + } + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D : + { + // extract BoundRect from bitmaps in world coordinates + const primitive2d::BitmapPrimitive2D& rBitmapCandidate(static_cast< const primitive2d::BitmapPrimitive2D& >(rCandidate)); + basegfx::B2DHomMatrix aLocalTransform(getViewInformation2D().getObjectTransformation() * rBitmapCandidate.getTransform()); + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + aPolygon.transform(aLocalTransform); + maExtractedContour.emplace_back(aPolygon); + break; + } + case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D : + { + // extract BoundRect from MetaFiles in world coordinates + const primitive2d::MetafilePrimitive2D& rMetaCandidate(static_cast< const primitive2d::MetafilePrimitive2D& >(rCandidate)); + basegfx::B2DHomMatrix aLocalTransform(getViewInformation2D().getObjectTransformation() * rMetaCandidate.getTransform()); + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + aPolygon.transform(aLocalTransform); + maExtractedContour.emplace_back(aPolygon); + break; + } + case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D : + { + // sub-transparence group. Look at children + const primitive2d::TransparencePrimitive2D& rTransCandidate(static_cast< const primitive2d::TransparencePrimitive2D& >(rCandidate)); + process(rTransCandidate.getChildren()); + break; + } + case PRIMITIVE2D_ID_MASKPRIMITIVE2D : + { + // extract mask in world coordinates, ignore content + const primitive2d::MaskPrimitive2D& rMaskCandidate(static_cast< const primitive2d::MaskPrimitive2D& >(rCandidate)); + basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask()); + aMask.transform(getViewInformation2D().getObjectTransformation()); + maExtractedContour.push_back(aMask); + break; + } + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D : + { + // remember current ViewInformation2D + const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate)); + const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); + + // create new local ViewInformation2D + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation()); + updateViewInformation(aViewInformation2D); + + // process content + process(rTransformCandidate.getChildren()); + + // restore transformations + updateViewInformation(aLastViewInformation2D); + + break; + } + case PRIMITIVE2D_ID_SCENEPRIMITIVE2D : + { + // 2D Scene primitive containing 3D stuff; extract 2D contour in world coordinates + const primitive2d::ScenePrimitive2D& rScenePrimitive2DCandidate(static_cast< const primitive2d::ScenePrimitive2D& >(rCandidate)); + const primitive2d::Primitive2DContainer xExtracted2DSceneGeometry(rScenePrimitive2DCandidate.getGeometry2D()); + const primitive2d::Primitive2DContainer xExtracted2DSceneShadow(rScenePrimitive2DCandidate.getShadow2D()); + + // process content + if(!xExtracted2DSceneGeometry.empty()) + { + process(xExtracted2DSceneGeometry); + } + + // process content + if(!xExtracted2DSceneShadow.empty()) + { + process(xExtracted2DSceneShadow); + } + + break; + } + case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D : + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D : + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D : + { + // ignorable primitives + break; + } + case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D : + case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D : + { + // primitives who's BoundRect will be added in world coordinates + basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D())); + if (!aRange.isEmpty()) + { + aRange.transform(getViewInformation2D().getObjectTransformation()); + maExtractedContour.emplace_back(basegfx::utils::createPolygonFromRect(aRange)); + } + break; + } + default : + { + // process recursively + process(rCandidate); + break; + } + } + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx b/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx new file mode 100644 index 0000000000..6bfc958783 --- /dev/null +++ b/drawinglayer/source/processor2d/d2dpixelprocessor2d.cxx @@ -0,0 +1,2191 @@ +/* -*- 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/config.h> + +// win-specific +#include <prewin.h> +#include <d2d1.h> +#include <d2d1_1.h> +#include <postwin.h> + +#include <drawinglayer/processor2d/d2dpixelprocessor2d.hxx> +#include <drawinglayer/processor2d/SDPRProcessor2dTools.hxx> +#include <sal/log.hxx> +#include <vcl/outdev.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/backgroundcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/baseprimitive2d.hxx> +#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/Tools.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/invertprimitive2d.hxx> +#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> +#include <drawinglayer/converters.hxx> +#include <basegfx/curve/b2dcubicbezier.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/utils/systemdependentdata.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/svapp.hxx> + +using namespace com::sun::star; + +namespace +{ +class ID2D1GlobalFactoryProvider +{ + sal::systools::COMReference<ID2D1Factory> mpD2DFactory; + +public: + ID2D1GlobalFactoryProvider() + : mpD2DFactory(nullptr) + { + const HRESULT hr(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, + __uuidof(ID2D1Factory), nullptr, + reinterpret_cast<void**>(&mpD2DFactory))); + + if (!SUCCEEDED(hr)) + mpD2DFactory.clear(); + } + + sal::systools::COMReference<ID2D1Factory>& getID2D1Factory() { return mpD2DFactory; } +}; + +ID2D1GlobalFactoryProvider aID2D1GlobalFactoryProvider; + +class ID2D1GlobalRenderTargetProvider +{ + sal::systools::COMReference<ID2D1DCRenderTarget> mpID2D1DCRenderTarget; + +public: + ID2D1GlobalRenderTargetProvider() + : mpID2D1DCRenderTarget() + { + } + + sal::systools::COMReference<ID2D1DCRenderTarget>& getID2D1DCRenderTarget() + { + if (!mpID2D1DCRenderTarget && aID2D1GlobalFactoryProvider.getID2D1Factory()) + { + const D2D1_RENDER_TARGET_PROPERTIES aRTProps(D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, + D2D1_ALPHA_MODE_IGNORE), //D2D1_ALPHA_MODE_PREMULTIPLIED), + 0, 0, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT)); + + const HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateDCRenderTarget( + &aRTProps, &mpID2D1DCRenderTarget)); + + // interestingly this ID2D1DCRenderTarget already works and can hold + // created ID2D1Bitmap(s) in RenderTarget-specific form, *without* + // any call to "BindDC", thus *without* the need of a real HDC - nice :-) + // When that would be needed, Application::GetDefaultDevice() would need + // to have a HDC that is valid during LO's lifetime. + + if (!SUCCEEDED(hr)) + mpID2D1DCRenderTarget.clear(); + } + + return mpID2D1DCRenderTarget; + } +}; + +ID2D1GlobalRenderTargetProvider aID2D1GlobalRenderTargetProvider; + +class SystemDependentData_ID2D1PathGeometry : public basegfx::SystemDependentData +{ +private: + sal::systools::COMReference<ID2D1PathGeometry> mpID2D1PathGeometry; + +public: + SystemDependentData_ID2D1PathGeometry( + sal::systools::COMReference<ID2D1PathGeometry>& rID2D1PathGeometry) + : basegfx::SystemDependentData(Application::GetSystemDependentDataManager()) + , mpID2D1PathGeometry(rID2D1PathGeometry) + { + } + + const sal::systools::COMReference<ID2D1PathGeometry>& getID2D1PathGeometry() const + { + return mpID2D1PathGeometry; + } + virtual sal_Int64 estimateUsageInBytes() const override; +}; + +sal_Int64 SystemDependentData_ID2D1PathGeometry::estimateUsageInBytes() const +{ + sal_Int64 aRetval(0); + + if (getID2D1PathGeometry()) + { + UINT32 nCount(0); + const HRESULT hr(getID2D1PathGeometry()->GetSegmentCount(&nCount)); + + if (SUCCEEDED(hr)) + { + // without completely receiving and tracing the GeometrySink + // do a rough estimation - each segment is 2D, so has two doubles. + // Some are beziers, so add some guessed buffer for two additional + // control points + aRetval = static_cast<sal_Int64>(nCount) * (6 * sizeof(double)); + } + } + + return aRetval; +} + +basegfx::B2DPoint impPixelSnap(const basegfx::B2DPolygon& rPolygon, + const drawinglayer::geometry::ViewInformation2D& rViewInformation, + sal_uInt32 nIndex) +{ + const sal_uInt32 nCount(rPolygon.count()); + + // get the data + const basegfx::B2ITuple aPrevTuple( + basegfx::fround(rViewInformation.getObjectToViewTransformation() + * rPolygon.getB2DPoint((nIndex + nCount - 1) % nCount))); + const basegfx::B2DPoint aCurrPoint(rViewInformation.getObjectToViewTransformation() + * rPolygon.getB2DPoint(nIndex)); + const basegfx::B2ITuple aCurrTuple(basegfx::fround(aCurrPoint)); + const basegfx::B2ITuple aNextTuple( + basegfx::fround(rViewInformation.getObjectToViewTransformation() + * rPolygon.getB2DPoint((nIndex + 1) % nCount))); + + // get the states + const bool bPrevVertical(aPrevTuple.getX() == aCurrTuple.getX()); + const bool bNextVertical(aNextTuple.getX() == aCurrTuple.getX()); + const bool bPrevHorizontal(aPrevTuple.getY() == aCurrTuple.getY()); + const bool bNextHorizontal(aNextTuple.getY() == aCurrTuple.getY()); + const bool bSnapX(bPrevVertical || bNextVertical); + const bool bSnapY(bPrevHorizontal || bNextHorizontal); + + if (bSnapX || bSnapY) + { + basegfx::B2DPoint aSnappedPoint(bSnapX ? aCurrTuple.getX() : aCurrPoint.getX(), + bSnapY ? aCurrTuple.getY() : aCurrPoint.getY()); + + aSnappedPoint *= rViewInformation.getInverseObjectToViewTransformation(); + + return aSnappedPoint; + } + + return rPolygon.getB2DPoint(nIndex); +} + +void addB2DPolygonToPathGeometry(sal::systools::COMReference<ID2D1GeometrySink>& rSink, + const basegfx::B2DPolygon& rPolygon, + const drawinglayer::geometry::ViewInformation2D* pViewInformation) +{ + const sal_uInt32 nPointCount(rPolygon.count()); + const sal_uInt32 nEdgeCount(rPolygon.isClosed() ? nPointCount : nPointCount - 1); + basegfx::B2DCubicBezier aEdge; + + for (sal_uInt32 a(0); a < nEdgeCount; a++) + { + rPolygon.getBezierSegment(a, aEdge); + + const basegfx::B2DPoint aEndPoint( + nullptr == pViewInformation + ? aEdge.getEndPoint() + : impPixelSnap(rPolygon, *pViewInformation, (a + 1) % nPointCount)); + + if (aEdge.isBezier()) + { + rSink->AddBezier( + D2D1::BezierSegment(D2D1::Point2F(aEdge.getControlPointA().getX(), + aEdge.getControlPointA().getY()), //C1 + D2D1::Point2F(aEdge.getControlPointB().getX(), + aEdge.getControlPointB().getY()), //c2 + D2D1::Point2F(aEndPoint.getX(), aEndPoint.getY()))); //end + } + else + { + rSink->AddLine(D2D1::Point2F(aEndPoint.getX(), aEndPoint.getY())); + } + } +} + +std::shared_ptr<SystemDependentData_ID2D1PathGeometry> +getOrCreatePathGeometry(const basegfx::B2DPolygon& rPolygon, + const drawinglayer::geometry::ViewInformation2D& rViewInformation) +{ + // try to access buffered data + std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry( + rPolygon.getSystemDependentData<SystemDependentData_ID2D1PathGeometry>()); + + if (pSystemDependentData_ID2D1PathGeometry) + { + if (rViewInformation.getPixelSnapHairline()) + { + // do not buffer when PixelSnap is active + pSystemDependentData_ID2D1PathGeometry.reset(); + } + else + { + // use and return buffered data + return pSystemDependentData_ID2D1PathGeometry; + } + } + + sal::systools::COMReference<ID2D1PathGeometry> pID2D1PathGeometry; + HRESULT hr( + aID2D1GlobalFactoryProvider.getID2D1Factory()->CreatePathGeometry(&pID2D1PathGeometry)); + const sal_uInt32 nPointCount(rPolygon.count()); + + if (SUCCEEDED(hr) && nPointCount) + { + sal::systools::COMReference<ID2D1GeometrySink> pSink; + hr = pID2D1PathGeometry->Open(&pSink); + + if (SUCCEEDED(hr) && pSink) + { + const basegfx::B2DPoint aStart(rViewInformation.getPixelSnapHairline() + ? rPolygon.getB2DPoint(0) + : impPixelSnap(rPolygon, rViewInformation, 0)); + + pSink->BeginFigure(D2D1::Point2F(aStart.getX(), aStart.getY()), + D2D1_FIGURE_BEGIN_HOLLOW); + addB2DPolygonToPathGeometry(pSink, rPolygon, &rViewInformation); + pSink->EndFigure(rPolygon.isClosed() ? D2D1_FIGURE_END_CLOSED : D2D1_FIGURE_END_OPEN); + pSink->Close(); + } + } + + // add to buffering mechanism + if (pID2D1PathGeometry) + { + if (rViewInformation.getPixelSnapHairline() || nPointCount <= 4) + { + // do not buffer when PixelSnap is active or small polygon + return std::make_shared<SystemDependentData_ID2D1PathGeometry>(pID2D1PathGeometry); + } + else + { + return rPolygon.addOrReplaceSystemDependentData<SystemDependentData_ID2D1PathGeometry>( + pID2D1PathGeometry); + } + } + + return std::shared_ptr<SystemDependentData_ID2D1PathGeometry>(); +} + +std::shared_ptr<SystemDependentData_ID2D1PathGeometry> +getOrCreateFillGeometry(const basegfx::B2DPolyPolygon& rPolyPolygon) +{ + // try to access buffered data + std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry( + rPolyPolygon.getSystemDependentData<SystemDependentData_ID2D1PathGeometry>()); + + if (pSystemDependentData_ID2D1PathGeometry) + { + // use and return buffered data + return pSystemDependentData_ID2D1PathGeometry; + } + + sal::systools::COMReference<ID2D1PathGeometry> pID2D1PathGeometry; + HRESULT hr( + aID2D1GlobalFactoryProvider.getID2D1Factory()->CreatePathGeometry(&pID2D1PathGeometry)); + const sal_uInt32 nCount(rPolyPolygon.count()); + + if (SUCCEEDED(hr) && nCount) + { + sal::systools::COMReference<ID2D1GeometrySink> pSink; + hr = pID2D1PathGeometry->Open(&pSink); + + if (SUCCEEDED(hr) && pSink) + { + for (sal_uInt32 a(0); a < nCount; a++) + { + const basegfx::B2DPolygon& rPolygon(rPolyPolygon.getB2DPolygon(a)); + const sal_uInt32 nPointCount(rPolygon.count()); + + if (nPointCount) + { + const basegfx::B2DPoint aStart(rPolygon.getB2DPoint(0)); + + pSink->BeginFigure(D2D1::Point2F(aStart.getX(), aStart.getY()), + D2D1_FIGURE_BEGIN_FILLED); + addB2DPolygonToPathGeometry(pSink, rPolygon, nullptr); + pSink->EndFigure(D2D1_FIGURE_END_CLOSED); + } + } + + pSink->Close(); + } + } + + // add to buffering mechanism + if (pID2D1PathGeometry) + { + return rPolyPolygon.addOrReplaceSystemDependentData<SystemDependentData_ID2D1PathGeometry>( + pID2D1PathGeometry); + } + + return std::shared_ptr<SystemDependentData_ID2D1PathGeometry>(); +} + +class SystemDependentData_ID2D1Bitmap : public basegfx::SystemDependentData +{ +private: + sal::systools::COMReference<ID2D1Bitmap> mpD2DBitmap; + const std::shared_ptr<SalBitmap> maAssociatedAlpha; + +public: + SystemDependentData_ID2D1Bitmap(sal::systools::COMReference<ID2D1Bitmap>& rD2DBitmap, + const std::shared_ptr<SalBitmap>& rAssociatedAlpha) + : basegfx::SystemDependentData(Application::GetSystemDependentDataManager()) + , mpD2DBitmap(rD2DBitmap) + , maAssociatedAlpha(rAssociatedAlpha) + { + } + + const sal::systools::COMReference<ID2D1Bitmap>& getID2D1Bitmap() const { return mpD2DBitmap; } + const std::shared_ptr<SalBitmap>& getAssociatedAlpha() const { return maAssociatedAlpha; } + + virtual sal_Int64 estimateUsageInBytes() const override; +}; + +sal_Int64 SystemDependentData_ID2D1Bitmap::estimateUsageInBytes() const +{ + sal_Int64 aRetval(0); + + if (getID2D1Bitmap()) + { + // use factor 4 for RGBA_8 as estimation + const D2D1_SIZE_U aSizePixel(getID2D1Bitmap()->GetPixelSize()); + aRetval = static_cast<sal_Int64>(aSizePixel.width) + * static_cast<sal_Int64>(aSizePixel.height) * 4; + } + + return aRetval; +} + +sal::systools::COMReference<ID2D1Bitmap> createB2DBitmap(const BitmapEx& rBitmapEx) +{ + const Size& rSizePixel(rBitmapEx.GetSizePixel()); + const bool bAlpha(rBitmapEx.IsAlpha()); + const sal_uInt32 nPixelCount(rSizePixel.Width() * rSizePixel.Height()); + std::unique_ptr<sal_uInt32[]> aData(new sal_uInt32[nPixelCount]); + sal_uInt32* pTarget = aData.get(); + + if (bAlpha) + { + Bitmap aSrcAlpha(rBitmapEx.GetAlphaMask().GetBitmap()); + BitmapScopedReadAccess pReadAccess(rBitmapEx.GetBitmap()); + BitmapScopedReadAccess pAlphaReadAccess(aSrcAlpha); + const tools::Long nHeight(pReadAccess->Height()); + const tools::Long nWidth(pReadAccess->Width()); + + for (tools::Long y = 0; y < nHeight; ++y) + { + for (tools::Long x = 0; x < nWidth; ++x) + { + const BitmapColor aColor(pReadAccess->GetColor(y, x)); + const BitmapColor aAlpha(pAlphaReadAccess->GetColor(y, x)); + const sal_uInt16 nAlpha(aAlpha.GetRed()); + + *pTarget++ = sal_uInt32(BitmapColor( + ColorAlpha, sal_uInt8((sal_uInt16(aColor.GetRed()) * nAlpha) >> 8), + sal_uInt8((sal_uInt16(aColor.GetGreen()) * nAlpha) >> 8), + sal_uInt8((sal_uInt16(aColor.GetBlue()) * nAlpha) >> 8), aAlpha.GetRed())); + } + } + } + else + { + BitmapScopedReadAccess pReadAccess(const_cast<Bitmap&>(rBitmapEx.GetBitmap())); + const tools::Long nHeight(pReadAccess->Height()); + const tools::Long nWidth(pReadAccess->Width()); + + for (tools::Long y = 0; y < nHeight; ++y) + { + for (tools::Long x = 0; x < nWidth; ++x) + { + const BitmapColor aColor(pReadAccess->GetColor(y, x)); + *pTarget++ = sal_uInt32(aColor); + } + } + } + + // use GlobalRenderTarget to allow usage combined with + // the Direct2D CreateSharedBitmap-mechanism. This is needed + // since ID2D1Bitmap is a ID2D1RenderTarget-dependent resource + // and thus - in principle - would have to be re-created for + // *each* new ID2D1RenderTarget, that means for *each* new + // target HDC, resp. OutputDevice + sal::systools::COMReference<ID2D1Bitmap> pID2D1Bitmap; + + if (aID2D1GlobalRenderTargetProvider.getID2D1DCRenderTarget()) + { + const HRESULT hr(aID2D1GlobalRenderTargetProvider.getID2D1DCRenderTarget()->CreateBitmap( + D2D1::SizeU(rSizePixel.Width(), rSizePixel.Height()), &aData[0], + rSizePixel.Width() * sizeof(sal_uInt32), + D2D1::BitmapProperties( + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, // DXGI_FORMAT + bAlpha ? D2D1_ALPHA_MODE_PREMULTIPLIED + : D2D1_ALPHA_MODE_IGNORE)), // D2D1_ALPHA_MODE + &pID2D1Bitmap)); + + if (!SUCCEEDED(hr)) + pID2D1Bitmap.clear(); + } + + return pID2D1Bitmap; +} + +sal::systools::COMReference<ID2D1Bitmap> +getOrCreateB2DBitmap(sal::systools::COMReference<ID2D1RenderTarget>& rRT, const BitmapEx& rBitmapEx) +{ + const basegfx::SystemDependentDataHolder* pHolder( + rBitmapEx.GetBitmap().accessSystemDependentDataHolder()); + std::shared_ptr<SystemDependentData_ID2D1Bitmap> pSystemDependentData_ID2D1Bitmap; + + if (nullptr != pHolder) + { + // try to access SystemDependentDataHolder and buffered data + pSystemDependentData_ID2D1Bitmap + = std::static_pointer_cast<SystemDependentData_ID2D1Bitmap>( + pHolder->getSystemDependentData( + typeid(SystemDependentData_ID2D1Bitmap).hash_code())); + + // check data validity for associated Alpha + if (pSystemDependentData_ID2D1Bitmap + && pSystemDependentData_ID2D1Bitmap->getAssociatedAlpha() + != rBitmapEx.GetAlphaMask().GetBitmap().ImplGetSalBitmap()) + { + // AssociatedAlpha did change, data invalid + pSystemDependentData_ID2D1Bitmap.reset(); + } + } + + if (!pSystemDependentData_ID2D1Bitmap) + { + // have to create newly + sal::systools::COMReference<ID2D1Bitmap> pID2D1Bitmap(createB2DBitmap(rBitmapEx)); + + if (pID2D1Bitmap) + { + // creation worked, create SystemDependentData_ID2D1Bitmap + pSystemDependentData_ID2D1Bitmap = std::make_shared<SystemDependentData_ID2D1Bitmap>( + pID2D1Bitmap, rBitmapEx.GetAlphaMask().GetBitmap().ImplGetSalBitmap()); + + // only add if feasible + if (nullptr != pHolder + && pSystemDependentData_ID2D1Bitmap->calculateCombinedHoldCyclesInSeconds() > 0) + { + basegfx::SystemDependentData_SharedPtr r2(pSystemDependentData_ID2D1Bitmap); + const_cast<basegfx::SystemDependentDataHolder*>(pHolder) + ->addOrReplaceSystemDependentData(r2); + } + } + } + + sal::systools::COMReference<ID2D1Bitmap> pWrappedD2DBitmap; + + if (pSystemDependentData_ID2D1Bitmap) + { + // embed to CreateSharedBitmap, that makes it usable on + // the specified RenderTarget + const HRESULT hr(rRT->CreateSharedBitmap( + __uuidof(ID2D1Bitmap), + static_cast<void*>(pSystemDependentData_ID2D1Bitmap->getID2D1Bitmap()), nullptr, + &pWrappedD2DBitmap)); + + if (!SUCCEEDED(hr)) + pWrappedD2DBitmap.clear(); + } + + return pWrappedD2DBitmap; +} + +// This is a simple local derivation of D2DPixelProcessor2D to be used +// when sub-content needs to be rendered to pixels. Hand over the adapted +// ViewInformation2D, a pixel size and the parent RenderTarget. It will +// locally create and use a ID2D1BitmapRenderTarget to render the stuff +// (you need to call process() with the primitives to be painted of +// course). Then use the local helper getID2D1Bitmap() to access the +// ID2D1Bitmap which was the target of that operation. +class D2DBitmapPixelProcessor2D final : public drawinglayer::processor2d::D2DPixelProcessor2D +{ + // the local ID2D1BitmapRenderTarget + sal::systools::COMReference<ID2D1BitmapRenderTarget> mpBitmapRenderTarget; + +public: + // helper class to create another instance of D2DPixelProcessor2D for + // creating helper-ID2D1Bitmap's for a given ID2D1RenderTarget + D2DBitmapPixelProcessor2D(const drawinglayer::geometry::ViewInformation2D& rViewInformation, + sal_uInt32 nWidth, sal_uInt32 nHeight, + const sal::systools::COMReference<ID2D1RenderTarget>& rParent) + : drawinglayer::processor2d::D2DPixelProcessor2D(rViewInformation) + , mpBitmapRenderTarget() + { + if (0 == nWidth || 0 == nHeight) + { + // no width/height, done + increaseError(); + } + + if (!hasError()) + { + // Allocate compatible RGBA render target + const D2D1_SIZE_U aRenderTargetSizePixel(D2D1::SizeU(nWidth, nHeight)); + const HRESULT hr(rParent->CreateCompatibleRenderTarget( + nullptr, &aRenderTargetSizePixel, nullptr, + D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, &mpBitmapRenderTarget)); + + if (!SUCCEEDED(hr) || nullptr == mpBitmapRenderTarget) + { + // did not work, done + increaseError(); + } + else + { + sal::systools::COMReference<ID2D1RenderTarget> pRT; + mpBitmapRenderTarget->QueryInterface(__uuidof(ID2D1RenderTarget), + reinterpret_cast<void**>(&pRT)); + setRenderTarget(pRT); + } + } + + if (hasRenderTarget()) + { + // set Viewort if none was given. We have a fixed pixel target, s we know the + // exact Viewport to work on + if (getViewInformation2D().getViewport().isEmpty()) + { + drawinglayer::geometry::ViewInformation2D aViewInformation(getViewInformation2D()); + basegfx::B2DRange aViewport(0.0, 0.0, nWidth, nHeight); + basegfx::B2DHomMatrix aInvViewTransform(aViewInformation.getViewTransformation()); + + aInvViewTransform.invert(); + aViewport.transform(aInvViewTransform); + aViewInformation.setViewport(aViewport); + updateViewInformation(aViewInformation); + } + + // clear as render preparation + getRenderTarget()->BeginDraw(); + getRenderTarget()->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f)); + getRenderTarget()->EndDraw(); + } + } + + sal::systools::COMReference<ID2D1Bitmap> getID2D1Bitmap() const + { + sal::systools::COMReference<ID2D1Bitmap> pResult; + + // access the resulting bitmap if exists + if (mpBitmapRenderTarget) + { + mpBitmapRenderTarget->GetBitmap(&pResult); + } + + return pResult; + } +}; + +bool createBitmapSubContent(sal::systools::COMReference<ID2D1Bitmap>& rResult, + basegfx::B2DRange& rDiscreteVisibleRange, + const drawinglayer::primitive2d::Primitive2DContainer& rContent, + const drawinglayer::geometry::ViewInformation2D& rViewInformation2D, + const sal::systools::COMReference<ID2D1RenderTarget>& rRenderTarget) +{ + if (rContent.empty() || !rRenderTarget) + { + // no content or no render target, done + return false; + } + + drawinglayer::processor2d::calculateDiscreteVisibleRange( + rDiscreteVisibleRange, rContent.getB2DRange(rViewInformation2D), rViewInformation2D); + + if (rDiscreteVisibleRange.isEmpty()) + { + // not visible, done + return false; + } + + // Use a temporary second instance of a D2DBitmapPixelProcessor2D with adapted + // ViewInformation2D, it will create the needed ID2D1BitmapRenderTarget + // locally and Clear() it. + drawinglayer::geometry::ViewInformation2D aAdaptedViewInformation2D(rViewInformation2D); + const double fTargetWidth(ceil(rDiscreteVisibleRange.getWidth())); + const double fTargetHeight(ceil(rDiscreteVisibleRange.getHeight())); + + { + // create adapted ViewTransform, needs to be offset in discrete coordinates, + // so multiply from left + basegfx::B2DHomMatrix aAdapted( + basegfx::utils::createTranslateB2DHomMatrix(-rDiscreteVisibleRange.getMinX(), + -rDiscreteVisibleRange.getMinY()) + * rViewInformation2D.getViewTransformation()); + aAdaptedViewInformation2D.setViewTransformation(aAdapted); + + // reset Viewport (world coordinates), so the helper renderer will create it's + // own based on it's given internal discrete size + aAdaptedViewInformation2D.setViewport(basegfx::B2DRange()); + } + + D2DBitmapPixelProcessor2D aSubContentRenderer(aAdaptedViewInformation2D, fTargetWidth, + fTargetHeight, rRenderTarget); + + if (!aSubContentRenderer.valid()) + { + // did not work, done + return false; + } + + // render sub-content recursively + aSubContentRenderer.process(rContent); + + // grab Bitmap & prepare results from RGBA content rendering + rResult = aSubContentRenderer.getID2D1Bitmap(); + return true; +} +} + +namespace drawinglayer::processor2d +{ +D2DPixelProcessor2D::D2DPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation) + : BaseProcessor2D(rViewInformation) + , maBColorModifierStack() + , mpRT() + , mnRecursionCounter(0) + , mnErrorCounter(0) +{ +} + +D2DPixelProcessor2D::D2DPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation, + HDC aHdc) + : BaseProcessor2D(rViewInformation) + , maBColorModifierStack() + , mpRT() + , mnRecursionCounter(0) + , mnErrorCounter(0) +{ + sal::systools::COMReference<ID2D1DCRenderTarget> pDCRT; + tools::Long aOutWidth(0), aOutHeight(0); + + if (aHdc) + { + aOutWidth = GetDeviceCaps(aHdc, HORZRES); + aOutHeight = GetDeviceCaps(aHdc, VERTRES); + } + + if (aOutWidth > 0 && aOutHeight > 0 && aID2D1GlobalFactoryProvider.getID2D1Factory()) + { + const D2D1_RENDER_TARGET_PROPERTIES aRTProps(D2D1::RenderTargetProperties( + D2D1_RENDER_TARGET_TYPE_DEFAULT, + D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, + D2D1_ALPHA_MODE_IGNORE), //D2D1_ALPHA_MODE_PREMULTIPLIED), + 0, 0, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT)); + + const HRESULT hr( + aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateDCRenderTarget(&aRTProps, &pDCRT)); + + if (!SUCCEEDED(hr)) + pDCRT.clear(); + } + + if (pDCRT) + { + const RECT rc( + { 0, 0, o3tl::narrowing<LONG>(aOutWidth), o3tl::narrowing<LONG>(aOutHeight) }); + const HRESULT hr(pDCRT->BindDC(aHdc, &rc)); + + if (!SUCCEEDED(hr)) + pDCRT.clear(); + } + + if (pDCRT) + { + if (rViewInformation.getUseAntiAliasing()) + { + D2D1_ANTIALIAS_MODE eAAMode = D2D1_ANTIALIAS_MODE_PER_PRIMITIVE; + pDCRT->SetAntialiasMode(eAAMode); + } + else + { + D2D1_ANTIALIAS_MODE eAAMode = D2D1_ANTIALIAS_MODE_ALIASED; + pDCRT->SetAntialiasMode(eAAMode); + } + + // since ID2D1DCRenderTarget depends on the transformation + // set at hdc, be careful and reset it to identity + XFORM aXForm; + aXForm.eM11 = 1.0; + aXForm.eM12 = 0.0; + aXForm.eM21 = 0.0; + aXForm.eM22 = 1.0; + aXForm.eDx = 0.0; + aXForm.eDy = 0.0; + SetWorldTransform(aHdc, &aXForm); + } + + if (pDCRT) + { + sal::systools::COMReference<ID2D1RenderTarget> pRT; + pDCRT->QueryInterface(__uuidof(ID2D1RenderTarget), reinterpret_cast<void**>(&pRT)); + setRenderTarget(pRT); + } + else + { + increaseError(); + } +} + +void D2DPixelProcessor2D::processPolygonHairlinePrimitive2D( + const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D) +{ + const basegfx::B2DPolygon& rPolygon(rPolygonHairlinePrimitive2D.getB2DPolygon()); + + if (!rPolygon.count()) + { + // no geometry, done + return; + } + + bool bDone(false); + std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry( + getOrCreatePathGeometry(rPolygon, getViewInformation2D())); + + if (pSystemDependentData_ID2D1PathGeometry) + { + sal::systools::COMReference<ID2D1TransformedGeometry> pTransformedGeometry; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateTransformedGeometry( + pSystemDependentData_ID2D1PathGeometry->getID2D1PathGeometry(), + D2D1::Matrix3x2F(rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e() + fAAOffset, + rObjectToView.f() + fAAOffset), + &pTransformedGeometry)); + + if (SUCCEEDED(hr) && pTransformedGeometry) + { + const basegfx::BColor aHairlineColor( + maBColorModifierStack.getModifiedColor(rPolygonHairlinePrimitive2D.getBColor())); + const D2D1::ColorF aD2DColor(aHairlineColor.getRed(), aHairlineColor.getGreen(), + aHairlineColor.getBlue()); + sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush; + hr = getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush); + + if (SUCCEEDED(hr) && pColorBrush) + { + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + // TODO: Unfortunately Direct2D paint of one pixel wide lines does not + // correctly and completely blend 100% over the background. Experimenting + // shows that a value around/slightly below 2.0 is needed which hints that + // alpha blending the half-shifted lines (see fAAOffset above) is involved. + // To get correct blending I try to use just wider hairlines for now. This + // may need to be improved - or balanced (trying sqrt(2) now...) + getRenderTarget()->DrawGeometry(pTransformedGeometry, pColorBrush, 1.44f); + bDone = true; + } + } + } + + if (!bDone) + increaseError(); +} + +bool D2DPixelProcessor2D::drawPolyPolygonColorTransformed( + const basegfx::B2DHomMatrix& rTansformation, const basegfx::B2DPolyPolygon& rPolyPolygon, + const basegfx::BColor& rColor) +{ + std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry( + getOrCreateFillGeometry(rPolyPolygon)); + + if (pSystemDependentData_ID2D1PathGeometry) + { + sal::systools::COMReference<ID2D1TransformedGeometry> pTransformedGeometry; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + basegfx::B2DHomMatrix aTansformation(getViewInformation2D().getObjectToViewTransformation() + * rTansformation); + HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateTransformedGeometry( + pSystemDependentData_ID2D1PathGeometry->getID2D1PathGeometry(), + D2D1::Matrix3x2F(aTansformation.a(), aTansformation.b(), aTansformation.c(), + aTansformation.d(), aTansformation.e() + fAAOffset, + aTansformation.f() + fAAOffset), + &pTransformedGeometry)); + + if (SUCCEEDED(hr) && pTransformedGeometry) + { + const basegfx::BColor aFillColor(maBColorModifierStack.getModifiedColor(rColor)); + const D2D1::ColorF aD2DColor(aFillColor.getRed(), aFillColor.getGreen(), + aFillColor.getBlue()); + + sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush; + hr = getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush); + + if (SUCCEEDED(hr) && pColorBrush) + { + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + getRenderTarget()->FillGeometry(pTransformedGeometry, pColorBrush); + return true; + } + } + } + + return false; +} + +void D2DPixelProcessor2D::processPolyPolygonColorPrimitive2D( + const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D) +{ + const basegfx::B2DPolyPolygon& rPolyPolygon(rPolyPolygonColorPrimitive2D.getB2DPolyPolygon()); + const sal_uInt32 nCount(rPolyPolygon.count()); + + if (!nCount) + { + // no geometry, done + return; + } + + const bool bDone(drawPolyPolygonColorTransformed(basegfx::B2DHomMatrix(), rPolyPolygon, + rPolyPolygonColorPrimitive2D.getBColor())); + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processBitmapPrimitive2D( + const primitive2d::BitmapPrimitive2D& rBitmapCandidate) +{ + // check if graphic content is inside discrete local ViewPort + if (!getViewInformation2D().getDiscreteViewport().isEmpty()) + { + // calculate logic object range, remember: the helper below will + // transform using getObjectToViewTransformation, so the bitmap-local + // transform would be missing + basegfx::B2DRange aDiscreteVisibleRange(basegfx::B2DRange::getUnitB2DRange()); + aDiscreteVisibleRange.transform(rBitmapCandidate.getTransform()); + + // calculate visible range + calculateDiscreteVisibleRange(aDiscreteVisibleRange, aDiscreteVisibleRange, + getViewInformation2D()); + + if (aDiscreteVisibleRange.isEmpty()) + { + // not visible, done + return; + } + } + + BitmapEx aBitmapEx(rBitmapCandidate.getBitmap()); + + if (aBitmapEx.IsEmpty() || aBitmapEx.GetSizePixel().IsEmpty()) + { + // no pixel data, done + return; + } + + if (maBColorModifierStack.count()) + { + // need to apply ColorModifier to Bitmap data + aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack); + + if (aBitmapEx.IsEmpty()) + { + // color gets completely replaced, get it (any input works) + const basegfx::BColor aModifiedColor( + maBColorModifierStack.getModifiedColor(basegfx::BColor())); + + // use unit geometry as fallback object geometry. Do *not* + // transform, the below used method will use the already + // correctly initialized local ViewInformation + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + + rtl::Reference<primitive2d::PolyPolygonColorPrimitive2D> aTemp( + new primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon), + aModifiedColor)); + + // draw as Polygon, done + processPolyPolygonColorPrimitive2D(*aTemp); + return; + } + } + + bool bDone(false); + sal::systools::COMReference<ID2D1Bitmap> pD2DBitmap( + getOrCreateB2DBitmap(getRenderTarget(), aBitmapEx)); + + if (pD2DBitmap) + { + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix aLocalTransform( + getViewInformation2D().getObjectToViewTransformation() + * rBitmapCandidate.getTransform()); + getRenderTarget()->SetTransform(D2D1::Matrix3x2F( + aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(), + aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset)); + + // destinationRectangle is part of transformation above, so use UnitRange + getRenderTarget()->DrawBitmap(pD2DBitmap, D2D1::RectF(0.0, 0.0, 1.0, 1.0)); + bDone = true; + } + + if (!bDone) + increaseError(); +} + +sal::systools::COMReference<ID2D1Bitmap> D2DPixelProcessor2D::implCreateAlpha_Direct( + const primitive2d::TransparencePrimitive2D& rTransCandidate) +{ + // Try if we can use ID2D1DeviceContext/d2d1_1 by querying for interface. + // Only then can we use ID2D1Effect/CLSID_D2D1LuminanceToAlpha and it makes + // sense to try to do it this way in this implementation + sal::systools::COMReference<ID2D1DeviceContext> pID2D1DeviceContext; + getRenderTarget()->QueryInterface(__uuidof(ID2D1DeviceContext), + reinterpret_cast<void**>(&pID2D1DeviceContext)); + sal::systools::COMReference<ID2D1Bitmap> pRetval; + + if (!pID2D1DeviceContext) + { + // no, done - tell caller to use fallback by returning empty - we have + // not the preconditions for this + return pRetval; + } + + // Release early + pID2D1DeviceContext.clear(); + basegfx::B2DRange aDiscreteVisibleRange; + + if (!createBitmapSubContent(pRetval, aDiscreteVisibleRange, rTransCandidate.getTransparence(), + getViewInformation2D(), getRenderTarget()) + || !pRetval) + { + // return of false means no display needed, return + return pRetval; + } + + // Now we need a target to render this to, using the ID2D1Effect tooling. + // We can directly apply the effect to an alpha-only 8bit target here, + // so create one (no RGBA needed for this). + // We need another render target: I tried to render pInBetweenResult + // to pContent again, but that does not work due to the bitmap + // fetched being probably only an internal reference to the + // ID2D1BitmapRenderTarget, thus it would draw onto itself -> chaos + sal::systools::COMReference<ID2D1BitmapRenderTarget> pContent; + const D2D1_PIXEL_FORMAT aAlphaFormat( + D2D1::PixelFormat(DXGI_FORMAT_A8_UNORM, D2D1_ALPHA_MODE_STRAIGHT)); + const D2D1_SIZE_U aRenderTargetSizePixel(D2D1::SizeU(ceil(aDiscreteVisibleRange.getWidth()), + ceil(aDiscreteVisibleRange.getHeight()))); + const HRESULT hr(getRenderTarget()->CreateCompatibleRenderTarget( + nullptr, &aRenderTargetSizePixel, &aAlphaFormat, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, + &pContent)); + + if (SUCCEEDED(hr) && pContent) + { + // try to access ID2D1DeviceContext of that target, we need that *now* + pContent->QueryInterface(__uuidof(ID2D1DeviceContext), + reinterpret_cast<void**>(&pID2D1DeviceContext)); + + if (pID2D1DeviceContext) + { + // create the effect + sal::systools::COMReference<ID2D1Effect> pLuminanceToAlpha; + pID2D1DeviceContext->CreateEffect(CLSID_D2D1LuminanceToAlpha, &pLuminanceToAlpha); + + if (pLuminanceToAlpha) + { + // chain effect stuff together & paint it + pLuminanceToAlpha->SetInput(0, pRetval); + + pID2D1DeviceContext->BeginDraw(); + pID2D1DeviceContext->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 0.0f)); + pID2D1DeviceContext->DrawImage(pLuminanceToAlpha); + pID2D1DeviceContext->EndDraw(); + + // grab result + pContent->GetBitmap(&pRetval); + } + } + } + + return pRetval; +} + +sal::systools::COMReference<ID2D1Bitmap> D2DPixelProcessor2D::implCreateAlpha_B2DBitmap( + const primitive2d::TransparencePrimitive2D& rTransCandidate, + const basegfx::B2DRange& rVisibleRange, D2D1_MATRIX_3X2_F& rMaskScale) +{ + // Use this fallback that will also use a pixel processor indirectly, + // but allows to get the AlphaMask as vcl Bitmap using existing tooling + const sal_uInt32 nDiscreteClippedWidth(ceil(rVisibleRange.getWidth())); + const sal_uInt32 nDiscreteClippedHeight(ceil(rVisibleRange.getHeight())); + const sal_uInt32 nMaximumQuadraticPixels(250000); + + // Embed content graphics to TransformPrimitive2D + const basegfx::B2DHomMatrix aAlphaEmbedding( + basegfx::utils::createTranslateB2DHomMatrix(-rVisibleRange.getMinX(), + -rVisibleRange.getMinY()) + * getViewInformation2D().getObjectToViewTransformation()); + const primitive2d::Primitive2DReference xAlphaEmbedRef(new primitive2d::TransformPrimitive2D( + aAlphaEmbedding, + drawinglayer::primitive2d::Primitive2DContainer(rTransCandidate.getTransparence()))); + drawinglayer::primitive2d::Primitive2DContainer xEmbedSeq{ xAlphaEmbedRef }; + + // use empty ViewInformation to have neutral transformation + const geometry::ViewInformation2D aEmptyViewInformation2D; + + // use new mode to create AlphaChannel (not just AlphaMask) for transparency channel + const AlphaMask aAlpha(::drawinglayer::createAlphaMask( + std::move(xEmbedSeq), aEmptyViewInformation2D, nDiscreteClippedWidth, + nDiscreteClippedHeight, nMaximumQuadraticPixels, true)); + sal::systools::COMReference<ID2D1Bitmap> pRetval; + + if (aAlpha.IsEmpty()) + { + // if we have no content we are done + return pRetval; + } + + // use alpha data to create the ID2D1Bitmap + const Size& rSizePixel(aAlpha.GetSizePixel()); + const sal_uInt32 nPixelCount(rSizePixel.Width() * rSizePixel.Height()); + std::unique_ptr<sal_uInt8[]> aData(new sal_uInt8[nPixelCount]); + sal_uInt8* pTarget = aData.get(); + Bitmap aSrcAlpha(aAlpha.GetBitmap()); + BitmapScopedReadAccess pReadAccess(aSrcAlpha); + const tools::Long nHeight(pReadAccess->Height()); + const tools::Long nWidth(pReadAccess->Width()); + + for (tools::Long y = 0; y < nHeight; ++y) + { + for (tools::Long x = 0; x < nWidth; ++x) + { + const BitmapColor aColor(pReadAccess->GetColor(y, x)); + *pTarget++ = aColor.GetLuminance(); + } + } + + const D2D1_BITMAP_PROPERTIES aBmProps(D2D1::BitmapProperties( + D2D1::PixelFormat(DXGI_FORMAT_A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED))); + const HRESULT hr(getRenderTarget()->CreateBitmap( + D2D1::SizeU(rSizePixel.Width(), rSizePixel.Height()), &aData[0], + rSizePixel.Width() * sizeof(sal_uInt8), &aBmProps, &pRetval)); + + if (!SUCCEEDED(hr) || !pRetval) + { + // did not work, done + return pRetval; + } + + // create needed adapted transformation for alpha brush. + // We may have to take a corrective scaling into account when the + // MaximumQuadraticPixel limit was used/triggered + const Size& rBitmapExSizePixel(aAlpha.GetSizePixel()); + + if (static_cast<sal_uInt32>(rBitmapExSizePixel.Width()) != nDiscreteClippedWidth + || static_cast<sal_uInt32>(rBitmapExSizePixel.Height()) != nDiscreteClippedHeight) + { + // scale in X and Y should be the same (see fReduceFactor in createAlphaMask), + // so adapt numerically to a single scale value, they are integer rounded values + const double fScaleX(static_cast<double>(rBitmapExSizePixel.Width()) + / static_cast<double>(nDiscreteClippedWidth)); + const double fScaleY(static_cast<double>(rBitmapExSizePixel.Height()) + / static_cast<double>(nDiscreteClippedHeight)); + + const double fScale(1.0 / ((fScaleX + fScaleY) * 0.5)); + rMaskScale = D2D1::Matrix3x2F::Scale(fScale, fScale); + } + + return pRetval; +} + +void D2DPixelProcessor2D::processTransparencePrimitive2D( + const primitive2d::TransparencePrimitive2D& rTransCandidate) +{ + if (rTransCandidate.getChildren().empty()) + { + // no content, done + return; + } + + if (rTransCandidate.getTransparence().empty()) + { + // no mask (so nothing visible), done + return; + } + + // calculate visible range, create only for that range + basegfx::B2DRange aDiscreteVisibleRange; + calculateDiscreteVisibleRange(aDiscreteVisibleRange, + rTransCandidate.getChildren().getB2DRange(getViewInformation2D()), + getViewInformation2D()); + + if (aDiscreteVisibleRange.isEmpty()) + { + // not visible, done + return; + } + + // try to create directly, this needs the current mpRT to be a ID2D1DeviceContext/d2d1_1 + // what is not guaranteed but usually works for more modern windows (after 7) + sal::systools::COMReference<ID2D1Bitmap> pAlphaBitmap(implCreateAlpha_Direct(rTransCandidate)); + D2D1_MATRIX_3X2_F aMaskScale(D2D1::Matrix3x2F::Identity()); + + if (!pAlphaBitmap) + { + // did not work, use more expensive fallback to existing tooling + pAlphaBitmap + = implCreateAlpha_B2DBitmap(rTransCandidate, aDiscreteVisibleRange, aMaskScale); + } + + if (!pAlphaBitmap) + { + // could not create alpha channel, error + increaseError(); + return; + } + + sal::systools::COMReference<ID2D1Layer> pLayer; + HRESULT hr(getRenderTarget()->CreateLayer(nullptr, &pLayer)); + bool bDone(false); + + if (SUCCEEDED(hr) && pLayer) + { + sal::systools::COMReference<ID2D1BitmapBrush> pBitmapBrush; + hr = getRenderTarget()->CreateBitmapBrush(pAlphaBitmap, &pBitmapBrush); + + if (SUCCEEDED(hr) && pBitmapBrush) + { + // apply MaskScale to Brush, maybe used if implCreateAlpha_B2DBitmap was needed + pBitmapBrush->SetTransform(aMaskScale); + + // need to set transform offset for Layer initialization, we work + // in discrete device coordinates + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Translation( + floor(aDiscreteVisibleRange.getMinX()), floor(aDiscreteVisibleRange.getMinY()))); + + getRenderTarget()->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), nullptr, + D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, + D2D1::Matrix3x2F::Identity(), 1.0, + pBitmapBrush), + pLayer); + + // ... but need to reset to paint content unchanged + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + + // draw content recursively + process(rTransCandidate.getChildren()); + + getRenderTarget()->PopLayer(); + bDone = true; + } + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processUnifiedTransparencePrimitive2D( + const primitive2d::UnifiedTransparencePrimitive2D& rTransCandidate) +{ + if (rTransCandidate.getChildren().empty()) + { + // no content, done + return; + } + + if (0.0 == rTransCandidate.getTransparence()) + { + // not transparent at all, use content + process(rTransCandidate.getChildren()); + return; + } + + if (rTransCandidate.getTransparence() < 0.0 || rTransCandidate.getTransparence() > 1.0) + { + // invalid transparence, done + return; + } + + // calculate visible range + basegfx::B2DRange aDiscreteVisibleRange; + calculateDiscreteVisibleRange(aDiscreteVisibleRange, + rTransCandidate.getChildren().getB2DRange(getViewInformation2D()), + getViewInformation2D()); + + if (aDiscreteVisibleRange.isEmpty()) + { + // not visible, done + return; + } + + bool bDone(false); + sal::systools::COMReference<ID2D1Layer> pLayer; + const HRESULT hr(getRenderTarget()->CreateLayer(nullptr, &pLayer)); + + if (SUCCEEDED(hr) && pLayer) + { + // need to set correct transform for Layer initialization, we work + // in discrete device coordinates + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + getRenderTarget()->PushLayer( + D2D1::LayerParameters(D2D1::InfiniteRect(), nullptr, D2D1_ANTIALIAS_MODE_PER_PRIMITIVE, + D2D1::IdentityMatrix(), + 1.0 - rTransCandidate.getTransparence()), // opacity + pLayer); + process(rTransCandidate.getChildren()); + getRenderTarget()->PopLayer(); + bDone = true; + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processMaskPrimitive2DPixel( + const primitive2d::MaskPrimitive2D& rMaskCandidate) +{ + if (rMaskCandidate.getChildren().empty()) + { + // no content, done + return; + } + + basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask()); + + if (!aMask.count()) + { + // no mask (so nothing inside), done + return; + } + + // calculate visible range + basegfx::B2DRange aDiscreteVisibleRange; + calculateDiscreteVisibleRange(aDiscreteVisibleRange, aMask.getB2DRange(), + getViewInformation2D()); + + if (aDiscreteVisibleRange.isEmpty()) + { + // not visible, done + return; + } + + bool bDone(false); + std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1MaskGeometry( + getOrCreateFillGeometry(rMaskCandidate.getMask())); + + if (pSystemDependentData_ID2D1MaskGeometry) + { + sal::systools::COMReference<ID2D1TransformedGeometry> pTransformedMaskGeometry; + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateTransformedGeometry( + pSystemDependentData_ID2D1MaskGeometry->getID2D1PathGeometry(), + D2D1::Matrix3x2F(rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e(), rObjectToView.f()), + &pTransformedMaskGeometry)); + + if (SUCCEEDED(hr) && pTransformedMaskGeometry) + { + sal::systools::COMReference<ID2D1Layer> pLayer; + hr = getRenderTarget()->CreateLayer(nullptr, &pLayer); + + if (SUCCEEDED(hr) && pLayer) + { + // need to set correct transform for Layer initialization, we work + // in discrete device coordinates + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + getRenderTarget()->PushLayer( + D2D1::LayerParameters(D2D1::InfiniteRect(), pTransformedMaskGeometry), pLayer); + process(rMaskCandidate.getChildren()); + getRenderTarget()->PopLayer(); + bDone = true; + } + } + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processPointArrayPrimitive2D( + const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate) +{ + const std::vector<basegfx::B2DPoint>& rPositions(rPointArrayCandidate.getPositions()); + + if (rPositions.empty()) + { + // no geometry, done + return; + } + + const basegfx::BColor aPointColor( + maBColorModifierStack.getModifiedColor(rPointArrayCandidate.getRGBColor())); + sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush; + D2D1::ColorF aD2DColor(aPointColor.getRed(), aPointColor.getGreen(), aPointColor.getBlue()); + const HRESULT hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush)); + bool bDone(false); + + if (SUCCEEDED(hr) && pColorBrush) + { + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + + // To really paint a single pixel I found nothing better than + // switch off AA and draw a pixel-aligned rectangle + const D2D1_ANTIALIAS_MODE aOldAAMode(getRenderTarget()->GetAntialiasMode()); + getRenderTarget()->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); + + for (auto const& pos : rPositions) + { + const basegfx::B2DPoint aDiscretePos( + getViewInformation2D().getObjectToViewTransformation() * pos); + const double fX(ceil(aDiscretePos.getX())); + const double fY(ceil(aDiscretePos.getY())); + const D2D1_RECT_F rect = { FLOAT(fX), FLOAT(fY), FLOAT(fX), FLOAT(fY) }; + + getRenderTarget()->DrawRectangle(&rect, pColorBrush); + } + + getRenderTarget()->SetAntialiasMode(aOldAAMode); + bDone = true; + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processMarkerArrayPrimitive2D( + const primitive2d::MarkerArrayPrimitive2D& rMarkerArrayCandidate) +{ + const std::vector<basegfx::B2DPoint>& rPositions(rMarkerArrayCandidate.getPositions()); + + if (rPositions.empty()) + { + // no geometry, done + return; + } + + const BitmapEx& rMarker(rMarkerArrayCandidate.getMarker()); + + if (rMarker.IsEmpty()) + { + // no marker defined, done + return; + } + + sal::systools::COMReference<ID2D1Bitmap> pD2DBitmap( + getOrCreateB2DBitmap(getRenderTarget(), rMarker)); + bool bDone(false); + + if (pD2DBitmap) + { + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + const Size& rSizePixel(rMarker.GetSizePixel()); + const tools::Long nMiX((rSizePixel.Width() / 2) + 1); + const tools::Long nMiY((rSizePixel.Height() / 2) + 1); + const tools::Long nPlX(rSizePixel.Width() - nMiX); + const tools::Long nPlY(rSizePixel.Height() - nMiY); + + // draw with non-AA to show unhampered, clear, non-scaled marker + const D2D1_ANTIALIAS_MODE aOldAAMode(getRenderTarget()->GetAntialiasMode()); + getRenderTarget()->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); + + for (auto const& pos : rPositions) + { + const basegfx::B2DPoint aDiscretePos( + getViewInformation2D().getObjectToViewTransformation() * pos); + const double fX(ceil(aDiscretePos.getX())); + const double fY(ceil(aDiscretePos.getY())); + const D2D1_RECT_F rect + = { FLOAT(fX - nMiX), FLOAT(fY - nMiY), FLOAT(fX + nPlX), FLOAT(fY + nPlY) }; + + getRenderTarget()->DrawBitmap(pD2DBitmap, &rect); + } + + getRenderTarget()->SetAntialiasMode(aOldAAMode); + bDone = true; + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processBackgroundColorPrimitive2D( + const primitive2d::BackgroundColorPrimitive2D& rBackgroundColorCandidate) +{ + // check for allowed range [0.0 .. 1.0[ + if (rBackgroundColorCandidate.getTransparency() < 0.0 + || rBackgroundColorCandidate.getTransparency() >= 1.0) + return; + + const D2D1::ColorF aD2DColor(rBackgroundColorCandidate.getBColor().getRed(), + rBackgroundColorCandidate.getBColor().getGreen(), + rBackgroundColorCandidate.getBColor().getBlue(), + 1.0 - rBackgroundColorCandidate.getTransparency()); + + getRenderTarget()->Clear(aD2DColor); +} + +void D2DPixelProcessor2D::processModifiedColorPrimitive2D( + const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate) +{ + if (!rModifiedCandidate.getChildren().empty()) + { + maBColorModifierStack.push(rModifiedCandidate.getColorModifier()); + process(rModifiedCandidate.getChildren()); + maBColorModifierStack.pop(); + } +} + +void D2DPixelProcessor2D::processTransformPrimitive2D( + const primitive2d::TransformPrimitive2D& rTransformCandidate) +{ + // remember current transformation and ViewInformation + const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); + + // create new transformations for local ViewInformation2D + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() + * rTransformCandidate.getTransformation()); + updateViewInformation(aViewInformation2D); + + // process content + process(rTransformCandidate.getChildren()); + + // restore transformations + updateViewInformation(aLastViewInformation2D); +} + +void D2DPixelProcessor2D::processPolygonStrokePrimitive2D( + const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate) +{ + const basegfx::B2DPolygon& rPolygon(rPolygonStrokeCandidate.getB2DPolygon()); + const attribute::LineAttribute& rLineAttribute(rPolygonStrokeCandidate.getLineAttribute()); + + if (!rPolygon.count() || rLineAttribute.getWidth() < 0.0) + { + // no geometry, done + return; + } + + // get some values early that might be used for decisions + const bool bHairline(0.0 == rLineAttribute.getWidth()); + const basegfx::B2DHomMatrix& rObjectToView( + getViewInformation2D().getObjectToViewTransformation()); + const double fDiscreteLineWidth( + bHairline + ? 1.0 + : (rObjectToView * basegfx::B2DVector(rLineAttribute.getWidth(), 0.0)).getLength()); + + // Here for every combination which the system-specific implementation is not + // capable of visualizing, use the (for decomposable Primitives always possible) + // fallback to the decomposition. + if (basegfx::B2DLineJoin::NONE == rLineAttribute.getLineJoin() && fDiscreteLineWidth > 1.5) + { + // basegfx::B2DLineJoin::NONE is special for our office, no other GraphicSystem + // knows that (so far), so fallback to decomposition. This is only needed if + // LineJoin will be used, so also check for discrete LineWidth before falling back + process(rPolygonStrokeCandidate); + return; + } + + // This is a method every system-specific implementation of a decomposable Primitive + // can use to allow simple optical control of paint implementation: + // Create a copy, e.g. change color to 'red' as here and paint before the system + // paints it using the decomposition. That way you can - if active - directly + // optically compare if the system-specific solution is geometrically identical to + // the decomposition (which defines our interpretation that we need to visualize). + // Look below in the impl for bRenderDecomposeForCompareInRed to see that in that case + // we create a half-transparent paint to better support visual control + static bool bRenderDecomposeForCompareInRed(false); + + if (bRenderDecomposeForCompareInRed) + { + const attribute::LineAttribute aRed( + basegfx::BColor(1.0, 0.0, 0.0), rLineAttribute.getWidth(), rLineAttribute.getLineJoin(), + rLineAttribute.getLineCap(), rLineAttribute.getMiterMinimumAngle()); + rtl::Reference<primitive2d::PolygonStrokePrimitive2D> aCopy( + new primitive2d::PolygonStrokePrimitive2D( + rPolygonStrokeCandidate.getB2DPolygon(), aRed, + rPolygonStrokeCandidate.getStrokeAttribute())); + process(*aCopy); + } + + bool bDone(false); + std::shared_ptr<SystemDependentData_ID2D1PathGeometry> pSystemDependentData_ID2D1PathGeometry( + getOrCreatePathGeometry(rPolygon, getViewInformation2D())); + + if (pSystemDependentData_ID2D1PathGeometry) + { + sal::systools::COMReference<ID2D1TransformedGeometry> pTransformedGeometry; + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + HRESULT hr(aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateTransformedGeometry( + pSystemDependentData_ID2D1PathGeometry->getID2D1PathGeometry(), + D2D1::Matrix3x2F(rObjectToView.a(), rObjectToView.b(), rObjectToView.c(), + rObjectToView.d(), rObjectToView.e() + fAAOffset, + rObjectToView.f() + fAAOffset), + &pTransformedGeometry)); + + if (SUCCEEDED(hr) && pTransformedGeometry) + { + const basegfx::BColor aLineColor( + maBColorModifierStack.getModifiedColor(rLineAttribute.getColor())); + D2D1::ColorF aD2DColor(aLineColor.getRed(), aLineColor.getGreen(), + aLineColor.getBlue()); + + if (bRenderDecomposeForCompareInRed) + { + aD2DColor.a = 0.5; + } + + sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush; + hr = getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush); + + if (SUCCEEDED(hr) && pColorBrush) + { + sal::systools::COMReference<ID2D1StrokeStyle> pStrokeStyle; + D2D1_CAP_STYLE aCapStyle(D2D1_CAP_STYLE_FLAT); + D2D1_LINE_JOIN aLineJoin(D2D1_LINE_JOIN_MITER); + const attribute::StrokeAttribute& rStrokeAttribute( + rPolygonStrokeCandidate.getStrokeAttribute()); + const bool bDashUsed(!rStrokeAttribute.isDefault() + && !rStrokeAttribute.getDotDashArray().empty() + && 0.0 < rStrokeAttribute.getFullDotDashLen()); + D2D1_DASH_STYLE aDashStyle(bDashUsed ? D2D1_DASH_STYLE_CUSTOM + : D2D1_DASH_STYLE_SOLID); + std::vector<float> dashes; + float miterLimit(1.0); + + switch (rLineAttribute.getLineCap()) + { + case css::drawing::LineCap_ROUND: + aCapStyle = D2D1_CAP_STYLE_ROUND; + break; + case css::drawing::LineCap_SQUARE: + aCapStyle = D2D1_CAP_STYLE_SQUARE; + break; + default: + break; + } + + switch (rLineAttribute.getLineJoin()) + { + case basegfx::B2DLineJoin::NONE: + break; + case basegfx::B2DLineJoin::Bevel: + aLineJoin = D2D1_LINE_JOIN_BEVEL; + break; + case basegfx::B2DLineJoin::Miter: + { + // for basegfx::B2DLineJoin::Miter there are two problems: + // (1) MS uses D2D1_LINE_JOIN_MITER which handles the cut-off when MiterLimit is hit not by + // fallback to Bevel, but by cutting miter geometry at the defined distance. That is + // nice, but not what we need or is the standard for other graphic systems. Luckily there + // is also D2D1_LINE_JOIN_MITER_OR_BEVEL and (after some search) the page + // https://learn.microsoft.com/en-us/windows/win32/api/d2d1/ne-d2d1-d2d1_line_join + // which gives some explanation, so that is what we need to use here. + // (2) Instead of using an angle in radians (15 deg default) MS uses + // "miterLimit is relative to 1/2 LineWidth", so a length. After some experimenting + // it shows that the (better understandable) angle has to be converted to the length + // that a miter prolongation would have at that angle, so use some trigonometry. + // Unfortunately there is also some'precision' problem (probably), so I had to + // experimentally come to a correction value around 0.9925. Since that seems to + // be no obvious numerical value involved somehow (and as long as I find no other + // explanation) I will have to use that. + // NOTE: To find that correction value I usd that handy bRenderDecomposeForCompareInRed + // and changes in debugger - as work tipp + // With both done I can use Direct2D for Miter completely - what is good for speed. + aLineJoin = D2D1_LINE_JOIN_MITER_OR_BEVEL; + + // snap absolute value of angle in radians to [0.0 .. PI] + double fVal(::basegfx::snapToZeroRange( + fabs(rLineAttribute.getMiterMinimumAngle()), M_PI)); + + // cut at 0.0 and PI since sin would be zero ('endless' miter) + const double fSmallValue(M_PI * 0.0000001); + fVal = std::max(fSmallValue, fVal); + fVal = std::min(M_PI - fSmallValue, fVal); + + // get relative length + fVal = 1.0 / sin(fVal); + + // use for miterLimit, we need factor 2.0 (relative to double LineWidth) + // and the correction mentioned in (2) above + const double fCorrector(2.0 * 0.9925); + + miterLimit = fVal * fCorrector; + break; + } + case basegfx::B2DLineJoin::Round: + aLineJoin = D2D1_LINE_JOIN_ROUND; + break; + default: + break; + } + + if (bDashUsed) + { + // dashes need to be discrete and relative to LineWidth + for (auto& value : rStrokeAttribute.getDotDashArray()) + { + dashes.push_back( + (rObjectToView * basegfx::B2DVector(value, 0.0)).getLength() + / fDiscreteLineWidth); + } + } + + hr = aID2D1GlobalFactoryProvider.getID2D1Factory()->CreateStrokeStyle( + D2D1::StrokeStyleProperties(aCapStyle, // startCap + aCapStyle, // endCap + aCapStyle, // dashCap + aLineJoin, // lineJoin + miterLimit, // miterLimit + aDashStyle, // dashStyle + 0.0f), // dashOffset + bDashUsed ? dashes.data() : nullptr, bDashUsed ? dashes.size() : 0, + &pStrokeStyle); + + if (SUCCEEDED(hr) && pStrokeStyle) + { + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + getRenderTarget()->DrawGeometry( + pTransformedGeometry, pColorBrush, + // TODO: Hairline LineWidth, see comment at processPolygonHairlinePrimitive2D + bHairline ? 1.44 : fDiscreteLineWidth, pStrokeStyle); + bDone = true; + } + } + } + } + + if (!bDone) + { + // fallback to decomposition + process(rPolygonStrokeCandidate); + } +} + +void D2DPixelProcessor2D::processLineRectanglePrimitive2D( + const primitive2d::LineRectanglePrimitive2D& rLineRectanglePrimitive2D) +{ + if (rLineRectanglePrimitive2D.getB2DRange().isEmpty()) + { + // no geometry, done + return; + } + + const basegfx::BColor aHairlineColor( + maBColorModifierStack.getModifiedColor(rLineRectanglePrimitive2D.getBColor())); + const D2D1::ColorF aD2DColor(aHairlineColor.getRed(), aHairlineColor.getGreen(), + aHairlineColor.getBlue()); + sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush; + const HRESULT hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush)); + bool bDone(false); + + if (SUCCEEDED(hr) && pColorBrush) + { + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix aLocalTransform( + getViewInformation2D().getObjectToViewTransformation()); + getRenderTarget()->SetTransform(D2D1::Matrix3x2F( + aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(), + aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset)); + const basegfx::B2DRange& rRange(rLineRectanglePrimitive2D.getB2DRange()); + const D2D1_RECT_F rect = { FLOAT(rRange.getMinX()), FLOAT(rRange.getMinY()), + FLOAT(rRange.getMaxX()), FLOAT(rRange.getMaxY()) }; + const double fDiscreteLineWidth( + (getViewInformation2D().getInverseObjectToViewTransformation() + * basegfx::B2DVector(1.44, 0.0)) + .getLength()); + + getRenderTarget()->DrawRectangle(&rect, pColorBrush, fDiscreteLineWidth); + bDone = true; + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processFilledRectanglePrimitive2D( + const primitive2d::FilledRectanglePrimitive2D& rFilledRectanglePrimitive2D) +{ + if (rFilledRectanglePrimitive2D.getB2DRange().isEmpty()) + { + // no geometry, done + return; + } + + const basegfx::BColor aFillColor( + maBColorModifierStack.getModifiedColor(rFilledRectanglePrimitive2D.getBColor())); + const D2D1::ColorF aD2DColor(aFillColor.getRed(), aFillColor.getGreen(), aFillColor.getBlue()); + sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush; + const HRESULT hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush)); + bool bDone(false); + + if (SUCCEEDED(hr) && pColorBrush) + { + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix aLocalTransform( + getViewInformation2D().getObjectToViewTransformation()); + getRenderTarget()->SetTransform(D2D1::Matrix3x2F( + aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(), + aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset)); + const basegfx::B2DRange& rRange(rFilledRectanglePrimitive2D.getB2DRange()); + const D2D1_RECT_F rect = { FLOAT(rRange.getMinX()), FLOAT(rRange.getMinY()), + FLOAT(rRange.getMaxX()), FLOAT(rRange.getMaxY()) }; + + getRenderTarget()->FillRectangle(&rect, pColorBrush); + bDone = true; + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processSingleLinePrimitive2D( + const primitive2d::SingleLinePrimitive2D& rSingleLinePrimitive2D) +{ + const basegfx::BColor aLineColor( + maBColorModifierStack.getModifiedColor(rSingleLinePrimitive2D.getBColor())); + const D2D1::ColorF aD2DColor(aLineColor.getRed(), aLineColor.getGreen(), aLineColor.getBlue()); + sal::systools::COMReference<ID2D1SolidColorBrush> pColorBrush; + const HRESULT hr(getRenderTarget()->CreateSolidColorBrush(aD2DColor, &pColorBrush)); + bool bDone(false); + + if (SUCCEEDED(hr) && pColorBrush) + { + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + basegfx::B2DHomMatrix aLocalTransform( + getViewInformation2D().getObjectToViewTransformation()); + const basegfx::B2DPoint aStart(aLocalTransform * rSingleLinePrimitive2D.getStart()); + const basegfx::B2DPoint aEnd(aLocalTransform * rSingleLinePrimitive2D.getEnd()); + + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + const D2D1_POINT_2F aD2D1Start + = { FLOAT(aStart.getX() + fAAOffset), FLOAT(aStart.getY() + fAAOffset) }; + const D2D1_POINT_2F aD2D1End + = { FLOAT(aEnd.getX() + fAAOffset), FLOAT(aEnd.getY() + fAAOffset) }; + + getRenderTarget()->DrawLine(aD2D1Start, aD2D1End, pColorBrush, 1.44f); + bDone = true; + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processFillGraphicPrimitive2D( + const primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D) +{ + BitmapEx aPreparedBitmap; + basegfx::B2DRange aFillUnitRange(rFillGraphicPrimitive2D.getFillGraphic().getGraphicRange()); + static double fBigDiscreteArea(300.0 * 300.0); + + // use tooling to do various checks and prepare tiled rendering, see + // description of method, parameters and return value there + if (!prepareBitmapForDirectRender(rFillGraphicPrimitive2D, getViewInformation2D(), + aPreparedBitmap, aFillUnitRange, fBigDiscreteArea)) + { + // no output needed, done + return; + } + + if (aPreparedBitmap.IsEmpty()) + { + // output needed and Bitmap data empty, so no bitmap data based + // tiled rendering is suggested. Use fallback for paint (decomposition) + process(rFillGraphicPrimitive2D); + return; + } + + // render tiled using the prepared Bitmap data + if (maBColorModifierStack.count()) + { + // need to apply ColorModifier to Bitmap data + aPreparedBitmap = aPreparedBitmap.ModifyBitmapEx(maBColorModifierStack); + + if (aPreparedBitmap.IsEmpty()) + { + // color gets completely replaced, get it (any input works) + const basegfx::BColor aModifiedColor( + maBColorModifierStack.getModifiedColor(basegfx::BColor())); + + // use unit geometry as fallback object geometry. Do *not* + // transform, the below used method will use the already + // correctly initialized local ViewInformation + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + + // what we still need to apply is the object transform from the + // local primitive, that is not part of DisplayInfo yet + aPolygon.transform(rFillGraphicPrimitive2D.getTransformation()); + + rtl::Reference<primitive2d::PolyPolygonColorPrimitive2D> aTemp( + new primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon), + aModifiedColor)); + + // draw as colored Polygon, done + processPolyPolygonColorPrimitive2D(*aTemp); + return; + } + } + + bool bDone(false); + sal::systools::COMReference<ID2D1Bitmap> pD2DBitmap( + getOrCreateB2DBitmap(getRenderTarget(), aPreparedBitmap)); + + if (pD2DBitmap) + { + sal::systools::COMReference<ID2D1BitmapBrush> pBitmapBrush; + const HRESULT hr(getRenderTarget()->CreateBitmapBrush(pD2DBitmap, &pBitmapBrush)); + + if (SUCCEEDED(hr) && pBitmapBrush) + { + // set extended to repeat/wrap AKA tiling + pBitmapBrush->SetExtendModeX(D2D1_EXTEND_MODE_WRAP); + pBitmapBrush->SetExtendModeY(D2D1_EXTEND_MODE_WRAP); + + // set interpolation mode + // NOTE: This uses D2D1_BITMAP_INTERPOLATION_MODE, but there seem to be + // advanced modes when using D2D1_INTERPOLATION_MODE, but that needs + // D2D1_BITMAP_BRUSH_PROPERTIES1 and ID2D1BitmapBrush1 + sal::systools::COMReference<ID2D1BitmapBrush1> pBrush1; + pBitmapBrush->QueryInterface(__uuidof(ID2D1BitmapBrush1), + reinterpret_cast<void**>(&pBrush1)); + + if (pBrush1) + { + pBrush1->SetInterpolationMode1(D2D1_INTERPOLATION_MODE_MULTI_SAMPLE_LINEAR); + } + else + { + pBitmapBrush->SetInterpolationMode(D2D1_BITMAP_INTERPOLATION_MODE_LINEAR); + } + + // set BitmapBrush transformation relative to it's PixelSize and + // the used FillUnitRange. Since we use unit coordinates here this + // is pretty simple + const D2D1_SIZE_U aBMSPixel(pD2DBitmap->GetPixelSize()); + const double fScaleX((aFillUnitRange.getMaxX() - aFillUnitRange.getMinX()) + / aBMSPixel.width); + const double fScaleY((aFillUnitRange.getMaxY() - aFillUnitRange.getMinY()) + / aBMSPixel.height); + const D2D1_MATRIX_3X2_F aBTrans(D2D1::Matrix3x2F( + fScaleX, 0.0, 0.0, fScaleY, aFillUnitRange.getMinX(), aFillUnitRange.getMinY())); + pBitmapBrush->SetTransform(&aBTrans); + + // set transform to ObjectToWorld to be able to paint in unit coordinates, so + // evtl. shear/rotate in that transform is used and does not influence the + // orthogonal and unit-oriented brush handling + const double fAAOffset(getViewInformation2D().getUseAntiAliasing() ? 0.5 : 0.0); + const basegfx::B2DHomMatrix aLocalTransform( + getViewInformation2D().getObjectToViewTransformation() + * rFillGraphicPrimitive2D.getTransformation()); + getRenderTarget()->SetTransform(D2D1::Matrix3x2F( + aLocalTransform.a(), aLocalTransform.b(), aLocalTransform.c(), aLocalTransform.d(), + aLocalTransform.e() + fAAOffset, aLocalTransform.f() + fAAOffset)); + + // use unit rectangle, transformation is already set to include ObjectToWorld + const D2D1_RECT_F rect = { FLOAT(0.0), FLOAT(0.0), FLOAT(1.0), FLOAT(1.0) }; + + // draw as unit rectangle as brush filled rectangle + getRenderTarget()->FillRectangle(&rect, pBitmapBrush); + bDone = true; + } + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processFillGradientPrimitive2D( + const primitive2d::FillGradientPrimitive2D& rFillGradientPrimitive2D) +{ + // draw all-covering initial BG polygon 1st + bool bDone(drawPolyPolygonColorTransformed( + basegfx::B2DHomMatrix(), + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect(rFillGradientPrimitive2D.getOutputRange())), + rFillGradientPrimitive2D.getOuterColor())); + + if (bDone) + { + const basegfx::B2DPolyPolygon aForm(rFillGradientPrimitive2D.getUnitPolygon()); + + // paint solid fill steps by providing callback as lambda + auto aCallback([&aForm, &bDone, this](const basegfx::B2DHomMatrix& rMatrix, + const basegfx::BColor& rColor) { + if (bDone) + { + bDone = drawPolyPolygonColorTransformed(rMatrix, aForm, rColor); + } + }); + + // call value generator to trigger callbacks + rFillGradientPrimitive2D.generateMatricesAndColors(aCallback); + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processInvertPrimitive2D( + const primitive2d::InvertPrimitive2D& rInvertPrimitive2D) +{ + if (rInvertPrimitive2D.getChildren().empty()) + { + // no content, done + return; + } + + // Try if we can use ID2D1DeviceContext/d2d1_1 by querying for interface. + // Only with ID2D1DeviceContext we can use ::DrawImage which supports + // D2D1_COMPOSITE_MODE_XOR + sal::systools::COMReference<ID2D1DeviceContext> pID2D1DeviceContext; + getRenderTarget()->QueryInterface(__uuidof(ID2D1DeviceContext), + reinterpret_cast<void**>(&pID2D1DeviceContext)); + + if (!pID2D1DeviceContext) + { + // TODO: We have *no* ID2D1DeviceContext and cannot use D2D1_COMPOSITE_MODE_XOR, + // so there is currently no (simple?) way to solve this, there is no 'Invert' method. + // It may be possible to convert to a WICBitmap (gets read access) and do the invert + // there, but that needs experimenting and is probably not performant - but doable. + increaseError(); + return; + } + + sal::systools::COMReference<ID2D1Bitmap> pInBetweenResult; + basegfx::B2DRange aDiscreteVisibleRange; + + // create in-between result in discrete coordinates, clipped against visible + // part of ViewInformation (if available) + if (!createBitmapSubContent(pInBetweenResult, aDiscreteVisibleRange, + rInvertPrimitive2D.getChildren(), getViewInformation2D(), + getRenderTarget())) + { + // return of false means no display needed, return + return; + } + + bool bDone(false); + + if (pInBetweenResult) + { + getRenderTarget()->SetTransform(D2D1::Matrix3x2F::Identity()); + const D2D1_POINT_2F aTopLeft = { FLOAT(floor(aDiscreteVisibleRange.getMinX())), + FLOAT(floor(aDiscreteVisibleRange.getMinY())) }; + + pID2D1DeviceContext->DrawImage(pInBetweenResult, aTopLeft, D2D1_INTERPOLATION_MODE_LINEAR, + D2D1_COMPOSITE_MODE_XOR); + bDone = true; + } + + if (!bDone) + increaseError(); +} + +void D2DPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) +{ + if (0 == mnRecursionCounter) + getRenderTarget()->BeginDraw(); + mnRecursionCounter++; + + switch (rCandidate.getPrimitive2DID()) + { + // Geometry that *has* to be processed + // + // These Primitives have *no* decompose implementation, so these are the basic ones + // Just four to go to make a processor work completely (but not optimized) + // NOTE: This *could* theoretically be reduced to one and all could implement + // a decompose to pixel data, but that seemed not to make sense to me when + // I designed this. Thus these four are the lowest-level best representation + // from my POV + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D: + { + processBitmapPrimitive2D( + static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D: + { + processPointArrayPrimitive2D( + static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D: + { + processPolygonHairlinePrimitive2D( + static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D: + { + processPolyPolygonColorPrimitive2D( + static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate)); + break; + } + + // Embedding/groups that *have* to be processed + // + // These represent qualifiers for freely defined content, e.g. making + // any combination of primitives freely transformed or transparent + // NOTE: PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D and + // PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D are pretty much default- + // implementations that can and are re-used in all processors. + // So - with these and PRIMITIVE2D_ID_INVERTPRIMITIVE2D marked to + // be removed in the future - just three really to be implemented + // locally specifically + case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D: + { + processTransparencePrimitive2D( + static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_INVERTPRIMITIVE2D: + { + // We urgently should get rid of XOR paint, modern graphic systems + // allow no read access to the pixel targets, but that's naturally + // a precondition for XOR. While we can do that for the office's + // visualization, we can in principle *not* fully avoid getting + // stuff that needs/defines XOR paint, e.g. EMF/WMF imports, so + // we *have* to support it (for now - sigh)... + processInvertPrimitive2D( + static_cast<const primitive2d::InvertPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MASKPRIMITIVE2D: + { + processMaskPrimitive2DPixel( + static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D: + { + processModifiedColorPrimitive2D( + static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D: + { + processTransformPrimitive2D( + static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate)); + break; + } + + // Geometry that *may* be processed due to being able to do it better + // then using the decomposition. + // NOTE: In these implementations you could always call what the default + // case below does - call process(rCandidate) to use the decomposition. + // So these impls should only do something if they can do it better/ + // faster that the decomposition. So some of them check if they could + // - and if not - use exactly that. + case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D: + { + // transparence with a fixed alpha for all content, can be done + // significally faster + processUnifiedTransparencePrimitive2D( + static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D: + { + // can be done simpler and without AA better locally + processMarkerArrayPrimitive2D( + static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D: + { + // reset to a color, can be done more effectively locally, would + // else decompose to a polygon fill + processBackgroundColorPrimitive2D( + static_cast<const primitive2d::BackgroundColorPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D: + { + // fat and stroked lines - much better doable locally, would decompose + // to the full line geometry creation (tessellation) + processPolygonStrokePrimitive2D( + static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_LINERECTANGLEPRIMITIVE2D: + { + // simple primitive to support future fast callbacks from OutputDevice + // (see 'Example POC' in Gerrit), decomposes to polygon primitive + processLineRectanglePrimitive2D( + static_cast<const primitive2d::LineRectanglePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_FILLEDRECTANGLEPRIMITIVE2D: + { + // simple primitive to support future fast callbacks from OutputDevice + // (see 'Example POC' in Gerrit), decomposes to filled polygon primitive + processFilledRectanglePrimitive2D( + static_cast<const primitive2d::FilledRectanglePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_SINGLELINEPRIMITIVE2D: + { + // simple primitive to support future fast callbacks from OutputDevice + // (see 'Example POC' in Gerrit), decomposes to polygon primitive + processSingleLinePrimitive2D( + static_cast<const primitive2d::SingleLinePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D: + { + processFillGraphicPrimitive2D( + static_cast<const primitive2d::FillGraphicPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D: + { + processFillGradientPrimitive2D( + static_cast<const primitive2d::FillGradientPrimitive2D&>(rCandidate)); + break; + } + + // continue with decompose as fallback + default: + { + SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString( + rCandidate.getPrimitive2DID())); + // process recursively + process(rCandidate); + break; + } + } + + mnRecursionCounter--; + if (0 == mnRecursionCounter) + getRenderTarget()->EndDraw(); +} +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/getdigitlanguage.cxx b/drawinglayer/source/processor2d/getdigitlanguage.cxx new file mode 100644 index 0000000000..464fbf642a --- /dev/null +++ b/drawinglayer/source/processor2d/getdigitlanguage.cxx @@ -0,0 +1,31 @@ +/* -*- 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/. + */ + +#include <sal/config.h> + +#include <i18nlangtag/lang.h> +#include <i18nlangtag/languagetag.hxx> +#include <svl/ctloptions.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> + +#include "getdigitlanguage.hxx" + +LanguageType drawinglayer::detail::getDigitLanguage() { + switch (SvtCTLOptions::GetCTLTextNumerals()) { + case SvtCTLOptions::NUMERALS_ARABIC: + return LANGUAGE_ENGLISH; + case SvtCTLOptions::NUMERALS_HINDI: + return LANGUAGE_ARABIC_SAUDI_ARABIA; + default: + return Application::GetSettings().GetLanguageTag().getLanguageType(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/getdigitlanguage.hxx b/drawinglayer/source/processor2d/getdigitlanguage.hxx new file mode 100644 index 0000000000..c22076fa7c --- /dev/null +++ b/drawinglayer/source/processor2d/getdigitlanguage.hxx @@ -0,0 +1,22 @@ +/* -*- 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/. + */ + +#pragma once + +#include <sal/config.h> + +#include <i18nlangtag/lang.h> + +namespace drawinglayer::detail +{ +/// Get digit language derived from SvtCTLOptions +LanguageType getDigitLanguage(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/helperwrongspellrenderer.cxx b/drawinglayer/source/processor2d/helperwrongspellrenderer.cxx new file mode 100644 index 0000000000..9f838a7e1b --- /dev/null +++ b/drawinglayer/source/processor2d/helperwrongspellrenderer.cxx @@ -0,0 +1,76 @@ +/* -*- 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 "helperwrongspellrenderer.hxx" +#include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx> +#include <tools/gen.hxx> +#include <vcl/outdev.hxx> +#include <basegfx/color/bcolormodifier.hxx> +#include <vcl/outdev/ScopedStates.hxx> + +using namespace css; + +namespace drawinglayer +{ +namespace +{ +constexpr sal_uInt32 constMinimumFontHeight = 5; // #define WRONG_SHOW_MIN 5 +} + +bool renderWrongSpellPrimitive2D(const primitive2d::WrongSpellPrimitive2D& rWrongSpellCandidate, + OutputDevice& rOutputDevice, + const basegfx::B2DHomMatrix& rObjectToViewTransformation, + const basegfx::BColorModifierStack& rBColorModifierStack) +{ + const basegfx::B2DHomMatrix aLocalTransform(rObjectToViewTransformation + * rWrongSpellCandidate.getTransformation()); + const basegfx::B2DVector aFontVectorPixel(aLocalTransform * basegfx::B2DVector(0.0, 1.0)); + const sal_uInt32 nFontPixelHeight(basegfx::fround(aFontVectorPixel.getLength())); + + if (nFontPixelHeight <= constMinimumFontHeight) + return true; + + const basegfx::B2DPoint aStart(aLocalTransform + * basegfx::B2DPoint(rWrongSpellCandidate.getStart(), 0.0)); + const basegfx::B2DPoint aStop(aLocalTransform + * basegfx::B2DPoint(rWrongSpellCandidate.getStop(), 0.0)); + const Point aVclStart(basegfx::fround(aStart.getX()), basegfx::fround(aStart.getY())); + const Point aVclStop(basegfx::fround(aStop.getX()), basegfx::fround(aStop.getY())); + + // #i101075# draw it. Do not forget to use the evtl. offsetted origin of the target device, + // e.g. when used with mask/transparence buffer device + const Point aOrigin(rOutputDevice.GetMapMode().GetOrigin()); + + const basegfx::BColor aProcessedColor( + rBColorModifierStack.getModifiedColor(rWrongSpellCandidate.getColor())); + const bool bMapModeEnabledState(rOutputDevice.IsMapModeEnabled()); + + vcl::ScopedAntialiasing a(rOutputDevice, true); + rOutputDevice.EnableMapMode(false); + rOutputDevice.SetLineColor(Color(aProcessedColor)); + rOutputDevice.SetFillColor(); + rOutputDevice.DrawWaveLine(aOrigin + aVclStart, aOrigin + aVclStop); + rOutputDevice.EnableMapMode(bMapModeEnabledState); + + // cannot really go wrong + return true; +} +} // end of namespace drawinglayer + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/helperwrongspellrenderer.hxx b/drawinglayer/source/processor2d/helperwrongspellrenderer.hxx new file mode 100644 index 0000000000..886ce99bb7 --- /dev/null +++ b/drawinglayer/source/processor2d/helperwrongspellrenderer.hxx @@ -0,0 +1,47 @@ +/* -*- 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 . + */ + +#pragma once + +class OutputDevice; + +namespace drawinglayer::primitive2d +{ +class WrongSpellPrimitive2D; +} + +namespace basegfx +{ +class B2DHomMatrix; +class BColorModifierStack; +} + +// support WrongSpell rendering using VCL from primitives due to VCLs nice +// and fast solution with wavelines + +namespace drawinglayer +{ +bool renderWrongSpellPrimitive2D(const primitive2d::WrongSpellPrimitive2D& rWrongSpellCandidate, + OutputDevice& rOutputDevice, + const basegfx::B2DHomMatrix& rObjectToViewTransformation, + const basegfx::BColorModifierStack& rBColorModifierStack); + +} // end of namespace drawinglayer + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/hittestprocessor2d.cxx b/drawinglayer/source/processor2d/hittestprocessor2d.cxx new file mode 100644 index 0000000000..b760b24f84 --- /dev/null +++ b/drawinglayer/source/processor2d/hittestprocessor2d.cxx @@ -0,0 +1,548 @@ +/* -*- 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 <drawinglayer/processor2d/hittestprocessor2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonMarkerPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonWavePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/sceneprimitive2d.hxx> +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#include <basegfx/matrix/b3dhommatrix.hxx> +#include <drawinglayer/processor3d/cutfindprocessor3d.hxx> +#include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <comphelper/lok.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +namespace drawinglayer::processor2d +{ + HitTestProcessor2D::HitTestProcessor2D(const geometry::ViewInformation2D& rViewInformation, + const basegfx::B2DPoint& rLogicHitPosition, + const basegfx::B2DVector& rLogicHitTolerancePerAxis, + bool bHitTextOnly) + : BaseProcessor2D(rViewInformation), + maDiscreteHitTolerancePerAxis(rLogicHitTolerancePerAxis), + mbCollectHitStack(false), + mbHit(false), + mbHitTextOnly(bHitTextOnly) + { + // ensure input parameters for hit tolerance is >= 0.0 + if (maDiscreteHitTolerancePerAxis.getX() < 0.0) + maDiscreteHitTolerancePerAxis.setX(0.0); + if (maDiscreteHitTolerancePerAxis.getY() < 0.0) + maDiscreteHitTolerancePerAxis.setY(0.0); + + if (!maDiscreteHitTolerancePerAxis.equalZero()) + { + // generate discrete hit tolerance + maDiscreteHitTolerancePerAxis + = getViewInformation2D().getObjectToViewTransformation() * rLogicHitTolerancePerAxis; + } + + // generate discrete hit position + maDiscreteHitPosition = getViewInformation2D().getObjectToViewTransformation() * rLogicHitPosition; + } + + HitTestProcessor2D::~HitTestProcessor2D() + { + } + + bool HitTestProcessor2D::checkHairlineHitWithTolerance( + const basegfx::B2DPolygon& rPolygon, + const basegfx::B2DVector& rDiscreteHitTolerancePerAxis) const + { + basegfx::B2DPolygon aLocalPolygon(rPolygon); + aLocalPolygon.transform(getViewInformation2D().getObjectToViewTransformation()); + + // get discrete range + basegfx::B2DRange aPolygonRange(aLocalPolygon.getB2DRange()); + + if(rDiscreteHitTolerancePerAxis.getX() > 0 || rDiscreteHitTolerancePerAxis.getY() > 0) + { + aPolygonRange.grow(rDiscreteHitTolerancePerAxis); + } + + // do rough range test first + if(aPolygonRange.isInside(getDiscreteHitPosition())) + { + // check if a polygon edge is hit + return basegfx::utils::isInEpsilonRange( + aLocalPolygon, + getDiscreteHitPosition(), + std::max(rDiscreteHitTolerancePerAxis.getX(), rDiscreteHitTolerancePerAxis.getY())); + } + + return false; + } + + bool HitTestProcessor2D::checkFillHitWithTolerance( + const basegfx::B2DPolyPolygon& rPolyPolygon, + const basegfx::B2DVector& rDiscreteHitTolerancePerAxis) const + { + bool bRetval(false); + basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon); + aLocalPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation()); + + // get discrete range + basegfx::B2DRange aPolygonRange(aLocalPolyPolygon.getB2DRange()); + + const bool bDiscreteHitToleranceUsed(rDiscreteHitTolerancePerAxis.getX() > 0 + || rDiscreteHitTolerancePerAxis.getY() > 0); + + if (bDiscreteHitToleranceUsed) + { + aPolygonRange.grow(rDiscreteHitTolerancePerAxis); + } + + // do rough range test first + if(aPolygonRange.isInside(getDiscreteHitPosition())) + { + // if a HitTolerance is given, check for polygon edge hit in epsilon first + if(bDiscreteHitToleranceUsed && + basegfx::utils::isInEpsilonRange( + aLocalPolyPolygon, + getDiscreteHitPosition(), + std::max(rDiscreteHitTolerancePerAxis.getX(), rDiscreteHitTolerancePerAxis.getY()))) + { + bRetval = true; + } + + // check for hit in filled polyPolygon + if(!bRetval && basegfx::utils::isInside( + aLocalPolyPolygon, + getDiscreteHitPosition(), + true)) + { + bRetval = true; + } + } + + return bRetval; + } + + void HitTestProcessor2D::check3DHit(const primitive2d::ScenePrimitive2D& rCandidate) + { + // calculate relative point in unified 2D scene + const basegfx::B2DPoint aLogicHitPosition(getViewInformation2D().getInverseObjectToViewTransformation() * getDiscreteHitPosition()); + + // use bitmap check in ScenePrimitive2D + bool bTryFastResult(false); + + if(rCandidate.tryToCheckLastVisualisationDirectHit(aLogicHitPosition, bTryFastResult)) + { + mbHit = bTryFastResult; + } + else + { + basegfx::B2DHomMatrix aInverseSceneTransform(rCandidate.getObjectTransformation()); + aInverseSceneTransform.invert(); + const basegfx::B2DPoint aRelativePoint(aInverseSceneTransform * aLogicHitPosition); + + // check if test point is inside scene's unified area at all + if(aRelativePoint.getX() >= 0.0 && aRelativePoint.getX() <= 1.0 + && aRelativePoint.getY() >= 0.0 && aRelativePoint.getY() <= 1.0) + { + // get 3D view information + const geometry::ViewInformation3D& rObjectViewInformation3D = rCandidate.getViewInformation3D(); + + // create HitPoint Front and Back, transform to object coordinates + basegfx::B3DHomMatrix aViewToObject(rObjectViewInformation3D.getObjectToView()); + aViewToObject.invert(); + const basegfx::B3DPoint aFront(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 0.0)); + const basegfx::B3DPoint aBack(aViewToObject * basegfx::B3DPoint(aRelativePoint.getX(), aRelativePoint.getY(), 1.0)); + + if(!aFront.equal(aBack)) + { + const primitive3d::Primitive3DContainer& rPrimitives = rCandidate.getChildren3D(); + + if(!rPrimitives.empty()) + { + // make BoundVolume empty and overlapping test for speedup + const basegfx::B3DRange aObjectRange( + rPrimitives.getB3DRange(rObjectViewInformation3D)); + + if(!aObjectRange.isEmpty()) + { + const basegfx::B3DRange aFrontBackRange(aFront, aBack); + + if(aObjectRange.overlaps(aFrontBackRange)) + { + // bound volumes hit, geometric cut tests needed + drawinglayer::processor3d::CutFindProcessor aCutFindProcessor( + rObjectViewInformation3D, + aFront, + aBack, + true); + aCutFindProcessor.process(rPrimitives); + + mbHit = (!aCutFindProcessor.getCutPoints().empty()); + } + } + } + } + } + + if(!getHit()) + { + // empty 3D scene; Check for border hit + basegfx::B2DPolygon aOutline(basegfx::utils::createUnitPolygon()); + aOutline.transform(rCandidate.getObjectTransformation()); + + mbHit = checkHairlineHitWithTolerance(aOutline, getDiscreteHitTolerance()); + } + } + } + + void HitTestProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) + { + if(getHit()) + { + // stop processing as soon as a hit was recognized + return; + } + + switch(rCandidate.getPrimitive2DID()) + { + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D : + { + // remember current ViewInformation2D + const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate)); + const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); + + // create new local ViewInformation2D containing transformation + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation()); + updateViewInformation(aViewInformation2D); + + // process child content recursively + process(rTransformCandidate.getChildren()); + + // restore transformations + updateViewInformation(aLastViewInformation2D); + + break; + } + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D : + { + if(!getHitTextOnly()) + { + // create hairline in discrete coordinates + const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate)); + + // use hairline test + mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance()); + } + + break; + } + case PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D : + { + if(!getHitTextOnly()) + { + // handle marker like hairline; no need to decompose in dashes + const primitive2d::PolygonMarkerPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonMarkerPrimitive2D& >(rCandidate)); + + // use hairline test + mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance()); + } + + break; + } + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D : + { + if(!getHitTextOnly()) + { + // handle stroke evtl. directly; no need to decompose to filled polygon outlines + const primitive2d::PolygonStrokePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonStrokePrimitive2D& >(rCandidate)); + const attribute::LineAttribute& rLineAttribute = rPolygonCandidate.getLineAttribute(); + + if(basegfx::fTools::more(rLineAttribute.getWidth(), 0.0)) + { + if(basegfx::B2DLineJoin::Miter == rLineAttribute.getLineJoin()) + { + // if line is mitered, use decomposition since mitered line + // geometry may use more space than the geometry grown by half line width + process(rCandidate); + } + else + { + // for all other B2DLINEJOIN_* do a hairline HitTest with expanded tolerance + const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation() + * basegfx::B2DVector(rLineAttribute.getWidth() * 0.5, rLineAttribute.getWidth() * 0.5)); + mbHit = checkHairlineHitWithTolerance( + rPolygonCandidate.getB2DPolygon(), + getDiscreteHitTolerance() + aDiscreteHalfLineVector); + } + } + else + { + // hairline; fallback to hairline test. Do not decompose + // since this may decompose the hairline to dashes + mbHit = checkHairlineHitWithTolerance(rPolygonCandidate.getB2DPolygon(), getDiscreteHitTolerance()); + } + } + + break; + } + case PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D : + { + if(!getHitTextOnly()) + { + // do not use decompose; just handle like a line with width + const primitive2d::PolygonWavePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonWavePrimitive2D& >(rCandidate)); + double fLogicHitTolerance(0.0); + + // if WaveHeight, grow by it + if(basegfx::fTools::more(rPolygonCandidate.getWaveHeight(), 0.0)) + { + fLogicHitTolerance += rPolygonCandidate.getWaveHeight(); + } + + // if line width, grow by it + if(basegfx::fTools::more(rPolygonCandidate.getLineAttribute().getWidth(), 0.0)) + { + fLogicHitTolerance += rPolygonCandidate.getLineAttribute().getWidth() * 0.5; + } + + const basegfx::B2DVector aDiscreteHalfLineVector(getViewInformation2D().getObjectToViewTransformation() + * basegfx::B2DVector(fLogicHitTolerance, fLogicHitTolerance)); + + mbHit = checkHairlineHitWithTolerance( + rPolygonCandidate.getB2DPolygon(), + getDiscreteHitTolerance() + aDiscreteHalfLineVector); + } + + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D : + { + if(!getHitTextOnly()) + { + // create filled polyPolygon in discrete coordinates + const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate)); + + // use fill hit test + mbHit = checkFillHitWithTolerance(rPolygonCandidate.getB2DPolyPolygon(), getDiscreteHitTolerance()); + } + + break; + } + case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D : + { + // sub-transparence group + const primitive2d::TransparencePrimitive2D& rTransCandidate(static_cast< const primitive2d::TransparencePrimitive2D& >(rCandidate)); + + // Currently the transparence content is not taken into account; only + // the children are recursively checked for hit. This may be refined for + // parts where the content is completely transparent if needed. + process(rTransCandidate.getChildren()); + + break; + } + case PRIMITIVE2D_ID_MASKPRIMITIVE2D : + { + // create mask in discrete coordinates; only recursively continue + // with content when HitTest position is inside the mask + const primitive2d::MaskPrimitive2D& rMaskCandidate(static_cast< const primitive2d::MaskPrimitive2D& >(rCandidate)); + + // use fill hit test + if(checkFillHitWithTolerance(rMaskCandidate.getMask(), getDiscreteHitTolerance())) + { + // recursively HitTest children + process(rMaskCandidate.getChildren()); + } + + break; + } + case PRIMITIVE2D_ID_SCENEPRIMITIVE2D : + { + if(!getHitTextOnly()) + { + const primitive2d::ScenePrimitive2D& rScenePrimitive2D( + static_cast< const primitive2d::ScenePrimitive2D& >(rCandidate)); + check3DHit(rScenePrimitive2D); + } + + break; + } + case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D : + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D : + case PRIMITIVE2D_ID_GRIDPRIMITIVE2D : + case PRIMITIVE2D_ID_HELPLINEPRIMITIVE2D : + { + // ignorable primitives + break; + } + case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D : + { + // Ignore shadows; we do not want to have shadows hittable. + // Remove this one to make shadows hittable on demand. + break; + } + case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D : + case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D : + { + // for text use the BoundRect of the primitive itself + const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D())); + + if(!aRange.isEmpty()) + { + const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange)); + mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance()); + } + + break; + } + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D : + { + if(!getHitTextOnly()) + { + // The recently added BitmapEx::GetTransparency() makes it easy to extend + // the BitmapPrimitive2D HitTest to take the contained BitmapEx and it's + // transparency into account + const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D())); + + if(!aRange.isEmpty()) + { + const primitive2d::BitmapPrimitive2D& rBitmapCandidate(static_cast< const primitive2d::BitmapPrimitive2D& >(rCandidate)); + const BitmapEx aBitmapEx(rBitmapCandidate.getBitmap()); + const Size& rSizePixel(aBitmapEx.GetSizePixel()); + + // When tiled rendering, don't bother with the pixel size of the candidate. + if(rSizePixel.Width() && rSizePixel.Height() && !comphelper::LibreOfficeKit::isActive()) + { + basegfx::B2DHomMatrix aBackTransform( + getViewInformation2D().getObjectToViewTransformation() * + rBitmapCandidate.getTransform()); + aBackTransform.invert(); + + const basegfx::B2DPoint aRelativePoint(aBackTransform * getDiscreteHitPosition()); + const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0); + + if(aUnitRange.isInside(aRelativePoint)) + { + const sal_Int32 nX(basegfx::fround(aRelativePoint.getX() * rSizePixel.Width())); + const sal_Int32 nY(basegfx::fround(aRelativePoint.getY() * rSizePixel.Height())); + + mbHit = (0 != aBitmapEx.GetAlpha(nX, nY)); + } + } + else + { + // fallback to standard HitTest + const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange)); + mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance()); + } + } + } + + break; + } + case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D : + case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D : + case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D : + case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D : + case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D : + case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D : + case PRIMITIVE2D_ID_MEDIAPRIMITIVE2D: + { + if(!getHitTextOnly()) + { + // Class of primitives for which just the BoundRect of the primitive itself + // will be used for HitTest currently. + // + // This may be refined in the future, e.g: + // - For Bitmaps, the mask and/or transparence information may be used + // - For MetaFiles, the MetaFile content may be used + const basegfx::B2DRange aRange(rCandidate.getB2DRange(getViewInformation2D())); + + if(!aRange.isEmpty()) + { + const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aRange)); + mbHit = checkFillHitWithTolerance(basegfx::B2DPolyPolygon(aOutline), getDiscreteHitTolerance()); + } + } + + break; + } + case PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D : + { + // HiddenGeometryPrimitive2D; the default decomposition would return an empty sequence, + // so force this primitive to process its children directly if the switch is set + // (which is the default). Else, ignore invisible content + const primitive2d::HiddenGeometryPrimitive2D& rHiddenGeometry(static_cast< const primitive2d::HiddenGeometryPrimitive2D& >(rCandidate)); + const primitive2d::Primitive2DContainer& rChildren = rHiddenGeometry.getChildren(); + + if(!rChildren.empty()) + { + process(rChildren); + } + + break; + } + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D : + { + if(!getHitTextOnly()) + { + const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate(static_cast< const primitive2d::PointArrayPrimitive2D& >(rCandidate)); + const std::vector< basegfx::B2DPoint >& rPositions = rPointArrayCandidate.getPositions(); + const sal_uInt32 nCount(rPositions.size()); + + for(sal_uInt32 a(0); !getHit() && a < nCount; a++) + { + const basegfx::B2DPoint aPosition(getViewInformation2D().getObjectToViewTransformation() * rPositions[a]); + const basegfx::B2DVector aDistance(aPosition - getDiscreteHitPosition()); + + if (aDistance.getLength() <= std::max(getDiscreteHitTolerance().getX(), + getDiscreteHitTolerance().getY())) + { + mbHit = true; + } + } + } + + break; + } + default : + { + // process recursively + process(rCandidate); + + break; + } + } + + if (getHit() && getCollectHitStack()) + { + /// push candidate to HitStack to create it. This only happens when a hit is found and + /// creating the HitStack was requested (see collectHitStack) + maHitStack.append(primitive2d::Primitive2DReference(const_cast< primitive2d::BasePrimitive2D* >(&rCandidate))); + } + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/linegeometryextractor2d.cxx b/drawinglayer/source/processor2d/linegeometryextractor2d.cxx new file mode 100644 index 0000000000..11af79725b --- /dev/null +++ b/drawinglayer/source/processor2d/linegeometryextractor2d.cxx @@ -0,0 +1,121 @@ +/* -*- 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 <drawinglayer/processor2d/linegeometryextractor2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::processor2d +{ + LineGeometryExtractor2D::LineGeometryExtractor2D(const geometry::ViewInformation2D& rViewInformation) + : BaseProcessor2D(rViewInformation), + mbInLineGeometry(false) + { + } + + LineGeometryExtractor2D::~LineGeometryExtractor2D() + { + } + + void LineGeometryExtractor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) + { + switch(rCandidate.getPrimitive2DID()) + { + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D : + case PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D : + { + // enter a line geometry group (with or without LineEnds) + bool bOldState(mbInLineGeometry); + mbInLineGeometry = true; + process(rCandidate); + mbInLineGeometry = bOldState; + break; + } + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D : + { + if(mbInLineGeometry) + { + // extract hairline line geometry in world coordinates + const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate)); + basegfx::B2DPolygon aLocalPolygon(rPolygonCandidate.getB2DPolygon()); + aLocalPolygon.transform(getViewInformation2D().getObjectTransformation()); + maExtractedHairlines.push_back(aLocalPolygon); + } + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D : + { + if(mbInLineGeometry) + { + // extract filled line geometry (line with width) + const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate)); + basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon()); + aLocalPolyPolygon.transform(getViewInformation2D().getObjectTransformation()); + maExtractedLineFills.push_back(aLocalPolyPolygon); + } + break; + } + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D : + { + // remember current transformation and ViewInformation + const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate)); + const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); + + // create new transformations for CurrentTransformation and for local ViewInformation2D + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation()); + updateViewInformation(aViewInformation2D); + + // process content + process(rTransformCandidate.getChildren()); + + // restore transformations + updateViewInformation(aLastViewInformation2D); + + break; + } + case PRIMITIVE2D_ID_SCENEPRIMITIVE2D : + case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D : + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D : + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D : + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D : + case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D : + case PRIMITIVE2D_ID_MASKPRIMITIVE2D : + { + // ignorable primitives + break; + } + default : + { + // process recursively + process(rCandidate); + break; + } + } + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/objectinfoextractor2d.cxx b/drawinglayer/source/processor2d/objectinfoextractor2d.cxx new file mode 100644 index 0000000000..552406d53f --- /dev/null +++ b/drawinglayer/source/processor2d/objectinfoextractor2d.cxx @@ -0,0 +1,77 @@ +/* -*- 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 <drawinglayer/processor2d/objectinfoextractor2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::processor2d +{ + void ObjectInfoPrimitiveExtractor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) + { + if(mpFound) + return; + + switch(rCandidate.getPrimitive2DID()) + { + case PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D : + { + mpFound = dynamic_cast< const primitive2d::ObjectInfoPrimitive2D* >(&rCandidate); + break; + } + default : + { + // we look for an encapsulated primitive, so do not decompose primitives + // based on GroupPrimitive2D, just visit their children. It may be that more + // group-like primitives need to be added here, but all primitives with + // grouping functionality should be implemented based on the GroupPrimitive2D + // class and have their main content accessible as children + const primitive2d::GroupPrimitive2D* pGroupPrimitive2D = dynamic_cast< const primitive2d::GroupPrimitive2D* >(&rCandidate); + + if(pGroupPrimitive2D) + { + // process group children recursively + process(pGroupPrimitive2D->getChildren()); + } + else + { + // do not process recursively, we *only* want to find existing + // ObjectInfoPrimitive2D entries + } + + break; + } + } + } + + ObjectInfoPrimitiveExtractor2D::ObjectInfoPrimitiveExtractor2D(const geometry::ViewInformation2D& rViewInformation) + : BaseProcessor2D(rViewInformation), + mpFound(nullptr) + { + } + + ObjectInfoPrimitiveExtractor2D::~ObjectInfoPrimitiveExtractor2D() + { + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/processor2dtools.cxx b/drawinglayer/source/processor2d/processor2dtools.cxx new file mode 100644 index 0000000000..cf823b005e --- /dev/null +++ b/drawinglayer/source/processor2d/processor2dtools.cxx @@ -0,0 +1,92 @@ +/* -*- 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 <drawinglayer/processor2d/processor2dtools.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/sysdata.hxx> +#include "vclpixelprocessor2d.hxx" +#include "vclmetafileprocessor2d.hxx" +#include <config_vclplug.h> + +#if defined(_WIN32) +#include <drawinglayer/processor2d/d2dpixelprocessor2d.hxx> +#elif USE_HEADLESS_CODE +#include <drawinglayer/processor2d/cairopixelprocessor2d.hxx> +#endif + +using namespace com::sun::star; + +namespace drawinglayer::processor2d +{ +std::unique_ptr<BaseProcessor2D> createPixelProcessor2DFromOutputDevice( + OutputDevice& rTargetOutDev, + const drawinglayer::geometry::ViewInformation2D& rViewInformation2D) +{ + static const bool bTestSystemPrimitiveRenderer(nullptr != std::getenv("TEST_SYSTEM_PRIMITIVE_RENDERER")); + if(bTestSystemPrimitiveRenderer) + { + drawinglayer::geometry::ViewInformation2D aViewInformation2D(rViewInformation2D); + // if mnOutOffX/mnOutOffY is set (a 'hack' to get a cheap additional offset), apply it additionally + if(0 != rTargetOutDev.GetOutOffXPixel() || 0 != rTargetOutDev.GetOutOffYPixel()) + { + basegfx::B2DHomMatrix aTransform(aViewInformation2D.getViewTransformation()); + aTransform.translate(rTargetOutDev.GetOutOffXPixel(), rTargetOutDev.GetOutOffYPixel()); + aViewInformation2D.setViewTransformation(aTransform); + } +#if defined(_WIN32) + SystemGraphicsData aData(rTargetOutDev.GetSystemGfxData()); + std::unique_ptr<D2DPixelProcessor2D> aRetval( + std::make_unique<D2DPixelProcessor2D>(aViewInformation2D, aData.hDC)); + if (aRetval->valid()) + return aRetval; +#elif USE_HEADLESS_CODE + SystemGraphicsData aData(rTargetOutDev.GetSystemGfxData()); + std::unique_ptr<CairoPixelProcessor2D> aRetval( + std::make_unique<CairoPixelProcessor2D>(aViewInformation2D, static_cast<cairo_surface_t*>(aData.pSurface))); + if (aRetval->valid()) + return aRetval; +#endif + } + + // create Pixel Vcl-Processor + return std::make_unique<VclPixelProcessor2D>(rViewInformation2D, rTargetOutDev); +} + +std::unique_ptr<BaseProcessor2D> createProcessor2DFromOutputDevice( + OutputDevice& rTargetOutDev, + const drawinglayer::geometry::ViewInformation2D& rViewInformation2D) +{ + const GDIMetaFile* pMetaFile = rTargetOutDev.GetConnectMetaFile(); + const bool bOutputToRecordingMetaFile(pMetaFile && pMetaFile->IsRecord() + && !pMetaFile->IsPause()); + + if (bOutputToRecordingMetaFile) + { + // create MetaFile Vcl-Processor and process + return std::make_unique<VclMetafileProcessor2D>(rViewInformation2D, rTargetOutDev); + } + else + { + // create Pixel Vcl-Processor + return createPixelProcessor2DFromOutputDevice(rTargetOutDev, rViewInformation2D); + } +} + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/textaspolygonextractor2d.cxx b/drawinglayer/source/processor2d/textaspolygonextractor2d.cxx new file mode 100644 index 0000000000..c98fae69d3 --- /dev/null +++ b/drawinglayer/source/processor2d/textaspolygonextractor2d.cxx @@ -0,0 +1,227 @@ +/* -*- 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 <drawinglayer/processor2d/textaspolygonextractor2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> + + +namespace drawinglayer::processor2d +{ + void TextAsPolygonExtractor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) + { + switch(rCandidate.getPrimitive2DID()) + { + case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D : + { + // TextDecoratedPortionPrimitive2D can produce the following primitives + // when being decomposed: + // + // - TextSimplePortionPrimitive2D + // - PolygonWavePrimitive2D + // - PolygonStrokePrimitive2D + // - PolygonStrokePrimitive2D + // - PolyPolygonColorPrimitive2D + // - PolyPolygonHairlinePrimitive2D + // - PolygonHairlinePrimitive2D + // - ShadowPrimitive2D + // - ModifiedColorPrimitive2D + // - TransformPrimitive2D + // - TextEffectPrimitive2D + // - ModifiedColorPrimitive2D + // - TransformPrimitive2D + // - GroupPrimitive2D + + // encapsulate with flag and use decomposition + mnInText++; + process(rCandidate); + mnInText--; + + break; + } + case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D : + { + // TextSimplePortionPrimitive2D can produce the following primitives + // when being decomposed: + // + // - PolyPolygonColorPrimitive2D + // - TextEffectPrimitive2D + // - ModifiedColorPrimitive2D + // - TransformPrimitive2D + // - GroupPrimitive2D + + // encapsulate with flag and use decomposition + mnInText++; + process(rCandidate); + mnInText--; + + break; + } + + // as can be seen from the TextSimplePortionPrimitive2D and the + // TextDecoratedPortionPrimitive2D, inside of the mnInText marks + // the following primitives can occur containing geometry data + // from text decomposition: + // + // - PolyPolygonColorPrimitive2D + // - PolygonHairlinePrimitive2D + // - PolyPolygonHairlinePrimitive2D (for convenience) + // + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D : + { + if(mnInText) + { + const primitive2d::PolyPolygonColorPrimitive2D& rPoPoCoCandidate(static_cast< const primitive2d::PolyPolygonColorPrimitive2D& >(rCandidate)); + basegfx::B2DPolyPolygon aPolyPolygon(rPoPoCoCandidate.getB2DPolyPolygon()); + + if(aPolyPolygon.count()) + { + // transform the PolyPolygon + aPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation()); + + // get evtl. corrected color + const basegfx::BColor aColor(maBColorModifierStack.getModifiedColor(rPoPoCoCandidate.getBColor())); + + // add to result vector + maTarget.emplace_back(aPolyPolygon, aColor, true); + } + } + + break; + } + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D : + { + if(mnInText) + { + const primitive2d::PolygonHairlinePrimitive2D& rPoHaCandidate(static_cast< const primitive2d::PolygonHairlinePrimitive2D& >(rCandidate)); + basegfx::B2DPolygon aPolygon(rPoHaCandidate.getB2DPolygon()); + + if(aPolygon.count()) + { + // transform the Polygon + aPolygon.transform(getViewInformation2D().getObjectToViewTransformation()); + + // get evtl. corrected color + const basegfx::BColor aColor(maBColorModifierStack.getModifiedColor(rPoHaCandidate.getBColor())); + + // add to result vector + maTarget.emplace_back(basegfx::B2DPolyPolygon(aPolygon), aColor, false); + } + } + + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONHAIRLINEPRIMITIVE2D : + { + if(mnInText) + { + const primitive2d::PolyPolygonHairlinePrimitive2D& rPoPoHaCandidate(static_cast< const primitive2d::PolyPolygonHairlinePrimitive2D& >(rCandidate)); + basegfx::B2DPolyPolygon aPolyPolygon(rPoPoHaCandidate.getB2DPolyPolygon()); + + if(aPolyPolygon.count()) + { + // transform the Polygon + aPolyPolygon.transform(getViewInformation2D().getObjectToViewTransformation()); + + // get evtl. corrected color + const basegfx::BColor aColor(maBColorModifierStack.getModifiedColor(rPoPoHaCandidate.getBColor())); + + // add to result vector + maTarget.emplace_back(aPolyPolygon, aColor, false); + } + } + + break; + } + + // usage of color modification stack is needed + case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D : + { + const primitive2d::ModifiedColorPrimitive2D& rModifiedColorCandidate(static_cast< const primitive2d::ModifiedColorPrimitive2D& >(rCandidate)); + + if(!rModifiedColorCandidate.getChildren().empty()) + { + maBColorModifierStack.push(rModifiedColorCandidate.getColorModifier()); + process(rModifiedColorCandidate.getChildren()); + maBColorModifierStack.pop(); + } + + break; + } + + // usage of transformation stack is needed + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D : + { + // remember current transformation and ViewInformation + const primitive2d::TransformPrimitive2D& rTransformCandidate(static_cast< const primitive2d::TransformPrimitive2D& >(rCandidate)); + const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); + + // create new transformations for CurrentTransformation and for local ViewInformation2D + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() * rTransformCandidate.getTransformation()); + updateViewInformation(aViewInformation2D); + + // process content + process(rTransformCandidate.getChildren()); + + // restore transformations + updateViewInformation(aLastViewInformation2D); + + break; + } + + // ignorable primitives + case PRIMITIVE2D_ID_SCENEPRIMITIVE2D : + case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D : + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D : + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D : + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D : + case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D : + case PRIMITIVE2D_ID_MASKPRIMITIVE2D : + { + break; + } + + default : + { + // process recursively + process(rCandidate); + break; + } + } + } + + TextAsPolygonExtractor2D::TextAsPolygonExtractor2D(const geometry::ViewInformation2D& rViewInformation) + : BaseProcessor2D(rViewInformation), + maBColorModifierStack(), + mnInText(0) + { + } + + TextAsPolygonExtractor2D::~TextAsPolygonExtractor2D() + { + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx b/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx new file mode 100644 index 0000000000..28d383230e --- /dev/null +++ b/drawinglayer/source/processor2d/vclhelperbufferdevice.cxx @@ -0,0 +1,593 @@ +/* -*- 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/config.h> +#include <sal/log.hxx> + +#include <algorithm> +#include <map> +#include <vector> + +#include "vclhelperbufferdevice.hxx" +#include <basegfx/range/b2drange.hxx> +#include <vcl/bitmapex.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <vcl/timer.hxx> +#include <vcl/lazydelete.hxx> +#include <vcl/dibtools.hxx> +#include <vcl/skia/SkiaHelper.hxx> +#include <mutex> + +#ifdef DBG_UTIL +#include <tools/stream.hxx> +#endif + +// #define SPEED_COMPARE +#ifdef SPEED_COMPARE +#include <tools/time.hxx> +#endif + +// buffered VDev usage +namespace +{ +class VDevBuffer : public Timer +{ +private: + struct Entry + { + VclPtr<VirtualDevice> buf; + Entry(const VclPtr<VirtualDevice>& vdev) + : buf(vdev) + { + } + }; + + std::mutex m_aMutex; + + // available buffers + std::vector<Entry> maFreeBuffers; + + // allocated/used buffers (remembered to allow deleting them in destructor) + std::vector<Entry> maUsedBuffers; + + // remember what outputdevice was the template passed to VirtualDevice::Create + // so we can test if that OutputDevice was disposed before reusing a + // virtualdevice because that isn't safe to do at least for Gtk2 + std::map<VclPtr<VirtualDevice>, VclPtr<OutputDevice>> maDeviceTemplates; + + static bool isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& size); + +public: + VDevBuffer(); + virtual ~VDevBuffer() override; + + VclPtr<VirtualDevice> alloc(OutputDevice& rOutDev, const Size& rSizePixel); + void free(VirtualDevice& rDevice); + + // Timer virtuals + virtual void Invoke() override; +}; + +VDevBuffer::VDevBuffer() + : Timer("drawinglayer::VDevBuffer via Invoke()") +{ + SetTimeout(10L * 1000L); // ten seconds +} + +VDevBuffer::~VDevBuffer() +{ + std::unique_lock aGuard(m_aMutex); + Stop(); + + while (!maFreeBuffers.empty()) + { + maFreeBuffers.back().buf.disposeAndClear(); + maFreeBuffers.pop_back(); + } + + while (!maUsedBuffers.empty()) + { + maUsedBuffers.back().buf.disposeAndClear(); + maUsedBuffers.pop_back(); + } +} + +bool VDevBuffer::isSizeSuitable(const VclPtr<VirtualDevice>& device, const Size& rSizePixel) +{ + if (device->GetOutputWidthPixel() >= rSizePixel.getWidth() + && device->GetOutputHeightPixel() >= rSizePixel.getHeight()) + { + bool requireSmall = false; +#if defined(UNX) + // HACK: See the small size handling in SvpSalVirtualDevice::CreateSurface(). + // Make sure to not reuse a larger device when a small one should be preferred. + if (device->GetRenderBackendName() == "svp") + requireSmall = true; +#endif + // The same for Skia, see renderMethodToUseForSize(). + if (SkiaHelper::isVCLSkiaEnabled()) + requireSmall = true; + if (requireSmall) + { + if (rSizePixel.getWidth() <= 32 && rSizePixel.getHeight() <= 32 + && (device->GetOutputWidthPixel() > 32 || device->GetOutputHeightPixel() > 32)) + { + return false; + } + } + return true; + } + return false; +} + +VclPtr<VirtualDevice> VDevBuffer::alloc(OutputDevice& rOutDev, const Size& rSizePixel) +{ + std::unique_lock aGuard(m_aMutex); + VclPtr<VirtualDevice> pRetval; + + sal_Int32 nBits = rOutDev.GetBitCount(); + + bool bOkay(false); + if (!maFreeBuffers.empty()) + { + auto aFound(maFreeBuffers.end()); + + for (auto a = maFreeBuffers.begin(); a != maFreeBuffers.end(); ++a) + { + assert(a->buf && "Empty pointer in VDevBuffer (!)"); + + if (nBits == a->buf->GetBitCount()) + { + // candidate is valid due to bit depth + if (aFound != maFreeBuffers.end()) + { + // already found + if (bOkay) + { + // found is valid + const bool bCandidateOkay = isSizeSuitable(a->buf, rSizePixel); + + if (bCandidateOkay) + { + // found and candidate are valid + const sal_uLong aSquare(aFound->buf->GetOutputWidthPixel() + * aFound->buf->GetOutputHeightPixel()); + const sal_uLong aCandidateSquare(a->buf->GetOutputWidthPixel() + * a->buf->GetOutputHeightPixel()); + + if (aCandidateSquare < aSquare) + { + // candidate is valid and smaller, use it + aFound = a; + } + } + else + { + // found is valid, candidate is not. Keep found + } + } + else + { + // found is invalid, use candidate + aFound = a; + bOkay = isSizeSuitable(aFound->buf, rSizePixel); + } + } + else + { + // none yet, use candidate + aFound = a; + bOkay = isSizeSuitable(aFound->buf, rSizePixel); + } + } + } + + if (aFound != maFreeBuffers.end()) + { + pRetval = aFound->buf; + maFreeBuffers.erase(aFound); + } + } + + if (pRetval) + { + // found a suitable cached virtual device, but the + // outputdevice it was based on has been disposed, + // drop it and create a new one instead as reusing + // such devices is unsafe under at least Gtk2 + if (maDeviceTemplates[pRetval]->isDisposed()) + { + maDeviceTemplates.erase(pRetval); + pRetval.disposeAndClear(); + } + else + { + if (bOkay) + { + pRetval->Erase(pRetval->PixelToLogic( + tools::Rectangle(0, 0, rSizePixel.getWidth(), rSizePixel.getHeight()))); + } + else + { + pRetval->SetOutputSizePixel(rSizePixel, true); + } + } + } + + // no success yet, create new buffer + if (!pRetval) + { + pRetval = VclPtr<VirtualDevice>::Create(rOutDev, DeviceFormat::WITHOUT_ALPHA); + maDeviceTemplates[pRetval] = &rOutDev; + pRetval->SetOutputSizePixel(rSizePixel, true); + } + else + { + // reused, reset some values + pRetval->SetMapMode(); + pRetval->SetRasterOp(RasterOp::OverPaint); + } + + // remember allocated buffer + maUsedBuffers.emplace_back(pRetval); + + return pRetval; +} + +void VDevBuffer::free(VirtualDevice& rDevice) +{ + std::unique_lock aGuard(m_aMutex); + const auto aUsedFound + = std::find_if(maUsedBuffers.begin(), maUsedBuffers.end(), + [&rDevice](const Entry& el) { return el.buf == &rDevice; }); + SAL_WARN_IF(aUsedFound == maUsedBuffers.end(), "drawinglayer", + "OOps, non-registered buffer freed (!)"); + if (aUsedFound != maUsedBuffers.end()) + { + maFreeBuffers.emplace_back(*aUsedFound); + maUsedBuffers.erase(aUsedFound); + SAL_WARN_IF(maFreeBuffers.size() > 1000, "drawinglayer", + "excessive cached buffers, " << maFreeBuffers.size() << " entries!"); + } + Start(); +} + +void VDevBuffer::Invoke() +{ + std::unique_lock aGuard(m_aMutex); + + while (!maFreeBuffers.empty()) + { + auto aLastOne = maFreeBuffers.back(); + maDeviceTemplates.erase(aLastOne.buf); + aLastOne.buf.disposeAndClear(); + maFreeBuffers.pop_back(); + } +} + +#ifdef SPEED_COMPARE +void doSpeedCompare(double fTrans, const Bitmap& rContent, const tools::Rectangle& rDestPixel, + OutputDevice& rOutDev) +{ + const int nAvInd(500); + static double fFactors[nAvInd]; + static int nIndex(nAvInd + 1); + static int nRepeat(5); + static int nWorseTotal(0); + static int nBetterTotal(0); + int a(0); + + const Size aSizePixel(rDestPixel.GetSize()); + + // init statics + if (nIndex > nAvInd) + { + for (a = 0; a < nAvInd; a++) + fFactors[a] = 1.0; + nIndex = 0; + } + + // get start time + const sal_uInt64 nTimeA(tools::Time::GetSystemTicks()); + + // loop nRepeat times to get somewhat better timings, else + // numbers are pretty small + for (a = 0; a < nRepeat; a++) + { + // "Former" method using a temporary AlphaMask & DrawBitmapEx + sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0))); + const AlphaMask aAlphaMask(aSizePixel, &nMaskValue); + rOutDev.DrawBitmapEx(rDestPixel.TopLeft(), BitmapEx(rContent, aAlphaMask)); + } + + // get intermediate time + const sal_uInt64 nTimeB(tools::Time::GetSystemTicks()); + + // loop nRepeat times + for (a = 0; a < nRepeat; a++) + { + // New method using DrawTransformedBitmapEx & fTrans directly + rOutDev.DrawTransformedBitmapEx(basegfx::utils::createScaleTranslateB2DHomMatrix( + aSizePixel.Width(), aSizePixel.Height(), + rDestPixel.TopLeft().X(), rDestPixel.TopLeft().Y()), + BitmapEx(rContent), 1 - fTrans); + } + + // get end time + const sal_uInt64 nTimeC(tools::Time::GetSystemTicks()); + + // calculate deltas + const sal_uInt64 nTimeFormer(nTimeB - nTimeA); + const sal_uInt64 nTimeNew(nTimeC - nTimeB); + + // compare & note down + if (nTimeFormer != nTimeNew && 0 != nTimeFormer && 0 != nTimeNew) + { + if ((nTimeFormer < 10 || nTimeNew < 10) && nRepeat < 500) + { + nRepeat += 1; + SAL_INFO("drawinglayer.processor2d", "Increment nRepeat to " << nRepeat); + return; + } + + const double fNewFactor((double)nTimeFormer / nTimeNew); + fFactors[nIndex % nAvInd] = fNewFactor; + nIndex++; + double fAverage(0.0); + { + for (a = 0; a < nAvInd; a++) + fAverage += fFactors[a]; + fAverage /= nAvInd; + } + if (fNewFactor < 1.0) + nWorseTotal++; + else + nBetterTotal++; + + char buf[300]; + sprintf(buf, + "Former: %ld New: %ld It got %s (factor %f) (av. last %d Former/New is %f, " + "WorseTotal: %d, BetterTotal: %d)", + nTimeFormer, nTimeNew, fNewFactor < 1.0 ? "WORSE" : "BETTER", + fNewFactor < 1.0 ? 1.0 / fNewFactor : fNewFactor, nAvInd, fAverage, nWorseTotal, + nBetterTotal); + SAL_INFO("drawinglayer.processor2d", buf); + } +} +#endif +} + +// support for rendering Bitmap and BitmapEx contents +namespace drawinglayer +{ +// static global VDev buffer for VclProcessor2D/VclPixelProcessor2D +VDevBuffer& getVDevBuffer() +{ + // secure global instance with Vcl's safe destroyer of external (seen by + // library base) stuff, the remembered VDevs need to be deleted before + // Vcl's deinit + static vcl::DeleteOnDeinit<VDevBuffer> aVDevBuffer{}; + return *aVDevBuffer.get(); +} + +impBufferDevice::impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange) + : mrOutDev(rOutDev) + , mpContent(nullptr) + , mpAlpha(nullptr) +{ + basegfx::B2DRange aRangePixel(rRange); + aRangePixel.transform(mrOutDev.GetViewTransformation()); + maDestPixel = tools::Rectangle(floor(aRangePixel.getMinX()), floor(aRangePixel.getMinY()), + ceil(aRangePixel.getMaxX()), ceil(aRangePixel.getMaxY())); + maDestPixel.Intersection(tools::Rectangle{ Point{}, mrOutDev.GetOutputSizePixel() }); + + if (!isVisible()) + return; + + mpContent = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize()); + + // #i93485# assert when copying from window to VDev is used + SAL_WARN_IF( + mrOutDev.GetOutDevType() == OUTDEV_WINDOW, "drawinglayer", + "impBufferDevice render helper: Copying from Window to VDev, this should be avoided (!)"); + + // initialize buffer by blitting content of source to prepare for + // transparence/ copying back + const bool bWasEnabledSrc(mrOutDev.IsMapModeEnabled()); + mrOutDev.EnableMapMode(false); + mpContent->DrawOutDev(Point(), maDestPixel.GetSize(), maDestPixel.TopLeft(), + maDestPixel.GetSize(), mrOutDev); + mrOutDev.EnableMapMode(bWasEnabledSrc); + + MapMode aNewMapMode(mrOutDev.GetMapMode()); + + const Point aLogicTopLeft(mrOutDev.PixelToLogic(maDestPixel.TopLeft())); + aNewMapMode.SetOrigin(Point(-aLogicTopLeft.X(), -aLogicTopLeft.Y())); + + mpContent->SetMapMode(aNewMapMode); + + // copy AA flag for new target + mpContent->SetAntialiasing(mrOutDev.GetAntialiasing()); + + // copy RasterOp (e.g. may be RasterOp::Xor on destination) + mpContent->SetRasterOp(mrOutDev.GetRasterOp()); +} + +impBufferDevice::~impBufferDevice() +{ + if (mpContent) + { + getVDevBuffer().free(*mpContent); + } + + if (mpAlpha) + { + getVDevBuffer().free(*mpAlpha); + } +} + +void impBufferDevice::paint(double fTrans) +{ + if (!isVisible()) + return; + + const Point aEmptyPoint; + const Size aSizePixel(maDestPixel.GetSize()); + const bool bWasEnabledDst(mrOutDev.IsMapModeEnabled()); + + mrOutDev.EnableMapMode(false); + mpContent->EnableMapMode(false); + +#ifdef DBG_UTIL + // VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/ + static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore + static const OUString sDumpPath(OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH"))); + + if (!sDumpPath.isEmpty() && bDoSaveForVisualControl) + { + SvFileStream aNew(sDumpPath + "content.bmp", StreamMode::WRITE | StreamMode::TRUNC); + Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel)); + WriteDIB(aContent, aNew, false, true); + } +#endif + + // during painting the buffer, disable evtl. set RasterOp (may be RasterOp::Xor) + const RasterOp aOrigRasterOp(mrOutDev.GetRasterOp()); + mrOutDev.SetRasterOp(RasterOp::OverPaint); + + if (mpAlpha) + { + mpAlpha->EnableMapMode(false); + AlphaMask aAlphaMask(mpAlpha->GetBitmap(aEmptyPoint, aSizePixel)); + aAlphaMask.Invert(); // convert transparency to alpha + +#ifdef DBG_UTIL + if (!sDumpPath.isEmpty() && bDoSaveForVisualControl) + { + SvFileStream aNew(sDumpPath + "transparence.bmp", + StreamMode::WRITE | StreamMode::TRUNC); + WriteDIB(aAlphaMask.GetBitmap(), aNew, false, true); + } +#endif + + Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel)); + mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent, aAlphaMask)); + } + else if (0.0 != fTrans) + { + const Bitmap aContent(mpContent->GetBitmap(aEmptyPoint, aSizePixel)); + +#ifdef SPEED_COMPARE + static bool bCompareFormerAndNewTimings(true); + + if (bCompareFormerAndNewTimings) + { + doSpeedCompare(fTrans, aContent, maDestPixel, mrOutDev); + } + else +#endif + // Note: this extra scope is needed due to 'clang plugin indentation'. It complains + // that lines 494 and (now) 539 are 'statement mis-aligned compared to neighbours'. + // That is true if SPEED_COMPARE is not defined. Not nice, but have to fix this. + { + // For the case we have a unified transparency value there is a former + // and new method to paint that which can be used. To decide on measurements, + // I added 'doSpeedCompare' above which can be activated by defining + // SPEED_COMPARE at the top of this file. + // I added the used Testdoc: blurplay3.odg as + // https://bugs.documentfoundation.org/attachment.cgi?id=182463 + // I did measure on + // + // Linux Dbg: + // Former: 21 New: 32 It got WORSE (factor 1.523810) (av. last 500 Former/New is 0.968533, WorseTotal: 515, BetterTotal: 934) + // + // Linux Pro: + // Former: 27 New: 44 It got WORSE (factor 1.629630) (av. last 500 Former/New is 0.923256, WorseTotal: 433, BetterTotal: 337) + // + // Win Dbg: + // Former: 21 New: 78 It got WORSE (factor 3.714286) (av. last 500 Former/New is 1.007176, WorseTotal: 85, BetterTotal: 1428) + // + // Win Pro: + // Former: 3 New: 4 It got WORSE (factor 1.333333) (av. last 500 Former/New is 1.054167, WorseTotal: 143, BetterTotal: 3909) + // + // Note: I am aware that the Dbg are of limited usefulness, but include them here + // for reference. + // + // The important part is "av. last 500 Former/New is %ld" which describes the averaged factor from Former/New + // over the last 500 measurements. When < 1.0 Former is better (Linux), > 1.0 (Win) New is better. Since the + // factor on Win is still close to 1.0 what means we lose nearly nothing and Linux Former is better, I will + // use Former for now. + // + // To easily allow to change this (maybe system-dependent) I add a static switch here, + // also for eventually experimenting (hint: can be changed in the debugger). + static bool bUseNew(false); + + if (bUseNew) + { + // New method using DrawTransformedBitmapEx & fTrans directly + mrOutDev.DrawTransformedBitmapEx(basegfx::utils::createScaleTranslateB2DHomMatrix( + aSizePixel.Width(), aSizePixel.Height(), + maDestPixel.TopLeft().X(), + maDestPixel.TopLeft().Y()), + BitmapEx(aContent), 1 - fTrans); + } + else + { + // "Former" method using a temporary AlphaMask & DrawBitmapEx + sal_uInt8 nMaskValue(static_cast<sal_uInt8>(basegfx::fround(fTrans * 255.0))); + const AlphaMask aAlphaMask(aSizePixel, &nMaskValue); + mrOutDev.DrawBitmapEx(maDestPixel.TopLeft(), BitmapEx(aContent, aAlphaMask)); + } + } + } + else + { + mrOutDev.DrawOutDev(maDestPixel.TopLeft(), aSizePixel, aEmptyPoint, aSizePixel, *mpContent); + } + + mrOutDev.SetRasterOp(aOrigRasterOp); + mrOutDev.EnableMapMode(bWasEnabledDst); +} + +VirtualDevice& impBufferDevice::getContent() +{ + SAL_WARN_IF(!mpContent, "drawinglayer", + "impBufferDevice: No content, check isVisible() before accessing (!)"); + return *mpContent; +} + +VirtualDevice& impBufferDevice::getTransparence() +{ + SAL_WARN_IF(!mpContent, "drawinglayer", + "impBufferDevice: No content, check isVisible() before accessing (!)"); + if (!mpAlpha) + { + mpAlpha = getVDevBuffer().alloc(mrOutDev, maDestPixel.GetSize()); + mpAlpha->SetMapMode(mpContent->GetMapMode()); + + // copy AA flag for new target; masking needs to be smooth + mpAlpha->SetAntialiasing(mpContent->GetAntialiasing()); + } + + return *mpAlpha; +} +} // end of namespace drawinglayer + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx b/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx new file mode 100644 index 0000000000..618fb38209 --- /dev/null +++ b/drawinglayer/source/processor2d/vclhelperbufferdevice.hxx @@ -0,0 +1,112 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/virdev.hxx> + +// Helper class *exclusively* for VclProcessor2D. It should only +// be used internally, see current four usages. It is used to +// render something with mask or transparence (see MaskPrimitive2D, +// UnifiedTransparencePrimitive2D and TransparencePrimitive2D) or +// as tooling for preparing pixelized output in the renderer +// (see PatternFillPrimitive2D) if that is faster. +// +// To do so, initializing this instance takes over a lot of work +// from you: +// - It initializes a 'Content' VDev which is buffered (since it had +// shown that re-allocating all the time is slower). It checks +// visibility and all usages initializing this should check for +// isVisible() after construction. +// - It pre-initializes the 'Content' VDev with blitting the content +// of the target VDev. +// - It offers to get a 'Transparence' VDev (also from the buffer) if +// needed. +// +// If 'Transparence' is/was used, it combines as needed to paint +// all buffered stuff to target VDev when calling paint(). +// Caution: It is designed to use *either* a fixed transparence in +// the paint()-call *or* a fill TransparenceChannel using a +// 'Transparence' VDev. It is *not* designed to use/combine +// both - it's simply not needed for it's intended purpose/usage. +// +// Painting transparent works based on a simple principle: It first +// blits the original content of the target VDev. Then the content +// is painted on top of that, plus a Transparence/Mask prepared. +// The combination always works since unchanged pixels will not +// change, independent of the transparence value [0..255] it gets +// mixed with. Or the other way around: Only pixels changed on the +// Content *can* be changed by transparence values. +// +// This is 2.5 times faster than first painting to a +// 'combined' VDev that supports transparency, as is used by the +// presentation engine. +// For the mentioned factor refer to: +// Patch to demonstrate former and now repaint differences +// https://gerrit.libreoffice.org/c/core/+/129301 +// git fetch https://git.libreoffice.org/core refs/changes/01/129301/3 && git cherry-pick FETCH_HEAD +// +// Note: This principle only works when the target is RGB, so +// useful for EditViews like for PrimitiveRenderers where this is +// the case. For usage with GBA targets the starting conditions +// would have to be modified to not blend against the initial color +// of 'Content' (usually COL_WHITE). +// +// After having finished the rework of ShadowPrimitive2D, +// SoftEdgePrimitive2D and GlowPrimitive2D (see commits:) +// e735ad1c57cddaf17d6ffc0cf15b5e14fa63c4ad +// 707b0c328a282d993fa33b618083d20b6c521de6 +// c2d1458723c66c2fd717a112f89f773226adc841 +// which used the impBufferDevice in such a mode combined with +// mentioned 'combined' transparence VDev it is now possible +// to return to this former, much faster method. +// +// Please do *not* hack/use this helper class, better create +// a new one fitting your/the intended purpose. I do not want +// to see losing performance by this getting modified again. +// +// Note: Using that 'combined' transparence VDev is not really +// recommended, it may vanish anytime. That it works with +// PrimitiveRenderers *at all* is neither designed nor tested +// or recommended - it's pure coincidence. +// +// I hope that for the future all this will vanish by getting to +// fully RGBA-capable devices - what is planned and makes sense. + +namespace drawinglayer +{ +class impBufferDevice +{ + OutputDevice& mrOutDev; + VclPtr<VirtualDevice> mpContent; + VclPtr<VirtualDevice> mpAlpha; + tools::Rectangle maDestPixel; + +public: + impBufferDevice(OutputDevice& rOutDev, const basegfx::B2DRange& rRange); + ~impBufferDevice(); + + void paint(double fTrans = 0.0); + bool isVisible() const { return !maDestPixel.IsEmpty(); } + VirtualDevice& getContent(); + VirtualDevice& getTransparence(); +}; +} // end of namespace drawinglayer + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx new file mode 100644 index 0000000000..e2bf833a4e --- /dev/null +++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx @@ -0,0 +1,2655 @@ +/* -*- 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 <cmath> +#include <memory> +#include "vclmetafileprocessor2d.hxx" +#include "vclpixelprocessor2d.hxx" +#include <rtl/ustring.hxx> +#include <tools/gen.hxx> +#include <tools/stream.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <comphelper/flagguard.hxx> +#include <comphelper/processfactory.hxx> +#include <config_global.h> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dlinegeometry.hxx> +#include <basegfx/utils/gradienttools.hxx> +#include <vcl/virdev.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/gradient.hxx> +#include <vcl/graphictools.hxx> +#include <vcl/metaact.hxx> +#include <vcl/graph.hxx> // for PDFExtOutDevData Graphic support +#include <vcl/formpdfexport.hxx> // for PDFExtOutDevData Graphic support +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonHatchPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx> +#include <drawinglayer/primitive2d/controlprimitive2d.hxx> +#include <drawinglayer/primitive2d/graphicprimitive2d.hxx> +#include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx> +#include <drawinglayer/primitive2d/epsprimitive2d.hxx> +#include <drawinglayer/primitive2d/structuretagprimitive2d.hxx> +#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> // for Title/Description metadata +#include <drawinglayer/converters.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <tools/vcompat.hxx> + +#include <com/sun/star/awt/XControl.hpp> +#include <com/sun/star/i18n/BreakIterator.hpp> +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> + +using namespace com::sun::star; + +// #112245# definition for maximum allowed point count due to Metafile target. +// To be on the safe side with the old tools polygon, use slightly less than +// the theoretical maximum (bad experiences with tools polygon) + +#define MAX_POLYGON_POINT_COUNT_METAFILE (0x0000fff0) + +namespace +{ +// #112245# helper to split line polygon in half +void splitLinePolygon(const basegfx::B2DPolygon& rBasePolygon, basegfx::B2DPolygon& o_aLeft, + basegfx::B2DPolygon& o_aRight) +{ + const sal_uInt32 nCount(rBasePolygon.count()); + + if (nCount) + { + const sal_uInt32 nHalfCount((nCount - 1) >> 1); + + o_aLeft = basegfx::B2DPolygon(rBasePolygon, 0, nHalfCount + 1); + o_aLeft.setClosed(false); + + o_aRight = basegfx::B2DPolygon(rBasePolygon, nHalfCount, nCount - nHalfCount); + o_aRight.setClosed(false); + + if (rBasePolygon.isClosed()) + { + o_aRight.append(rBasePolygon.getB2DPoint(0)); + + if (rBasePolygon.areControlPointsUsed()) + { + o_aRight.setControlPoints(o_aRight.count() - 1, rBasePolygon.getPrevControlPoint(0), + rBasePolygon.getNextControlPoint(0)); + } + } + } + else + { + o_aLeft.clear(); + o_aRight.clear(); + } +} + +// #112245# helper to evtl. split filled polygons to maximum metafile point count +void fillPolyPolygonNeededToBeSplit(basegfx::B2DPolyPolygon& rPolyPolygon) +{ + const sal_uInt32 nPolyCount(rPolyPolygon.count()); + + if (!nPolyCount) + return; + + basegfx::B2DPolyPolygon aSplitted; + + for (sal_uInt32 a(0); a < nPolyCount; a++) + { + const basegfx::B2DPolygon& aCandidate(rPolyPolygon.getB2DPolygon(a)); + const sal_uInt32 nPointCount(aCandidate.count()); + bool bNeedToSplit(false); + + if (aCandidate.areControlPointsUsed()) + { + // compare with the maximum for bezier curved polygons + bNeedToSplit = nPointCount > ((MAX_POLYGON_POINT_COUNT_METAFILE / 3L) - 1); + } + else + { + // compare with the maximum for simple point polygons + bNeedToSplit = nPointCount > (MAX_POLYGON_POINT_COUNT_METAFILE - 1); + } + + if (bNeedToSplit) + { + // need to split the partial polygon + const basegfx::B2DRange aRange(aCandidate.getB2DRange()); + const basegfx::B2DPoint aCenter(aRange.getCenter()); + + if (aRange.getWidth() > aRange.getHeight()) + { + // clip in left and right + const basegfx::B2DPolyPolygon aLeft(basegfx::utils::clipPolygonOnParallelAxis( + aCandidate, false, true, aCenter.getX(), false)); + const basegfx::B2DPolyPolygon aRight(basegfx::utils::clipPolygonOnParallelAxis( + aCandidate, false, false, aCenter.getX(), false)); + + aSplitted.append(aLeft); + aSplitted.append(aRight); + } + else + { + // clip in top and bottom + const basegfx::B2DPolyPolygon aTop(basegfx::utils::clipPolygonOnParallelAxis( + aCandidate, true, true, aCenter.getY(), false)); + const basegfx::B2DPolyPolygon aBottom(basegfx::utils::clipPolygonOnParallelAxis( + aCandidate, true, false, aCenter.getY(), false)); + + aSplitted.append(aTop); + aSplitted.append(aBottom); + } + } + else + { + aSplitted.append(aCandidate); + } + } + + if (aSplitted.count() != nPolyCount) + { + rPolyPolygon = aSplitted; + } +} + +/** Filter input polypolygon for effectively empty sub-fills + + Needed to fix fdo#37559 + + @param rPoly + tools::PolyPolygon to filter + + @return converted tools PolyPolygon, w/o one-point fills + */ +tools::PolyPolygon getFillPolyPolygon(const ::basegfx::B2DPolyPolygon& rPoly) +{ + // filter input rPoly + basegfx::B2DPolyPolygon aPoly; + sal_uInt32 nCount(rPoly.count()); + for (sal_uInt32 i = 0; i < nCount; ++i) + { + const basegfx::B2DPolygon& aCandidate(rPoly.getB2DPolygon(i)); + if (!aCandidate.isClosed() || aCandidate.count() > 1) + aPoly.append(aCandidate); + } + return tools::PolyPolygon(aPoly); +} + +} // end of anonymous namespace + +namespace drawinglayer::processor2d +{ +tools::Rectangle +VclMetafileProcessor2D::impDumpToMetaFile(const primitive2d::Primitive2DContainer& rContent, + GDIMetaFile& o_rContentMetafile) +{ + // Prepare VDev, MetaFile and connections + OutputDevice* pLastOutputDevice = mpOutputDevice; + GDIMetaFile* pLastMetafile = mpMetaFile; + basegfx::B2DRange aPrimitiveRange(rContent.getB2DRange(getViewInformation2D())); + + // transform primitive range with current transformation (e.g shadow offset) + aPrimitiveRange.transform(maCurrentTransformation); + + const tools::Rectangle aPrimitiveRectangle( + basegfx::fround(aPrimitiveRange.getMinX()), basegfx::fround(aPrimitiveRange.getMinY()), + basegfx::fround(aPrimitiveRange.getMaxX()), basegfx::fround(aPrimitiveRange.getMaxY())); + ScopedVclPtrInstance<VirtualDevice> aContentVDev; + MapMode aNewMapMode(pLastOutputDevice->GetMapMode()); + + mpOutputDevice = aContentVDev.get(); + mpMetaFile = &o_rContentMetafile; + aContentVDev->EnableOutput(false); + aContentVDev->SetMapMode(pLastOutputDevice->GetMapMode()); + o_rContentMetafile.Record(aContentVDev.get()); + aContentVDev->SetLineColor(pLastOutputDevice->GetLineColor()); + aContentVDev->SetFillColor(pLastOutputDevice->GetFillColor()); + aContentVDev->SetFont(pLastOutputDevice->GetFont()); + aContentVDev->SetDrawMode(pLastOutputDevice->GetDrawMode()); + aContentVDev->SetSettings(pLastOutputDevice->GetSettings()); + aContentVDev->SetRefPoint(pLastOutputDevice->GetRefPoint()); + + // dump to MetaFile + process(rContent); + + // cleanups + o_rContentMetafile.Stop(); + o_rContentMetafile.WindStart(); + aNewMapMode.SetOrigin(aPrimitiveRectangle.TopLeft()); + o_rContentMetafile.SetPrefMapMode(aNewMapMode); + o_rContentMetafile.SetPrefSize(aPrimitiveRectangle.GetSize()); + mpOutputDevice = pLastOutputDevice; + mpMetaFile = pLastMetafile; + + return aPrimitiveRectangle; +} + +void VclMetafileProcessor2D::impConvertFillGradientAttributeToVCLGradient( + Gradient& o_rVCLGradient, const attribute::FillGradientAttribute& rFiGrAtt, + bool bIsTransparenceGradient) const +{ + const basegfx::BColor aStartColor(rFiGrAtt.getColorStops().front().getStopColor()); + const basegfx::BColor aEndColor(rFiGrAtt.getColorStops().back().getStopColor()); + + if (bIsTransparenceGradient) + { + // it's about transparence channel intensities (black/white), do not use color modifier + o_rVCLGradient.SetStartColor(Color(aStartColor)); + o_rVCLGradient.SetEndColor(Color(aEndColor)); + } + else + { + // use color modifier to influence start/end color of gradient + o_rVCLGradient.SetStartColor(Color(maBColorModifierStack.getModifiedColor(aStartColor))); + o_rVCLGradient.SetEndColor(Color(maBColorModifierStack.getModifiedColor(aEndColor))); + } + + o_rVCLGradient.SetAngle( + Degree10(static_cast<sal_uInt32>(basegfx::rad2deg<10>(rFiGrAtt.getAngle())))); + o_rVCLGradient.SetBorder(static_cast<sal_uInt16>(rFiGrAtt.getBorder() * 100.0)); + o_rVCLGradient.SetOfsX(static_cast<sal_uInt16>(rFiGrAtt.getOffsetX() * 100.0)); + o_rVCLGradient.SetOfsY(static_cast<sal_uInt16>(rFiGrAtt.getOffsetY() * 100.0)); + o_rVCLGradient.SetSteps(rFiGrAtt.getSteps()); + + // defaults for intensity; those were computed into the start/end colors already + o_rVCLGradient.SetStartIntensity(100); + o_rVCLGradient.SetEndIntensity(100); + o_rVCLGradient.SetStyle(rFiGrAtt.getStyle()); +} + +void VclMetafileProcessor2D::impStartSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill) +{ + if (pSvtGraphicFill && !mnSvtGraphicFillCount) + { + SvMemoryStream aMemStm; + + WriteSvtGraphicFill(aMemStm, *pSvtGraphicFill); + mpMetaFile->AddAction(new MetaCommentAction( + "XPATHFILL_SEQ_BEGIN"_ostr, 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), + aMemStm.TellEnd())); + mnSvtGraphicFillCount++; + } +} + +void VclMetafileProcessor2D::impEndSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill) +{ + if (pSvtGraphicFill && mnSvtGraphicFillCount) + { + mnSvtGraphicFillCount--; + mpMetaFile->AddAction(new MetaCommentAction("XPATHFILL_SEQ_END"_ostr)); + } +} + +double VclMetafileProcessor2D::getTransformedLineWidth(double fWidth) const +{ + // #i113922# the LineWidth is duplicated in the MetaPolylineAction, + // and also inside the SvtGraphicStroke and needs transforming into + // the same space as its coordinates here cf. fdo#61789 + // This is a partial fix. When an object transformation is used which + // e.g. contains a scaleX != scaleY, an unproportional scaling will happen. + const basegfx::B2DVector aDiscreteUnit(maCurrentTransformation + * basegfx::B2DVector(fWidth, 0.0)); + + return aDiscreteUnit.getLength(); +} + +std::unique_ptr<SvtGraphicStroke> VclMetafileProcessor2D::impTryToCreateSvtGraphicStroke( + const basegfx::B2DPolygon& rB2DPolygon, const basegfx::BColor* pColor, + const attribute::LineAttribute* pLineAttribute, + const attribute::StrokeAttribute* pStrokeAttribute, + const attribute::LineStartEndAttribute* pStart, const attribute::LineStartEndAttribute* pEnd) +{ + std::unique_ptr<SvtGraphicStroke> pRetval; + + if (rB2DPolygon.count() && !mnSvtGraphicStrokeCount) + { + basegfx::B2DPolygon aLocalPolygon(rB2DPolygon); + basegfx::BColor aStrokeColor; + basegfx::B2DPolyPolygon aStartArrow; + basegfx::B2DPolyPolygon aEndArrow; + + if (pColor) + { + aStrokeColor = *pColor; + } + else if (pLineAttribute) + { + aStrokeColor = maBColorModifierStack.getModifiedColor(pLineAttribute->getColor()); + } + + // It IS needed to record the stroke color at all in the metafile, + // SvtGraphicStroke has NO entry for stroke color(!) + mpOutputDevice->SetLineColor(Color(aStrokeColor)); + + if (!aLocalPolygon.isClosed()) + { + double fPolyLength(0.0); + double fStart(0.0); + double fEnd(0.0); + + if (pStart && pStart->isActive()) + { + fPolyLength = basegfx::utils::getLength(aLocalPolygon); + + aStartArrow = basegfx::utils::createAreaGeometryForLineStartEnd( + aLocalPolygon, pStart->getB2DPolyPolygon(), true, pStart->getWidth(), + fPolyLength, pStart->isCentered() ? 0.5 : 0.0, &fStart); + } + + if (pEnd && pEnd->isActive()) + { + if (basegfx::fTools::equalZero(fPolyLength)) + { + fPolyLength = basegfx::utils::getLength(aLocalPolygon); + } + + aEndArrow = basegfx::utils::createAreaGeometryForLineStartEnd( + aLocalPolygon, pEnd->getB2DPolyPolygon(), false, pEnd->getWidth(), fPolyLength, + pEnd->isCentered() ? 0.5 : 0.0, &fEnd); + } + + if (0.0 != fStart || 0.0 != fEnd) + { + // build new poly, consume something from old poly + aLocalPolygon = basegfx::utils::getSnippetAbsolute(aLocalPolygon, fStart, + fPolyLength - fEnd, fPolyLength); + } + } + + SvtGraphicStroke::JoinType eJoin(SvtGraphicStroke::joinNone); + SvtGraphicStroke::CapType eCap(SvtGraphicStroke::capButt); + double fLineWidth(0.0); + double fMiterLength(0.0); + SvtGraphicStroke::DashArray aDashArray; + + if (pLineAttribute) + { + fLineWidth = fMiterLength = getTransformedLineWidth(pLineAttribute->getWidth()); + + // get Join + switch (pLineAttribute->getLineJoin()) + { + case basegfx::B2DLineJoin::NONE: + { + eJoin = SvtGraphicStroke::joinNone; + break; + } + case basegfx::B2DLineJoin::Bevel: + { + eJoin = SvtGraphicStroke::joinBevel; + break; + } + case basegfx::B2DLineJoin::Miter: + { + eJoin = SvtGraphicStroke::joinMiter; + // ATM 15 degrees is assumed + // TODO wait for P1383R0 and C++20's std::numbers::pi + fMiterLength /= std::sin(M_PI / 12); + break; + } + case basegfx::B2DLineJoin::Round: + { + eJoin = SvtGraphicStroke::joinRound; + break; + } + } + + // get stroke + switch (pLineAttribute->getLineCap()) + { + default: /* css::drawing::LineCap_BUTT */ + { + eCap = SvtGraphicStroke::capButt; + break; + } + case css::drawing::LineCap_ROUND: + { + eCap = SvtGraphicStroke::capRound; + break; + } + case css::drawing::LineCap_SQUARE: + { + eCap = SvtGraphicStroke::capSquare; + break; + } + } + } + + if (pStrokeAttribute) + { + // copy dash array + aDashArray = pStrokeAttribute->getDotDashArray(); + } + + // #i101734# apply current object transformation to created geometry. + // This is a partial fix. When an object transformation is used which + // e.g. contains a scaleX != scaleY, an unproportional scaling would + // have to be applied to the evtl. existing fat line. The current + // concept of PDF export and SvtGraphicStroke usage does simply not + // allow handling such definitions. The only clean way would be to + // add the transformation to SvtGraphicStroke and to handle it there + aLocalPolygon.transform(maCurrentTransformation); + aStartArrow.transform(maCurrentTransformation); + aEndArrow.transform(maCurrentTransformation); + + pRetval.reset( + new SvtGraphicStroke(tools::Polygon(aLocalPolygon), tools::PolyPolygon(aStartArrow), + tools::PolyPolygon(aEndArrow), mfCurrentUnifiedTransparence, + fLineWidth, eCap, eJoin, fMiterLength, std::move(aDashArray))); + } + + return pRetval; +} + +void VclMetafileProcessor2D::impStartSvtGraphicStroke(SvtGraphicStroke const* pSvtGraphicStroke) +{ + if (pSvtGraphicStroke && !mnSvtGraphicStrokeCount) + { + SvMemoryStream aMemStm; + + WriteSvtGraphicStroke(aMemStm, *pSvtGraphicStroke); + mpMetaFile->AddAction(new MetaCommentAction( + "XPATHSTROKE_SEQ_BEGIN"_ostr, 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), + aMemStm.TellEnd())); + mnSvtGraphicStrokeCount++; + } +} + +void VclMetafileProcessor2D::impEndSvtGraphicStroke(SvtGraphicStroke const* pSvtGraphicStroke) +{ + if (pSvtGraphicStroke && mnSvtGraphicStrokeCount) + { + mnSvtGraphicStrokeCount--; + mpMetaFile->AddAction(new MetaCommentAction("XPATHSTROKE_SEQ_END"_ostr)); + } +} + +void VclMetafileProcessor2D::popStructureElement(vcl::PDFWriter::StructElement eElem) +{ + if (!maListElements.empty() && maListElements.top() == eElem) + { + maListElements.pop(); + mpPDFExtOutDevData->EndStructureElement(); + } +} + +void VclMetafileProcessor2D::popListItem() +{ + popStructureElement(vcl::PDFWriter::LIBody); + popStructureElement(vcl::PDFWriter::ListItem); +} + +void VclMetafileProcessor2D::popList() +{ + popListItem(); + popStructureElement(vcl::PDFWriter::List); +} + +// init static break iterator +vcl::DeleteOnDeinit<uno::Reference<css::i18n::XBreakIterator>> + VclMetafileProcessor2D::mxBreakIterator; + +VclMetafileProcessor2D::VclMetafileProcessor2D(const geometry::ViewInformation2D& rViewInformation, + OutputDevice& rOutDev) + : VclProcessor2D(rViewInformation, rOutDev) + , mpMetaFile(rOutDev.GetConnectMetaFile()) + , mnSvtGraphicFillCount(0) + , mnSvtGraphicStrokeCount(0) + , mfCurrentUnifiedTransparence(0.0) + , mpPDFExtOutDevData(dynamic_cast<vcl::PDFExtOutDevData*>(rOutDev.GetExtOutDevData())) + , mnCurrentOutlineLevel(-1) + , mbInListItem(false) + , mbBulletPresent(false) +{ + OSL_ENSURE(rOutDev.GetConnectMetaFile(), + "VclMetafileProcessor2D: Used on OutDev which has no MetaFile Target (!)"); + // draw to logic coordinates, do not initialize maCurrentTransformation to viewTransformation + // but only to ObjectTransformation. Do not change MapMode of destination. + maCurrentTransformation = rViewInformation.getObjectTransformation(); +} + +VclMetafileProcessor2D::~VclMetafileProcessor2D() +{ + // MapMode was not changed, no restore necessary +} + +/*********************************************************************************************** + + Support of MetaCommentActions in the VclMetafileProcessor2D + Found MetaCommentActions and how they are supported: + + XGRAD_SEQ_BEGIN, XGRAD_SEQ_END: + + Used inside OutputDevice::DrawGradient to mark the start and end of a MetaGradientEx action. + It is used in various exporters/importers to have direct access to the gradient before it + is rendered by VCL (and thus fragmented to polygon color actions and others). On that base, e.g. + the Metafile to SdrObject import creates its gradient objects. + Best (and safest) way to support it here is to use PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D, + map it back to the corresponding tools tools::PolyPolygon and the Gradient and just call + OutputDevice::DrawGradient which creates the necessary compatible actions. + + XPATHFILL_SEQ_BEGIN, XPATHFILL_SEQ_END: + + Two producers, one is vcl/source/gdi/gdimtf.cxx, line 1273. There, it is transformed + inside GDIMetaFile::Rotate, nothing to take care of here. + The second producer is in graphics/svx/source/svdraw/impgrfll.cxx, line 374. This is used + with each incarnation of Imp_GraphicFill when a metafile is recorded, fillstyle is not + XFILL_NONE and not completely transparent. It creates a SvtGraphicFill and streams it + to the comment action. A closing end token is created in the destructor. + Usages of Imp_GraphicFill are in Do_Paint_Object-methods of SdrCircObj, SdrPathObj and + SdrRectObj. + The token users pick various actions from SvtGraphicFill, so it may need to be added for all kind + of filled objects, even simple colored polygons. It is added as extra information; the + Metafile actions between the two tokens are interpreted as output generated from those + fills. Thus, users have the choice to use the SvtGraphicFill info or the created output + actions. + Even for XFillTransparenceItem it is used, thus it may need to be supported in + UnifiedTransparencePrimitive2D, too, when interpreted as normally filled PolyPolygon. + Implemented for: + PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D, + PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D, + PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D, + PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D, + and for PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D when detected unified transparence + + XPATHSTROKE_SEQ_BEGIN, XPATHSTROKE_SEQ_END: + + Similar to pathfill, but using SvtGraphicStroke instead. It also has two producers where one + is also the GDIMetaFile::Rotate. Another user is MetaCommentAction::Move which modifies the + contained path accordingly. + The other one is SdrObject::Imp_DrawLineGeometry. It's done when MetaFile is set at OutDev and + only when geometry is a single polygon (!). I see no reason for that; in the PS exporter this + would hinder to make use of tools::PolyPolygon strokes. I will need to add support at: + PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D + PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D + PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D + This can be done hierarchical, too. + Okay, base implementation done based on those three primitives. + + FIELD_SEQ_BEGIN, FIELD_SEQ_END + + Used from slideshow for URLs, created from diverse SvxField implementations inside + createBeginComment()/createEndComment(). createBeginComment() is used from editeng\impedit3.cxx + inside ImpEditEngine::Paint. + Created TextHierarchyFieldPrimitive2D and added needed infos there; it is a group primitive and wraps + text primitives (but is not limited to that). It contains the field type if special actions for the + support of FIELD_SEQ_BEGIN/END are needed; this is the case for Page and URL fields. If more is + needed, it may be supported there. + FIELD_SEQ_BEGIN;PageField + FIELD_SEQ_END + Okay, these are now completely supported by TextHierarchyFieldPrimitive2D. URL works, too. + + XTEXT + + XTEXT_EOC(i) end of character + XTEXT_EOW(i) end of word + XTEXT_EOS(i) end of sentence + + this three are with index and are created with the help of an i18n::XBreakIterator in + ImplDrawWithComments. Simplifying, moving out text painting, reworking to create some + data structure for holding those TEXT infos. + Supported directly by TextSimplePortionPrimitive2D with adding a Locale to the basic text + primitive. In the MetaFileRenderer, the creation is now done (see below). This has the advantage + that this creations do not need to be done for all paints all the time. This would be + expensive since the BreakIterator and it's usage is expensive and for each paint also the + whole character stops would need to be created. + Created only for TextDecoratedPortionPrimitive2D due to XTEXT_EOL and XTEXT_EOP (see below) + + XTEXT_EOL() end of line + XTEXT_EOP() end of paragraph + + First try with boolean marks at TextDecoratedPortionPrimitive2D did not work too well, + i decided to solve it with structure. I added the TextHierarchyPrimitives for this, + namely: + - TextHierarchyLinePrimitive2D: Encapsulates single line + - TextHierarchyParagraphPrimitive2D: Encapsulates single paragraph + - TextHierarchyBlockPrimitive2D: encapsulates object texts (only one ATM) + Those are now supported in hierarchy. This means the MetaFile renderer will support them + by using them, recursively using their content and adding MetaFile comments as needed. + This also means that when another text layouter will be used it will be necessary to + create/support the same HierarchyPrimitives to support users. + To transport the information using this hierarchy is best suited to all future needs; + the slideshow will be able to profit from it directly when using primitives; all other + renderers not interested in the text structure will just ignore the encapsulations. + + XTEXT_PAINTSHAPE_BEGIN, XTEXT_PAINTSHAPE_END + Supported now by the TextHierarchyBlockPrimitive2D. + + EPSReplacementGraphic: + Only used in goodies\source\filter.vcl\ieps\ieps.cxx and svx\source\xml\xmlgrhlp.cxx to + hold the original EPS which was imported in the same MetaFile as first 2 entries. Only + used to export the original again (if exists). + Not necessary to support with MetaFileRenderer. + + XTEXT_SCROLLRECT, XTEXT_PAINTRECT + Currently used to get extra MetaFile infos using GraphicExporter which again uses + SdrTextObj::GetTextScrollMetaFileAndRectangle(). ATM works with primitives since + the rectangle data is added directly by the GraphicsExporter as comment. Does not need + to be adapted at once. + When adapting later, the only user - the diashow - should directly use the provided + Animation infos in the appropriate primitives (e.g. AnimatedSwitchPrimitive2D) + + PRNSPOOL_TRANSPARENTBITMAP_BEGIN, PRNSPOOL_TRANSPARENTBITMAP_END + VCL usage when printing PL -> THB. Okay, THB confirms that it is only used as + a fix (hack) while VCL printing. It is needed to not downscale a bitmap which + was explicitly created for the printer already again to some default maximum + bitmap sizes. + Nothing to do here for the primitive renderer. + + Support for vcl::PDFExtOutDevData: + PL knows that SJ did that stuff, it's used to hold a pointer to PDFExtOutDevData at + the OutDev. When set, some extra data is written there. Trying simple PDF export and + watching if I get those infos. + Well, a PDF export does not use e.g. ImpEditEngine::Paint since the PdfFilter uses + the SdXImpressDocument::render and thus uses the VclMetafileProcessor2D. I will check + if I get a PDFExtOutDevData at the target output device. + Indeed, I get one. Checking what all may be done when that extra-device-info is there. + + All in all I have to talk to SJ. I will need to emulate some of those actions, but + i need to discuss which ones. + In the future, all those infos would be taken from the primitive sequence anyways, + thus these extensions would potentially be temporary, too. + Discussed with SJ, added the necessary support and tested it. Details follow. + + - In ImpEditEngine::Paint, paragraph infos and URL stuff is added. + Added in primitive MetaFile renderer. + Checking URL: Indeed, current version exports it, but it is missing in primitive + CWS version. Adding support. + Okay, URLs work. Checked, Done. + + - UnoControlPDFExportContact is only created when PDFExtOutDevData is used at the + target and uno control data is created in UnoControlPDFExportContact::do_PaintObject. + This was added in primitive MetaFile renderer. + Checked form control export, it works well. Done. + + - In goodies, in GraphicObject::Draw, when the used Graphic is linked, infos are + generated. I will need to check what happens here with primitives. + To support, use of GraphicPrimitive2D (PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D) may be needed. + Added support, but feature is broken in main version, so i cannot test at all. + Writing a bug to CL (or SJ) and seeing what happens (#i80380#). + SJ took a look and we got it working. Tested VCL MetaFile Renderer based export, + as intended, the original file is exported. Works, Done. + + + To be done: + + - Maybe there are more places to take care of for vcl::PDFExtOutDevData! + + +****************************************************************************************************/ + +void VclMetafileProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) +{ + switch (rCandidate.getPrimitive2DID()) + { + case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D: + { + // directdraw of wrong spell primitive + // Ignore for VclMetafileProcessor2D, this is for printing and MetaFile recording only + break; + } + case PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D: + { + processGraphicPrimitive2D( + static_cast<const primitive2d::GraphicPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D: + { + processControlPrimitive2D( + static_cast<const primitive2d::ControlPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTHIERARCHYFIELDPRIMITIVE2D: + { + processTextHierarchyFieldPrimitive2D( + static_cast<const primitive2d::TextHierarchyFieldPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTHIERARCHYLINEPRIMITIVE2D: + { + processTextHierarchyLinePrimitive2D( + static_cast<const primitive2d::TextHierarchyLinePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTHIERARCHYBULLETPRIMITIVE2D: + { + processTextHierarchyBulletPrimitive2D( + static_cast<const primitive2d::TextHierarchyBulletPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTHIERARCHYPARAGRAPHPRIMITIVE2D: + { + processTextHierarchyParagraphPrimitive2D( + static_cast<const primitive2d::TextHierarchyParagraphPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTHIERARCHYBLOCKPRIMITIVE2D: + { + processTextHierarchyBlockPrimitive2D( + static_cast<const primitive2d::TextHierarchyBlockPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D: + case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D: + { + // for supporting TEXT_ MetaFile actions there is more to do here; get the candidate + processTextSimplePortionPrimitive2D( + static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D: + { + processPolygonHairlinePrimitive2D( + static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D: + { + processPolygonStrokePrimitive2D( + static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D: + { + processPolygonStrokeArrowPrimitive2D( + static_cast<const primitive2d::PolygonStrokeArrowPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D: + { + // direct draw of transformed BitmapEx primitive; use default processing, but without + // former testing if graphic content is inside discrete local viewport; this is not + // setup for metafile targets (metafile renderer tries to render in logic coordinates, + // the mapping is kept to the OutputDevice for better Metafile recording) + RenderBitmapPrimitive2D(static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D: + { + if (maBColorModifierStack.count()) + { + // tdf#151104 unfortunately processPolyPolygonGraphicPrimitive2D below + // does not support an active BColorModifierStack, so use the default + process(rCandidate); + } + else + { + processPolyPolygonGraphicPrimitive2D( + static_cast<const primitive2d::PolyPolygonGraphicPrimitive2D&>(rCandidate)); + } + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D: + { + processPolyPolygonHatchPrimitive2D( + static_cast<const primitive2d::PolyPolygonHatchPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D: + { + processPolyPolygonGradientPrimitive2D( + static_cast<const primitive2d::PolyPolygonGradientPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D: + { + processPolyPolygonColorPrimitive2D( + static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MASKPRIMITIVE2D: + { + processMaskPrimitive2D(static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D: + { + // modified color group. Force output to unified color. Use default processing. + RenderModifiedColorPrimitive2D( + static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D: + { + processUnifiedTransparencePrimitive2D( + static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D: + { + processTransparencePrimitive2D( + static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D: + { + // use default transform group processing + RenderTransformPrimitive2D( + static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D: + { + // new XDrawPage for ViewInformation2D + RenderPagePreviewPrimitive2D( + static_cast<const primitive2d::PagePreviewPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D: + { + // use default marker array processing + RenderMarkerArrayPrimitive2D( + static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D: + { + // use default point array processing + RenderPointArrayPrimitive2D( + static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_STRUCTURETAGPRIMITIVE2D: + { + processStructureTagPrimitive2D( + static_cast<const primitive2d::StructureTagPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTHIERARCHYEDITPRIMITIVE2D: + { + // This primitive is created if a text edit is active and contains it's + // current content, not from model data itself. + // Pixel renderers need to suppress that content, it gets displayed by the active + // TextEdit in the EditView. Suppression is done by decomposing to nothing. + // MetaFile renderers have to show it, so that the edited text is part of the + // MetaFile, e.g. needed for presentation previews and exports. + // So take action here and process it's content: + // Note: Former error was #i97628# + process(static_cast<const primitive2d::TextHierarchyEditPrimitive2D&>(rCandidate) + .getContent()); + break; + } + case PRIMITIVE2D_ID_EPSPRIMITIVE2D: + { + RenderEpsPrimitive2D(static_cast<const primitive2d::EpsPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D: + { + processObjectInfoPrimitive2D( + static_cast<const primitive2d::ObjectInfoPrimitive2D&>(rCandidate)); + break; + } + default: + { + // process recursively + process(rCandidate); + break; + } + } +} + +void VclMetafileProcessor2D::processObjectInfoPrimitive2D( + primitive2d::ObjectInfoPrimitive2D const& rObjectInfoPrimitive2D) +{ + // tdf#154982 process content first, so this object overrides any nested one + process(rObjectInfoPrimitive2D.getChildren()); + + // currently StructureTagPrimitive2D is only used for SdrObjects - have to + // avoid adding Alt text if the SdrObject is not actually tagged, as it + // would then end up on an unrelated structure element. + if (mpCurrentStructureTag && mpCurrentStructureTag->isTaggedSdrObject()) + { + // Create image alternative description from ObjectInfoPrimitive2D info + // for PDF export, for the currently active SdrObject's structure element + if (mpPDFExtOutDevData->GetIsExportTaggedPDF()) + { + OUString aAlternateDescription; + + if (!rObjectInfoPrimitive2D.getTitle().isEmpty()) + { + aAlternateDescription += rObjectInfoPrimitive2D.getTitle(); + } + + if (!rObjectInfoPrimitive2D.getDesc().isEmpty()) + { + if (!aAlternateDescription.isEmpty()) + { + aAlternateDescription += " - "; + } + + aAlternateDescription += rObjectInfoPrimitive2D.getDesc(); + } + + // Use SetAlternateText to set it. This will work as long as some + // structure is used (see PDFWriterImpl::setAlternateText and + // m_nCurrentStructElement - tagged PDF export works with this in + // Draw/Impress/Writer, but not in Calc due to too less structure...) + //Z maybe add structure to Calc PDF export, may need some BeginGroup/EndGroup stuff ..? + if (!aAlternateDescription.isEmpty()) + { + mpPDFExtOutDevData->SetAlternateText(aAlternateDescription); + } + } + } +} + +void VclMetafileProcessor2D::processGraphicPrimitive2D( + const primitive2d::GraphicPrimitive2D& rGraphicPrimitive) +{ + bool bUsingPDFExtOutDevData(false); + basegfx::B2DVector aTranslate, aScale; + static bool bSuppressPDFExtOutDevDataSupport(false); // loplugin:constvars:ignore + + if (mpPDFExtOutDevData && !bSuppressPDFExtOutDevDataSupport) + { + // emulate data handling from UnoControlPDFExportContact, original see + // svtools/source/graphic/grfmgr.cxx + const Graphic& rGraphic = rGraphicPrimitive.getGraphicObject().GetGraphic(); + + if (rGraphic.IsGfxLink()) + { + const GraphicAttr& rAttr = rGraphicPrimitive.getGraphicAttr(); + + if (!rAttr.IsSpecialDrawMode() && !rAttr.IsAdjusted()) + { + const basegfx::B2DHomMatrix& rTransform = rGraphicPrimitive.getTransform(); + double fRotate, fShearX; + rTransform.decompose(aScale, aTranslate, fRotate, fShearX); + + if (basegfx::fTools::equalZero(fRotate) && (aScale.getX() > 0.0) + && (aScale.getY() > 0.0)) + { + bUsingPDFExtOutDevData = true; + mpPDFExtOutDevData->BeginGroup(); + } + } + } + } + + // process recursively and add MetaFile comment + process(rGraphicPrimitive); + + if (!bUsingPDFExtOutDevData) + return; + + // emulate data handling from UnoControlPDFExportContact, original see + // svtools/source/graphic/grfmgr.cxx + const basegfx::B2DRange aCurrentRange(aTranslate.getX(), aTranslate.getY(), + aTranslate.getX() + aScale.getX(), + aTranslate.getY() + aScale.getY()); + const tools::Rectangle aCurrentRect( + sal_Int32(floor(aCurrentRange.getMinX())), sal_Int32(floor(aCurrentRange.getMinY())), + sal_Int32(ceil(aCurrentRange.getMaxX())), sal_Int32(ceil(aCurrentRange.getMaxY()))); + const GraphicAttr& rAttr = rGraphicPrimitive.getGraphicAttr(); + // fdo#72530 don't pass empty Rectangle to EndGroup + tools::Rectangle aCropRect(aCurrentRect); + + if (rAttr.IsCropped()) + { + // calculate scalings between real image size and logic object size. This + // is necessary since the crop values are relative to original bitmap size + double fFactorX(1.0); + double fFactorY(1.0); + + { + const MapMode aMapMode100thmm(MapUnit::Map100thMM); + const Size aBitmapSize(OutputDevice::LogicToLogic( + rGraphicPrimitive.getGraphicObject().GetPrefSize(), + rGraphicPrimitive.getGraphicObject().GetPrefMapMode(), aMapMode100thmm)); + const double fDivX(aBitmapSize.Width() - rAttr.GetLeftCrop() - rAttr.GetRightCrop()); + const double fDivY(aBitmapSize.Height() - rAttr.GetTopCrop() - rAttr.GetBottomCrop()); + + if (!basegfx::fTools::equalZero(fDivX)) + { + fFactorX = aScale.getX() / fDivX; + } + + if (!basegfx::fTools::equalZero(fDivY)) + { + fFactorY = aScale.getY() / fDivY; + } + } + + // calculate crop range and rect + basegfx::B2DRange aCropRange; + aCropRange.expand( + aCurrentRange.getMinimum() + - basegfx::B2DPoint(rAttr.GetLeftCrop() * fFactorX, rAttr.GetTopCrop() * fFactorY)); + aCropRange.expand( + aCurrentRange.getMaximum() + + basegfx::B2DPoint(rAttr.GetRightCrop() * fFactorX, rAttr.GetBottomCrop() * fFactorY)); + + aCropRect = tools::Rectangle( + sal_Int32(floor(aCropRange.getMinX())), sal_Int32(floor(aCropRange.getMinY())), + sal_Int32(ceil(aCropRange.getMaxX())), sal_Int32(ceil(aCropRange.getMaxY()))); + } + + // #i123295# 3rd param is uncropped rect, 4th is cropped. The primitive has the cropped + // object transformation, thus aCurrentRect *is* the clip region while aCropRect is the expanded, + // uncropped region. Thus, correct order is aCropRect, aCurrentRect + mpPDFExtOutDevData->EndGroup(rGraphicPrimitive.getGraphicObject().GetGraphic(), + 255 - rAttr.GetAlpha(), aCropRect, aCurrentRect); +} + +void VclMetafileProcessor2D::processControlPrimitive2D( + const primitive2d::ControlPrimitive2D& rControlPrimitive) +{ + const uno::Reference<awt::XControl>& rXControl(rControlPrimitive.getXControl()); + bool bIsPrintableControl(false); + + // find out if control is printable + if (rXControl.is()) + { + try + { + uno::Reference<beans::XPropertySet> xModelProperties(rXControl->getModel(), + uno::UNO_QUERY); + uno::Reference<beans::XPropertySetInfo> xPropertyInfo( + xModelProperties.is() ? xModelProperties->getPropertySetInfo() + : uno::Reference<beans::XPropertySetInfo>()); + static constexpr OUString sPrintablePropertyName(u"Printable"_ustr); + + if (xPropertyInfo.is() && xPropertyInfo->hasPropertyByName(sPrintablePropertyName)) + { + OSL_VERIFY(xModelProperties->getPropertyValue(sPrintablePropertyName) + >>= bIsPrintableControl); + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("drawinglayer", + "VclMetafileProcessor2D: No access to printable flag of Control"); + } + } + + // PDF export and printing only for printable controls + if (!bIsPrintableControl) + return; + + ::std::optional<sal_Int32> oAnchorParent; + if (mpPDFExtOutDevData) + { + if (rControlPrimitive.GetAnchorStructureElementKey()) + { + sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement( + rControlPrimitive.GetAnchorStructureElementKey()); + oAnchorParent.emplace(mpPDFExtOutDevData->GetCurrentStructureElement()); + mpPDFExtOutDevData->SetCurrentStructureElement(id); + } + } + + const bool bPDFExport(mpPDFExtOutDevData && mpPDFExtOutDevData->GetIsExportFormFields()); + bool bDoProcessRecursively(true); + + if (bPDFExport) + { + // PDF export. Emulate data handling from UnoControlPDFExportContact + std::unique_ptr<vcl::PDFWriter::AnyWidget> pPDFControl( + ::toolkitform::describePDFControl(rXControl, *mpPDFExtOutDevData)); + + if (pPDFControl) + { + // still need to fill in the location (is a class Rectangle) + const basegfx::B2DRange aRangeLogic( + rControlPrimitive.getB2DRange(getViewInformation2D())); + const tools::Rectangle aRectLogic(static_cast<sal_Int32>(floor(aRangeLogic.getMinX())), + static_cast<sal_Int32>(floor(aRangeLogic.getMinY())), + static_cast<sal_Int32>(ceil(aRangeLogic.getMaxX())), + static_cast<sal_Int32>(ceil(aRangeLogic.getMaxY()))); + pPDFControl->Location = aRectLogic; + + Size aFontSize(pPDFControl->TextFont.GetFontSize()); + aFontSize = OutputDevice::LogicToLogic(aFontSize, MapMode(MapUnit::MapPoint), + mpOutputDevice->GetMapMode()); + pPDFControl->TextFont.SetFontSize(aFontSize); + + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Form); + vcl::PDFWriter::StructAttributeValue role; + switch (pPDFControl->Type) + { + case vcl::PDFWriter::PushButton: + role = vcl::PDFWriter::Pb; + break; + case vcl::PDFWriter::RadioButton: + role = vcl::PDFWriter::Rb; + break; + case vcl::PDFWriter::CheckBox: + role = vcl::PDFWriter::Cb; + break; + default: // there is a paucity of roles, tv is the catch-all one + role = vcl::PDFWriter::Tv; + break; + } + // ISO 14289-1:2014, Clause: 7.18.4 + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Role, role); + // ISO 14289-1:2014, Clause: 7.18.1 + OUString const& rAltText(rControlPrimitive.GetAltText()); + if (!rAltText.isEmpty()) + { + mpPDFExtOutDevData->SetAlternateText(rAltText); + } + mpPDFExtOutDevData->CreateControl(*pPDFControl); + mpPDFExtOutDevData->EndStructureElement(); + if (oAnchorParent) + { + mpPDFExtOutDevData->SetCurrentStructureElement(*oAnchorParent); + } + + // no normal paint needed (see original UnoControlPDFExportContact::do_PaintObject); + // do not process recursively + bDoProcessRecursively = false; + } + else + { + // PDF export did not work, try simple output. + // Fallback to printer output by not setting bDoProcessRecursively + // to false. + } + } + + if (!bDoProcessRecursively) + { + return; + } + + if (mpPDFExtOutDevData) + { // no corresponding PDF Form, use Figure instead + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Figure); + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Placement, vcl::PDFWriter::Block); + auto const range(rControlPrimitive.getB2DRange(getViewInformation2D())); + tools::Rectangle const aLogicRect( + basegfx::fround(range.getMinX()), basegfx::fround(range.getMinY()), + basegfx::fround(range.getMaxX()), basegfx::fround(range.getMaxY())); + mpPDFExtOutDevData->SetStructureBoundingBox(aLogicRect); + OUString const& rAltText(rControlPrimitive.GetAltText()); + if (!rAltText.isEmpty()) + { + mpPDFExtOutDevData->SetAlternateText(rAltText); + } + } + + // #i93169# used flag the wrong way; true means that nothing was done yet + if (bDoProcessRecursively) + { + // printer output + try + { + // remember old graphics and create new + uno::Reference<awt::XView> xControlView(rXControl, uno::UNO_QUERY_THROW); + const uno::Reference<awt::XGraphics> xOriginalGraphics(xControlView->getGraphics()); + const uno::Reference<awt::XGraphics> xNewGraphics(mpOutputDevice->CreateUnoGraphics()); + + if (xNewGraphics.is()) + { + // link graphics and view + xControlView->setGraphics(xNewGraphics); + + // get position + const basegfx::B2DHomMatrix aObjectToDiscrete( + getViewInformation2D().getObjectToViewTransformation() + * rControlPrimitive.getTransform()); + const basegfx::B2DPoint aTopLeftDiscrete(aObjectToDiscrete + * basegfx::B2DPoint(0.0, 0.0)); + + // draw it + xControlView->draw(basegfx::fround(aTopLeftDiscrete.getX()), + basegfx::fround(aTopLeftDiscrete.getY())); + bDoProcessRecursively = false; + + // restore original graphics + xControlView->setGraphics(xOriginalGraphics); + } + } + catch (const uno::Exception&) + { + TOOLS_WARN_EXCEPTION("drawinglayer", + "VclMetafileProcessor2D: Printing of Control failed"); + } + } + + // process recursively if not done yet to export as decomposition (bitmap) + if (bDoProcessRecursively) + { + process(rControlPrimitive); + } + + if (mpPDFExtOutDevData) + { + mpPDFExtOutDevData->EndStructureElement(); + if (oAnchorParent) + { + mpPDFExtOutDevData->SetCurrentStructureElement(*oAnchorParent); + } + } +} + +void VclMetafileProcessor2D::processTextHierarchyFieldPrimitive2D( + const primitive2d::TextHierarchyFieldPrimitive2D& rFieldPrimitive) +{ + // support for FIELD_SEQ_BEGIN, FIELD_SEQ_END and URL. It wraps text primitives (but is not limited to) + // thus do the MetafileAction embedding stuff but just handle recursively. + static constexpr OString aCommentStringCommon("FIELD_SEQ_BEGIN"_ostr); + OUString aURL; + + switch (rFieldPrimitive.getType()) + { + default: // case drawinglayer::primitive2d::FIELD_TYPE_COMMON : + { + mpMetaFile->AddAction(new MetaCommentAction(aCommentStringCommon)); + break; + } + case drawinglayer::primitive2d::FIELD_TYPE_PAGE: + { + mpMetaFile->AddAction(new MetaCommentAction("FIELD_SEQ_BEGIN;PageField"_ostr)); + break; + } + case drawinglayer::primitive2d::FIELD_TYPE_URL: + { + aURL = rFieldPrimitive.getValue("URL"); + + if (!aURL.isEmpty()) + { + mpMetaFile->AddAction(new MetaCommentAction( + aCommentStringCommon, 0, reinterpret_cast<const sal_uInt8*>(aURL.getStr()), + 2 * aURL.getLength())); + } + + break; + } + } + + // process recursively + primitive2d::Primitive2DContainer rContent; + rFieldPrimitive.get2DDecomposition(rContent, getViewInformation2D()); + process(rContent); + + // for the end comment the type is not relevant yet, they are all the same. Just add. + mpMetaFile->AddAction(new MetaCommentAction("FIELD_SEQ_END"_ostr)); + + if (!(mpPDFExtOutDevData + && drawinglayer::primitive2d::FIELD_TYPE_URL == rFieldPrimitive.getType())) + return; + + // emulate data handling from ImpEditEngine::Paint + const basegfx::B2DRange aViewRange(rContent.getB2DRange(getViewInformation2D())); + const tools::Rectangle aRectLogic(static_cast<sal_Int32>(floor(aViewRange.getMinX())), + static_cast<sal_Int32>(floor(aViewRange.getMinY())), + static_cast<sal_Int32>(ceil(aViewRange.getMaxX())), + static_cast<sal_Int32>(ceil(aViewRange.getMaxY()))); + vcl::PDFExtOutDevBookmarkEntry aBookmark; + OUString const content(rFieldPrimitive.getValue("Representation")); + aBookmark.nLinkId = mpPDFExtOutDevData->CreateLink(aRectLogic, content); + aBookmark.aBookmark = aURL; + std::vector<vcl::PDFExtOutDevBookmarkEntry>& rBookmarks = mpPDFExtOutDevData->GetBookmarks(); + rBookmarks.push_back(aBookmark); +} + +void VclMetafileProcessor2D::processTextHierarchyLinePrimitive2D( + const primitive2d::TextHierarchyLinePrimitive2D& rLinePrimitive) +{ + // process recursively and add MetaFile comment + process(rLinePrimitive); + mpMetaFile->AddAction(new MetaCommentAction("XTEXT_EOL"_ostr)); +} + +void VclMetafileProcessor2D::processTextHierarchyBulletPrimitive2D( + const primitive2d::TextHierarchyBulletPrimitive2D& rBulletPrimitive) +{ + // this is a part of list item, start LILabel ( = bullet) + if (mbInListItem) + { + maListElements.push(vcl::PDFWriter::LILabel); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::LILabel); + } + + // process recursively and add MetaFile comment + process(rBulletPrimitive); + // in Outliner::PaintBullet(), a MetafileComment for bullets is added, too. The + // "XTEXT_EOC" is used, use here, too. + mpMetaFile->AddAction(new MetaCommentAction("XTEXT_EOC"_ostr)); + + if (mbInListItem) + { + if (maListElements.top() == vcl::PDFWriter::LILabel) + { + maListElements.pop(); + mpPDFExtOutDevData->EndStructureElement(); // end LILabel + mbBulletPresent = true; + } + } +} + +void VclMetafileProcessor2D::processTextHierarchyParagraphPrimitive2D( + const primitive2d::TextHierarchyParagraphPrimitive2D& rParagraphPrimitive) +{ + static constexpr OString aCommentString("XTEXT_EOP"_ostr); + static bool bSuppressPDFExtOutDevDataSupport(false); // loplugin:constvars:ignore + + if (nullptr == mpPDFExtOutDevData || bSuppressPDFExtOutDevDataSupport) + { + // Non-PDF export behaviour (metafile only). + // Process recursively and add MetaFile comment. + process(rParagraphPrimitive); + mpMetaFile->AddAction(new MetaCommentAction(aCommentString)); + return; + } + + if (!mpPDFExtOutDevData->GetIsExportTaggedPDF()) + { + // No Tagged PDF -> Dump as Paragraph + // Emulate data handling from old ImpEditEngine::Paint + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Paragraph); + + // Process recursively and add MetaFile comment + process(rParagraphPrimitive); + mpMetaFile->AddAction(new MetaCommentAction(aCommentString)); + + // Emulate data handling from ImpEditEngine::Paint + mpPDFExtOutDevData->EndStructureElement(); + return; + } + + // Create Tagged PDF -> deeper tagged data using StructureElements. + // Use OutlineLevel from ParagraphPrimitive, ensure not below -1 what + // means 'not active' + const sal_Int16 nNewOutlineLevel( + std::max(static_cast<sal_Int16>(-1), rParagraphPrimitive.getOutlineLevel())); + + // Do we have a change in OutlineLevel compared to the current one? + if (nNewOutlineLevel != mnCurrentOutlineLevel) + { + if (nNewOutlineLevel > mnCurrentOutlineLevel) + { + // increase List level + for (sal_Int16 a(mnCurrentOutlineLevel); a != nNewOutlineLevel; ++a) + { + maListElements.push(vcl::PDFWriter::List); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::List); + } + } + else // if(nNewOutlineLevel < mnCurrentOutlineLevel) + { + // close list levels below nNewOutlineLevel completely by removing + // list items as well as list tag itself + for (sal_Int16 a(nNewOutlineLevel); a < mnCurrentOutlineLevel; ++a) + { + popList(); // end LBody LI and L + } + + // on nNewOutlineLevel close the previous list item (LBody and LI) + popListItem(); + } + + // Remember new current OutlineLevel + mnCurrentOutlineLevel = nNewOutlineLevel; + } + else // the same list level + { + // close the previous list item (LBody and LI) + popListItem(); + } + + const bool bDumpAsListItem(-1 != mnCurrentOutlineLevel); + + if (bDumpAsListItem) + { + // Dump as ListItem + maListElements.push(vcl::PDFWriter::ListItem); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::ListItem); + mbInListItem = true; + } + else + { + // Dump as Paragraph + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::Paragraph); + } + + // Process recursively and add MetaFile comment + process(rParagraphPrimitive); + mpMetaFile->AddAction(new MetaCommentAction(aCommentString)); + + if (bDumpAsListItem) + mbInListItem = false; + else + mpPDFExtOutDevData->EndStructureElement(); // end Paragraph +} + +void VclMetafileProcessor2D::processTextHierarchyBlockPrimitive2D( + const primitive2d::TextHierarchyBlockPrimitive2D& rBlockPrimitive) +{ + // add MetaFile comment, process recursively and add MetaFile comment + mpMetaFile->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_BEGIN"_ostr)); + process(rBlockPrimitive); + + if (mnCurrentOutlineLevel >= 0) + { + // end any opened List structure elements (LBody, LI, L) + for (sal_Int16 a(0); a <= mnCurrentOutlineLevel; ++a) + { + popList(); + } + } + + mpMetaFile->AddAction(new MetaCommentAction("XTEXT_PAINTSHAPE_END"_ostr)); +} + +void VclMetafileProcessor2D::processTextSimplePortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate) +{ + // Adapt evtl. used special DrawMode + const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode()); + adaptTextToFillDrawMode(); + + // this is a 2nd portion of list item + // bullet has been already processed, start LIBody + if (mbInListItem && mbBulletPresent) + { + maListElements.push(vcl::PDFWriter::LIBody); + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::LIBody); + } + + // directdraw of text simple portion; use default processing + RenderTextSimpleOrDecoratedPortionPrimitive2D(rTextCandidate); + + if (mbInListItem && mbBulletPresent) + mbBulletPresent = false; + + // restore DrawMode + mpOutputDevice->SetDrawMode(nOriginalDrawMode); + + // #i101169# if(pTextDecoratedCandidate) + { + // support for TEXT_ MetaFile actions only for decorated texts + if (!mxBreakIterator.get() || !mxBreakIterator.get()->get()) + { + uno::Reference<uno::XComponentContext> xContext( + ::comphelper::getProcessComponentContext()); + mxBreakIterator.set(i18n::BreakIterator::create(xContext)); + } + auto& rBreakIterator = *mxBreakIterator.get()->get(); + + const OUString& rTxt = rTextCandidate.getText(); + const sal_Int32 nTextLength(rTextCandidate.getTextLength()); // rTxt.getLength()); + + if (nTextLength) + { + const css::lang::Locale& rLocale = rTextCandidate.getLocale(); + const sal_Int32 nTextPosition(rTextCandidate.getTextPosition()); + + sal_Int32 nDone; + sal_Int32 nNextCellBreak(rBreakIterator.nextCharacters( + rTxt, nTextPosition, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 0, + nDone)); + css::i18n::Boundary nNextWordBoundary(rBreakIterator.getWordBoundary( + rTxt, nTextPosition, rLocale, css::i18n::WordType::ANY_WORD, true)); + sal_Int32 nNextSentenceBreak( + rBreakIterator.endOfSentence(rTxt, nTextPosition, rLocale)); + static constexpr OStringLiteral aCommentStringA("XTEXT_EOC"); + static constexpr OStringLiteral aCommentStringB("XTEXT_EOW"); + static constexpr OStringLiteral aCommentStringC("XTEXT_EOS"); + + for (sal_Int32 i(nTextPosition); i < nTextPosition + nTextLength; i++) + { + // create the entries for the respective break positions + if (i == nNextCellBreak) + { + mpMetaFile->AddAction( + new MetaCommentAction(aCommentStringA, i - nTextPosition)); + nNextCellBreak = rBreakIterator.nextCharacters( + rTxt, i, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); + } + if (i == nNextWordBoundary.endPos) + { + mpMetaFile->AddAction( + new MetaCommentAction(aCommentStringB, i - nTextPosition)); + nNextWordBoundary = rBreakIterator.getWordBoundary( + rTxt, i + 1, rLocale, css::i18n::WordType::ANY_WORD, true); + } + if (i == nNextSentenceBreak) + { + mpMetaFile->AddAction( + new MetaCommentAction(aCommentStringC, i - nTextPosition)); + nNextSentenceBreak = rBreakIterator.endOfSentence(rTxt, i + 1, rLocale); + } + } + } + } +} + +void VclMetafileProcessor2D::processPolygonHairlinePrimitive2D( + const primitive2d::PolygonHairlinePrimitive2D& rHairlinePrimitive) +{ + const basegfx::B2DPolygon& rBasePolygon = rHairlinePrimitive.getB2DPolygon(); + + if (rBasePolygon.count() > (MAX_POLYGON_POINT_COUNT_METAFILE - 1)) + { + // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points + // per polygon. If there are more, split the polygon in half and call recursively + basegfx::B2DPolygon aLeft, aRight; + splitLinePolygon(rBasePolygon, aLeft, aRight); + rtl::Reference<primitive2d::PolygonHairlinePrimitive2D> xPLeft( + new primitive2d::PolygonHairlinePrimitive2D(std::move(aLeft), + rHairlinePrimitive.getBColor())); + rtl::Reference<primitive2d::PolygonHairlinePrimitive2D> xPRight( + new primitive2d::PolygonHairlinePrimitive2D(std::move(aRight), + rHairlinePrimitive.getBColor())); + + processBasePrimitive2D(*xPLeft); + processBasePrimitive2D(*xPRight); + } + else + { + // direct draw of hairline; use default processing + // support SvtGraphicStroke MetaCommentAction + const basegfx::BColor aLineColor( + maBColorModifierStack.getModifiedColor(rHairlinePrimitive.getBColor())); + std::unique_ptr<SvtGraphicStroke> pSvtGraphicStroke; + + // #i121267# Not needed, does not give better quality compared with + // the MetaActionType::POLYPOLYGON written by RenderPolygonHairlinePrimitive2D + // below + const bool bSupportSvtGraphicStroke(false); + + if (bSupportSvtGraphicStroke) + { + pSvtGraphicStroke + = impTryToCreateSvtGraphicStroke(rHairlinePrimitive.getB2DPolygon(), &aLineColor, + nullptr, nullptr, nullptr, nullptr); + + impStartSvtGraphicStroke(pSvtGraphicStroke.get()); + } + + RenderPolygonHairlinePrimitive2D(rHairlinePrimitive, false); + + if (bSupportSvtGraphicStroke) + { + impEndSvtGraphicStroke(pSvtGraphicStroke.get()); + } + } +} + +void VclMetafileProcessor2D::processPolygonStrokePrimitive2D( + const primitive2d::PolygonStrokePrimitive2D& rStrokePrimitive) +{ + const basegfx::B2DPolygon& rBasePolygon = rStrokePrimitive.getB2DPolygon(); + + if (rBasePolygon.count() > (MAX_POLYGON_POINT_COUNT_METAFILE - 1)) + { + // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points + // per polygon. If there are more, split the polygon in half and call recursively + basegfx::B2DPolygon aLeft, aRight; + splitLinePolygon(rBasePolygon, aLeft, aRight); + rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xPLeft( + new primitive2d::PolygonStrokePrimitive2D(std::move(aLeft), + rStrokePrimitive.getLineAttribute(), + rStrokePrimitive.getStrokeAttribute())); + rtl::Reference<primitive2d::PolygonStrokePrimitive2D> xPRight( + new primitive2d::PolygonStrokePrimitive2D(std::move(aRight), + rStrokePrimitive.getLineAttribute(), + rStrokePrimitive.getStrokeAttribute())); + + processBasePrimitive2D(*xPLeft); + processBasePrimitive2D(*xPRight); + } + else + { + mpOutputDevice->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR); + + // support SvtGraphicStroke MetaCommentAction + std::unique_ptr<SvtGraphicStroke> pSvtGraphicStroke = impTryToCreateSvtGraphicStroke( + rBasePolygon, nullptr, &rStrokePrimitive.getLineAttribute(), + &rStrokePrimitive.getStrokeAttribute(), nullptr, nullptr); + + impStartSvtGraphicStroke(pSvtGraphicStroke.get()); + const attribute::LineAttribute& rLine = rStrokePrimitive.getLineAttribute(); + + // create MetaPolyLineActions, but without LineStyle::Dash + if (basegfx::fTools::more(rLine.getWidth(), 0.0)) + { + const attribute::StrokeAttribute& rStroke = rStrokePrimitive.getStrokeAttribute(); + + const basegfx::BColor aHairlineColor( + maBColorModifierStack.getModifiedColor(rLine.getColor())); + mpOutputDevice->SetLineColor(Color(aHairlineColor)); + mpOutputDevice->SetFillColor(); + + // use the transformed line width + LineInfo aLineInfo(LineStyle::Solid, + basegfx::fround(getTransformedLineWidth(rLine.getWidth()))); + aLineInfo.SetLineJoin(rLine.getLineJoin()); + aLineInfo.SetLineCap(rLine.getLineCap()); + + basegfx::B2DPolyPolygon aHairLinePolyPolygon; + if (0.0 == rStroke.getFullDotDashLen()) + { + aHairLinePolyPolygon.append(rBasePolygon); + } + else + { + bool done = false; + const std::vector<double>& array = rStroke.getDotDashArray(); + // The dotdash array should generally have the form + // (<dashLen> <distance>)+ (<dotLen> <distance>)* + // (where (,),+ and * have their regex meaning). + // Find out what the lengths and their counts are. + if (!array.empty() && array.size() % 2 == 0) + { + double dashLen = array[0]; + double distance = array[1]; + int dashCount = 1; + double dotLen = 0; + int dotCount = 0; + size_t pos = 2; + while (pos + 2 <= array.size()) + { + if (array[pos] != dashLen || array[pos + 1] != distance) + break; + ++dashCount; + pos += 2; + } + if (pos + 2 <= array.size() && array[pos + 1] == distance) + { + dotLen = array[pos]; + ++dotCount; + pos += 2; + while (pos + 2 <= array.size()) + { + if (array[pos] != dotLen || array[pos + 1] != distance) + break; + ++dotCount; + pos += 2; + } + } + if (array.size() == pos) + { + aHairLinePolyPolygon.append(rBasePolygon); + // This will be used by setupStrokeAttributes() in cppcanvas. + aLineInfo.SetStyle(LineStyle::Dash); + aLineInfo.SetDashCount(dashCount); + aLineInfo.SetDashLen(getTransformedLineWidth(dashLen)); + aLineInfo.SetDistance(getTransformedLineWidth(distance)); + if (dotCount != 0) + { + aLineInfo.SetDotCount(dotCount); + aLineInfo.SetDotLen(getTransformedLineWidth(dotLen)); + } + done = true; + } + } + if (!done) + { + // LineInfo can hold only limited info about dashing, apply dashing manually + // if LineInfo cannot describe it. That should not happen though. + SAL_WARN("drawinglayer", "dotdash array cannot be converted to LineInfo"); + basegfx::utils::applyLineDashing(rBasePolygon, rStroke.getDotDashArray(), + &aHairLinePolyPolygon, nullptr, + rStroke.getFullDotDashLen()); + } + } + aHairLinePolyPolygon.transform(maCurrentTransformation); + + for (sal_uInt32 a(0); a < aHairLinePolyPolygon.count(); a++) + { + const basegfx::B2DPolygon& aCandidate(aHairLinePolyPolygon.getB2DPolygon(a)); + + if (aCandidate.count() > 1) + { + const tools::Polygon aToolsPolygon(aCandidate); + + mpMetaFile->AddAction(new MetaPolyLineAction(aToolsPolygon, aLineInfo)); + } + } + } + else + { + process(rStrokePrimitive); + } + + impEndSvtGraphicStroke(pSvtGraphicStroke.get()); + + mpOutputDevice->Pop(); + } +} + +void VclMetafileProcessor2D::processPolygonStrokeArrowPrimitive2D( + const primitive2d::PolygonStrokeArrowPrimitive2D& rStrokeArrowPrimitive) +{ + const basegfx::B2DPolygon& rBasePolygon = rStrokeArrowPrimitive.getB2DPolygon(); + + if (rBasePolygon.count() > (MAX_POLYGON_POINT_COUNT_METAFILE - 1)) + { + // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points + // per polygon. If there are more, split the polygon in half and call recursively + basegfx::B2DPolygon aLeft, aRight; + splitLinePolygon(rBasePolygon, aLeft, aRight); + const attribute::LineStartEndAttribute aEmpty; + rtl::Reference<primitive2d::PolygonStrokeArrowPrimitive2D> xPLeft( + new primitive2d::PolygonStrokeArrowPrimitive2D( + aLeft, rStrokeArrowPrimitive.getLineAttribute(), + rStrokeArrowPrimitive.getStrokeAttribute(), rStrokeArrowPrimitive.getStart(), + aEmpty)); + rtl::Reference<primitive2d::PolygonStrokeArrowPrimitive2D> xPRight( + new primitive2d::PolygonStrokeArrowPrimitive2D( + aRight, rStrokeArrowPrimitive.getLineAttribute(), + rStrokeArrowPrimitive.getStrokeAttribute(), aEmpty, + rStrokeArrowPrimitive.getEnd())); + + processBasePrimitive2D(*xPLeft); + processBasePrimitive2D(*xPRight); + } + else + { + // support SvtGraphicStroke MetaCommentAction + std::unique_ptr<SvtGraphicStroke> pSvtGraphicStroke = impTryToCreateSvtGraphicStroke( + rBasePolygon, nullptr, &rStrokeArrowPrimitive.getLineAttribute(), + &rStrokeArrowPrimitive.getStrokeAttribute(), &rStrokeArrowPrimitive.getStart(), + &rStrokeArrowPrimitive.getEnd()); + + // write LineGeometry start marker + impStartSvtGraphicStroke(pSvtGraphicStroke.get()); + + // #i116162# When B&W is set as DrawMode, DrawModeFlags::WhiteFill is used + // to let all fills be just white; for lines DrawModeFlags::BlackLine is used + // so all line geometry is supposed to get black. Since in the in-between + // stages of line geometry drawing filled polygons are used (e.g. line + // start/ends) it is necessary to change these drawmodes to preserve + // that lines shall be black; thus change DrawModeFlags::WhiteFill to + // DrawModeFlags::BlackFill during line geometry processing to have line geometry + // parts filled black. + const DrawModeFlags nOldDrawMode(mpOutputDevice->GetDrawMode()); + const bool bDrawmodeChange(nOldDrawMode & DrawModeFlags::WhiteFill + && mnSvtGraphicStrokeCount); + + if (bDrawmodeChange) + { + mpOutputDevice->SetDrawMode((nOldDrawMode & ~DrawModeFlags::WhiteFill) + | DrawModeFlags::BlackFill); + } + + // process sub-line geometry (evtl. filled PolyPolygons) + process(rStrokeArrowPrimitive); + + if (bDrawmodeChange) + { + mpOutputDevice->SetDrawMode(nOldDrawMode); + } + + // write LineGeometry end marker + impEndSvtGraphicStroke(pSvtGraphicStroke.get()); + } +} + +void VclMetafileProcessor2D::processPolyPolygonGraphicPrimitive2D( + const primitive2d::PolyPolygonGraphicPrimitive2D& rBitmapCandidate) +{ + // need to handle PolyPolygonGraphicPrimitive2D here to support XPATHFILL_SEQ_BEGIN/XPATHFILL_SEQ_END + basegfx::B2DPolyPolygon aLocalPolyPolygon(rBitmapCandidate.getB2DPolyPolygon()); + + if (!rBitmapCandidate.getDefinitionRange().isEmpty() + && aLocalPolyPolygon.getB2DRange() != rBitmapCandidate.getDefinitionRange()) + { + // The range which defines the bitmap fill is defined and different from the + // range of the defining geometry (e.g. used for FillStyle UseSlideBackground). + // This cannot be done calling vcl, thus use decomposition here directly + process(rBitmapCandidate); + return; + } + + fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); + + std::unique_ptr<SvtGraphicFill> pSvtGraphicFill; + + if (!mnSvtGraphicFillCount && aLocalPolyPolygon.count()) + { + // #121194# Changed implementation and checked usages of convert to metafile, + // presentation start (uses SvtGraphicFill) and printing. + + // calculate transformation. Get real object size, all values in FillGraphicAttribute + // are relative to the unified object + aLocalPolyPolygon.transform(maCurrentTransformation); + const basegfx::B2DVector aOutlineSize(aLocalPolyPolygon.getB2DRange().getRange()); + + // the scaling needs scale from pixel to logic coordinate system + const attribute::FillGraphicAttribute& rFillGraphicAttribute + = rBitmapCandidate.getFillGraphic(); + const Size aBmpSizePixel(rFillGraphicAttribute.getGraphic().GetSizePixel()); + + // setup transformation like in impgrfll. Multiply with aOutlineSize + // to get from unit coordinates in rFillGraphicAttribute.getGraphicRange() + // to object coordinates with object's top left being at (0,0). Divide + // by pixel size so that scale from pixel to logic will work in SvtGraphicFill. + const basegfx::B2DVector aTransformScale( + rFillGraphicAttribute.getGraphicRange().getRange() + / basegfx::B2DVector(std::max(1.0, double(aBmpSizePixel.Width())), + std::max(1.0, double(aBmpSizePixel.Height()))) + * aOutlineSize); + const basegfx::B2DPoint aTransformPosition( + rFillGraphicAttribute.getGraphicRange().getMinimum() * aOutlineSize); + + // setup transformation like in impgrfll + SvtGraphicFill::Transform aTransform; + + // scale values are divided by bitmap pixel sizes + aTransform.matrix[0] = aTransformScale.getX(); + aTransform.matrix[4] = aTransformScale.getY(); + + // translates are absolute + aTransform.matrix[2] = aTransformPosition.getX(); + aTransform.matrix[5] = aTransformPosition.getY(); + + pSvtGraphicFill.reset(new SvtGraphicFill( + getFillPolyPolygon(aLocalPolyPolygon), Color(), 0.0, SvtGraphicFill::fillEvenOdd, + SvtGraphicFill::fillTexture, aTransform, rFillGraphicAttribute.getTiling(), + SvtGraphicFill::hatchSingle, Color(), SvtGraphicFill::GradientType::Linear, Color(), + Color(), 0, rFillGraphicAttribute.getGraphic())); + } + + // Do use decomposition; encapsulate with SvtGraphicFill + impStartSvtGraphicFill(pSvtGraphicFill.get()); + process(rBitmapCandidate); + impEndSvtGraphicFill(pSvtGraphicFill.get()); +} + +void VclMetafileProcessor2D::processPolyPolygonHatchPrimitive2D( + const primitive2d::PolyPolygonHatchPrimitive2D& rHatchCandidate) +{ + // need to handle PolyPolygonHatchPrimitive2D here to support XPATHFILL_SEQ_BEGIN/XPATHFILL_SEQ_END + const attribute::FillHatchAttribute& rFillHatchAttribute = rHatchCandidate.getFillHatch(); + basegfx::B2DPolyPolygon aLocalPolyPolygon(rHatchCandidate.getB2DPolyPolygon()); + + if (aLocalPolyPolygon.getB2DRange() != rHatchCandidate.getDefinitionRange()) + { + // the range which defines the hatch is different from the range of the + // geometry (used for writer frames). This cannot be done calling vcl, thus use + // decomposition here + process(rHatchCandidate); + return; + } + + // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points + // per polygon. Split polygon until there are less than that + fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); + + if (rFillHatchAttribute.isFillBackground()) + { + // with fixing #i111954# (see below) the possible background + // fill of a hatched object was lost.Generate a background fill + // primitive and render it + const primitive2d::Primitive2DReference xBackground( + new primitive2d::PolyPolygonColorPrimitive2D(aLocalPolyPolygon, + rHatchCandidate.getBackgroundColor())); + + process(primitive2d::Primitive2DContainer{ xBackground }); + } + + std::unique_ptr<SvtGraphicFill> pSvtGraphicFill; + aLocalPolyPolygon.transform(maCurrentTransformation); + + if (!mnSvtGraphicFillCount && aLocalPolyPolygon.count()) + { + // re-create a VCL hatch as base data + SvtGraphicFill::HatchType eHatch(SvtGraphicFill::hatchSingle); + + switch (rFillHatchAttribute.getStyle()) + { + default: // attribute::HatchStyle::Single : + { + eHatch = SvtGraphicFill::hatchSingle; + break; + } + case attribute::HatchStyle::Double: + { + eHatch = SvtGraphicFill::hatchDouble; + break; + } + case attribute::HatchStyle::Triple: + { + eHatch = SvtGraphicFill::hatchTriple; + break; + } + } + + SvtGraphicFill::Transform aTransform; + + // scale + aTransform.matrix[0] *= rFillHatchAttribute.getDistance(); + aTransform.matrix[4] *= rFillHatchAttribute.getDistance(); + + // rotate (was never correct in impgrfll anyways, use correct angle now) + aTransform.matrix[0] *= cos(rFillHatchAttribute.getAngle()); + aTransform.matrix[1] *= -sin(rFillHatchAttribute.getAngle()); + aTransform.matrix[3] *= sin(rFillHatchAttribute.getAngle()); + aTransform.matrix[4] *= cos(rFillHatchAttribute.getAngle()); + + pSvtGraphicFill.reset(new SvtGraphicFill( + getFillPolyPolygon(aLocalPolyPolygon), Color(), 0.0, SvtGraphicFill::fillEvenOdd, + SvtGraphicFill::fillHatch, aTransform, false, eHatch, + Color(maBColorModifierStack.getModifiedColor(rFillHatchAttribute.getColor())), + SvtGraphicFill::GradientType::Linear, Color(), Color(), 0, Graphic())); + } + + // Do use decomposition; encapsulate with SvtGraphicFill + impStartSvtGraphicFill(pSvtGraphicFill.get()); + + // #i111954# do NOT use decomposition, but use direct VCL-command + // process(rCandidate.get2DDecomposition(getViewInformation2D())); + const tools::PolyPolygon aToolsPolyPolygon( + basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon)); + const HatchStyle aHatchStyle( + attribute::HatchStyle::Single == rFillHatchAttribute.getStyle() + ? HatchStyle::Single + : attribute::HatchStyle::Double == rFillHatchAttribute.getStyle() ? HatchStyle::Double + : HatchStyle::Triple); + + mpOutputDevice->DrawHatch( + aToolsPolyPolygon, + Hatch(aHatchStyle, + Color(maBColorModifierStack.getModifiedColor(rFillHatchAttribute.getColor())), + basegfx::fround(rFillHatchAttribute.getDistance()), + Degree10(basegfx::fround(basegfx::rad2deg<10>(rFillHatchAttribute.getAngle()))))); + + impEndSvtGraphicFill(pSvtGraphicFill.get()); +} + +void VclMetafileProcessor2D::processPolyPolygonGradientPrimitive2D( + const primitive2d::PolyPolygonGradientPrimitive2D& rGradientCandidate) +{ + bool useDecompose(false); + + if (!useDecompose) + { + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + + maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX); + + // detect if transformation is rotated, sheared or mirrored in X and/or Y + if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX) + || aScale.getX() < 0.0 || aScale.getY() < 0.0) + { + // #i121185# When rotation or shear is used, a VCL Gradient cannot be used directly. + // This is because VCL Gradient mechanism does *not* support to rotate the gradient + // with objects and this case is not expressible in a Metafile (and cannot be added + // since the FileFormats used, e.g. *.wmf, do not support it either). + // Such cases happen when a graphic object uses a Metafile as graphic information or + // a fill style definition uses a Metafile. In this cases the graphic content is + // rotated with the graphic or filled object; this is not supported by the target + // format of this conversion renderer - Metafiles. + // To solve this, not a Gradient is written, but the decomposition of this object + // is written to the Metafile. This is the PolyPolygons building the gradient fill. + // These will need more space and time, but the result will be as if the Gradient + // was rotated with the object. + // This mechanism is used by all exporters still not using Primitives (e.g. Print, + // Slideshow, Export rto PDF, export to Picture, ...) but relying on Metafile + // transfers. One more reason to *change* these to primitives. + // BTW: One more example how useful the principles of primitives are; the decomposition + // is by definition a simpler, maybe more expensive representation of the same content. + useDecompose = true; + } + } + + // tdf#150551 for PDF export, use the decomposition for better gradient visualization + if (!useDecompose && nullptr != mpPDFExtOutDevData) + { + useDecompose = true; + } + + basegfx::B2DPolyPolygon aLocalPolyPolygon(rGradientCandidate.getB2DPolyPolygon()); + + if (!useDecompose && aLocalPolyPolygon.getB2DRange() != rGradientCandidate.getDefinitionRange()) + { + // the range which defines the gradient is different from the range of the + // geometry (used for writer frames). This cannot be done calling vcl, thus use + // decomposition here + useDecompose = true; + } + + const attribute::FillGradientAttribute& rFillGradient(rGradientCandidate.getFillGradient()); + + if (!useDecompose && rFillGradient.cannotBeHandledByVCL()) + { + // MCGR: if we have ColorStops, do not try to fallback to old VCL-Gradient, + // that will *not* be capable of representing this properly. Use the + // correct decomposition instead + useDecompose = true; + } + + if (useDecompose) + { + GDIMetaFile* pMetaFile(mpOutputDevice->GetConnectMetaFile()); + + // tdf#155479 only add 'BGRAD_SEQ_BEGIN' if SVG export + if (nullptr != pMetaFile && pMetaFile->getSVG()) + { + // write the color stops to a memory stream + SvMemoryStream aMemStm; + VersionCompatWrite aCompat(aMemStm, 1); + + const basegfx::BColorStops& rColorStops(rFillGradient.getColorStops()); + sal_uInt16 nTmp(sal::static_int_cast<sal_uInt16>(rColorStops.size())); + aMemStm.WriteUInt16(nTmp); + + for (auto const& rCand : rColorStops) + { + aMemStm.WriteDouble(rCand.getStopOffset()); + const basegfx::BColor& rColor(rCand.getStopColor()); + aMemStm.WriteDouble(rColor.getRed()); + aMemStm.WriteDouble(rColor.getGreen()); + aMemStm.WriteDouble(rColor.getBlue()); + } + + // Add a new MetaCommentAction section of type 'BGRAD_SEQ_BEGIN/BGRAD_SEQ_END' + // that is capable of holding the new color step information, plus the + // already used MetaActionType::GRADIENTEX. + // With that combination only places that know about that new BGRAD_SEQ_* will + // use it while all others will work on the created decomposition of the + // gradient for compatibility - which are single-color filled polygons + pMetaFile->AddAction(new MetaCommentAction( + "BGRAD_SEQ_BEGIN"_ostr, 0, static_cast<const sal_uInt8*>(aMemStm.GetData()), + aMemStm.TellEnd())); + + // create MetaActionType::GRADIENTEX + // NOTE: with the new BGRAD_SEQ_* we could use basegfx::B2DPolygon and + // basegfx::BGradient here directly, but may have to add streaming OPs + // for these, so for now just go with what we use all the time. The real + // work for improvement should not go to this 'compromize' but to a real + // re-work of the SVG export (or/and others) to no longer work on metafiles + // but on UNO API or primitives (whatever fits best to the specific export) + fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); + Gradient aVCLGradient; + impConvertFillGradientAttributeToVCLGradient(aVCLGradient, rFillGradient, false); + aLocalPolyPolygon.transform(maCurrentTransformation); + const tools::PolyPolygon aToolsPolyPolygon( + getFillPolyPolygon(basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon))); + mpOutputDevice->DrawGradient(aToolsPolyPolygon, aVCLGradient); + } + + // use decompose to draw, will create PolyPolygon ColorFill actions + process(rGradientCandidate); + + // tdf#155479 only add 'BGRAD_SEQ_END' if SVG export + if (nullptr != pMetaFile && pMetaFile->getSVG()) + { + // close the BGRAD_SEQ_* actions range + pMetaFile->AddAction(new MetaCommentAction("BGRAD_SEQ_END"_ostr)); + } + + return; + } + + // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points + // per polygon. Split polygon until there are less than that + fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); + + // for support of MetaCommentActions of the form XGRAD_SEQ_BEGIN, XGRAD_SEQ_END + // it is safest to use the VCL OutputDevice::DrawGradient method which creates those. + // re-create a VCL-gradient from FillGradientPrimitive2D and the needed tools PolyPolygon + Gradient aVCLGradient; + impConvertFillGradientAttributeToVCLGradient(aVCLGradient, rFillGradient, false); + aLocalPolyPolygon.transform(maCurrentTransformation); + + // #i82145# ATM VCL printing of gradients using curved shapes does not work, + // i submitted the bug with the given ID to THB. When that task is fixed it is + // necessary to again remove this subdivision since it decreases possible + // printing quality (not even resolution-dependent for now). THB will tell + // me when that task is fixed in the master + const tools::PolyPolygon aToolsPolyPolygon( + getFillPolyPolygon(basegfx::utils::adaptiveSubdivideByAngle(aLocalPolyPolygon))); + + // XPATHFILL_SEQ_BEGIN/XPATHFILL_SEQ_END support + std::unique_ptr<SvtGraphicFill> pSvtGraphicFill; + + if (!mnSvtGraphicFillCount && aLocalPolyPolygon.count()) + { + // setup gradient stuff like in impgrfll + SvtGraphicFill::GradientType eGrad(SvtGraphicFill::GradientType::Linear); + + switch (aVCLGradient.GetStyle()) + { + default: // css::awt::GradientStyle_LINEAR: + case css::awt::GradientStyle_AXIAL: + eGrad = SvtGraphicFill::GradientType::Linear; + break; + case css::awt::GradientStyle_RADIAL: + case css::awt::GradientStyle_ELLIPTICAL: + eGrad = SvtGraphicFill::GradientType::Radial; + break; + case css::awt::GradientStyle_SQUARE: + case css::awt::GradientStyle_RECT: + eGrad = SvtGraphicFill::GradientType::Rectangular; + break; + } + + pSvtGraphicFill.reset(new SvtGraphicFill( + aToolsPolyPolygon, Color(), 0.0, SvtGraphicFill::fillEvenOdd, + SvtGraphicFill::fillGradient, SvtGraphicFill::Transform(), false, + SvtGraphicFill::hatchSingle, Color(), eGrad, aVCLGradient.GetStartColor(), + aVCLGradient.GetEndColor(), aVCLGradient.GetSteps(), Graphic())); + } + + // call VCL directly; encapsulate with SvtGraphicFill + impStartSvtGraphicFill(pSvtGraphicFill.get()); + mpOutputDevice->DrawGradient(aToolsPolyPolygon, aVCLGradient); + impEndSvtGraphicFill(pSvtGraphicFill.get()); +} + +void VclMetafileProcessor2D::processPolyPolygonColorPrimitive2D( + const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate) +{ + mpOutputDevice->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR); + basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon()); + + // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points + // per polygon. Split polygon until there are less than that + fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); + + const basegfx::BColor aPolygonColor( + maBColorModifierStack.getModifiedColor(rPolygonCandidate.getBColor())); + aLocalPolyPolygon.transform(maCurrentTransformation); + + // set line and fill color + mpOutputDevice->SetFillColor(Color(aPolygonColor)); + mpOutputDevice->SetLineColor(); + + mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon); + + mpOutputDevice->Pop(); +} + +void VclMetafileProcessor2D::processMaskPrimitive2D( + const primitive2d::MaskPrimitive2D& rMaskCandidate) +{ + // mask group. Special handling for MetaFiles. + if (rMaskCandidate.getChildren().empty()) + return; + + basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask()); + + if (aMask.count()) + { + // prepare new mask polygon and rescue current one + aMask.transform(maCurrentTransformation); + const basegfx::B2DPolyPolygon aLastClipPolyPolygon(maClipPolyPolygon); + + if (maClipPolyPolygon.count()) + { + // there is already a clip polygon set; build clipped union of + // current mask polygon and new one + maClipPolyPolygon = basegfx::utils::clipPolyPolygonOnPolyPolygon( + aMask, maClipPolyPolygon, + true, // #i106516# we want the inside of aMask, not the outside + false); + } + else + { + // use mask directly + maClipPolyPolygon = aMask; + } + + if (maClipPolyPolygon.count()) + { + // set VCL clip region; subdivide before conversion to tools polygon. Subdivision necessary (!) + // Removed subdivision and fixed in vcl::Region::ImplPolyPolyRegionToBandRegionFunc() in VCL where + // the ClipRegion is built from the Polygon. An AdaptiveSubdivide on the source polygon was missing there + mpOutputDevice->Push(vcl::PushFlags::CLIPREGION); + mpOutputDevice->SetClipRegion(vcl::Region(maClipPolyPolygon)); + + // recursively paint content + // #i121267# Only need to process sub-content when clip polygon is *not* empty. + // If it is empty, the clip is empty and there can be nothing inside. + process(rMaskCandidate.getChildren()); + + // restore VCL clip region + mpOutputDevice->Pop(); + } + + // restore to rescued clip polygon + maClipPolyPolygon = aLastClipPolyPolygon; + } + else + { + // no mask, no clipping. recursively paint content + process(rMaskCandidate.getChildren()); + } +} + +void VclMetafileProcessor2D::processUnifiedTransparencePrimitive2D( + const primitive2d::UnifiedTransparencePrimitive2D& rUniTransparenceCandidate) +{ + mpOutputDevice->Push(vcl::PushFlags::LINECOLOR | vcl::PushFlags::FILLCOLOR); + // for metafile: Need to examine what the pure vcl version is doing here actually + // - uses DrawTransparent with metafile for content and a gradient + // - uses DrawTransparent for single PolyPolygons directly. Can be detected by + // checking the content for single PolyPolygonColorPrimitive2D + const primitive2d::Primitive2DContainer& rContent = rUniTransparenceCandidate.getChildren(); + + if (!rContent.empty()) + { + if (0.0 == rUniTransparenceCandidate.getTransparence()) + { + // not transparent at all, use content + process(rUniTransparenceCandidate.getChildren()); + } + else if (rUniTransparenceCandidate.getTransparence() > 0.0 + && rUniTransparenceCandidate.getTransparence() < 1.0) + { + // try to identify a single PolyPolygonColorPrimitive2D in the + // content part of the transparence primitive + const primitive2d::PolyPolygonColorPrimitive2D* pPoPoColor = nullptr; + static bool bForceToMetafile(false); // loplugin:constvars:ignore + + if (!bForceToMetafile && 1 == rContent.size()) + { + const primitive2d::Primitive2DReference xReference(rContent[0]); + pPoPoColor = dynamic_cast<const primitive2d::PolyPolygonColorPrimitive2D*>( + xReference.get()); + } + + // PolyPolygonGradientPrimitive2D, PolyPolygonHatchPrimitive2D and + // PolyPolygonGraphicPrimitive2D are derived from PolyPolygonColorPrimitive2D. + // Check also for correct ID to exclude derived implementations + if (pPoPoColor + && PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D == pPoPoColor->getPrimitive2DID()) + { + // single transparent tools::PolyPolygon identified, use directly + const basegfx::BColor aPolygonColor( + maBColorModifierStack.getModifiedColor(pPoPoColor->getBColor())); + basegfx::B2DPolyPolygon aLocalPolyPolygon(pPoPoColor->getB2DPolyPolygon()); + + // #i112245# Metafiles use tools Polygon and are not able to have more than 65535 points + // per polygon. Split polygon until there are less than that + fillPolyPolygonNeededToBeSplit(aLocalPolyPolygon); + + // now transform + aLocalPolyPolygon.transform(maCurrentTransformation); + + // set line and fill color + const sal_uInt16 nTransPercentVcl(static_cast<sal_uInt16>( + basegfx::fround(rUniTransparenceCandidate.getTransparence() * 100.0))); + mpOutputDevice->SetFillColor(Color(aPolygonColor)); + mpOutputDevice->SetLineColor(); + + mpOutputDevice->DrawTransparent(tools::PolyPolygon(aLocalPolyPolygon), + nTransPercentVcl); + } + else + { + // save old mfCurrentUnifiedTransparence and set new one + // so that contained SvtGraphicStroke may use the current one + const double fLastCurrentUnifiedTransparence(mfCurrentUnifiedTransparence); + // #i105377# paint the content metafile opaque as the transparency gets + // split of into the gradient below + // mfCurrentUnifiedTransparence = rUniTransparenceCandidate.getTransparence(); + mfCurrentUnifiedTransparence = 0; + + // various content, create content-metafile + GDIMetaFile aContentMetafile; + + // tdf#155479 always forward propagate SVG flag for sub-content, + // it may contain cannotBeHandledByVCL gradients or transparencyGradients + aContentMetafile.setSVG(mpOutputDevice->GetConnectMetaFile()->getSVG()); + + const tools::Rectangle aPrimitiveRectangle( + impDumpToMetaFile(rContent, aContentMetafile)); + + // restore mfCurrentUnifiedTransparence; it may have been used + // while processing the sub-content in impDumpToMetaFile + mfCurrentUnifiedTransparence = fLastCurrentUnifiedTransparence; + + // create uniform VCL gradient for uniform transparency + Gradient aVCLGradient; + const sal_uInt8 nTransPercentVcl(static_cast<sal_uInt8>( + basegfx::fround(rUniTransparenceCandidate.getTransparence() * 255.0))); + const Color aTransColor(nTransPercentVcl, nTransPercentVcl, nTransPercentVcl); + + aVCLGradient.SetStyle(css::awt::GradientStyle_LINEAR); + aVCLGradient.SetStartColor(aTransColor); + aVCLGradient.SetEndColor(aTransColor); + aVCLGradient.SetAngle(0_deg10); + aVCLGradient.SetBorder(0); + aVCLGradient.SetOfsX(0); + aVCLGradient.SetOfsY(0); + aVCLGradient.SetStartIntensity(100); + aVCLGradient.SetEndIntensity(100); + aVCLGradient.SetSteps(2); + + // render it to VCL + mpOutputDevice->DrawTransparent(aContentMetafile, aPrimitiveRectangle.TopLeft(), + aPrimitiveRectangle.GetSize(), aVCLGradient); + } + } + } + + mpOutputDevice->Pop(); +} + +void VclMetafileProcessor2D::processTransparencePrimitive2D( + const primitive2d::TransparencePrimitive2D& rTransparenceCandidate) +{ + // for metafile: Need to examine what the pure vcl version is doing here actually + // - uses DrawTransparent with metafile for content and a gradient + // i can detect this here with checking the gradient part for a single + // FillGradientPrimitive2D and reconstruct the gradient. + // If that detection goes wrong, I have to create a transparence-blended bitmap. Eventually + // do that in stripes, else RenderTransparencePrimitive2D may just be used + const primitive2d::Primitive2DContainer& rContent(rTransparenceCandidate.getChildren()); + const primitive2d::Primitive2DContainer& rTransparence( + rTransparenceCandidate.getTransparence()); + + if (rContent.empty() || rTransparence.empty()) + return; + + // try to identify a single FillGradientPrimitive2D in the + // transparence part of the primitive. The hope is to handle + // the more specific case in a better way than the general + // TransparencePrimitive2D which has strongly separated + // definitions for transparency and content, both completely + // free definable by primitives + const primitive2d::FillGradientPrimitive2D* pFiGradient(nullptr); + static bool bForceToBigTransparentVDev(false); // loplugin:constvars:ignore + + // check for single FillGradientPrimitive2D + if (!bForceToBigTransparentVDev && 1 == rTransparence.size()) + { + pFiGradient + = dynamic_cast<const primitive2d::FillGradientPrimitive2D*>(rTransparence[0].get()); + + // check also for correct ID to exclude derived implementations + if (pFiGradient + && PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D != pFiGradient->getPrimitive2DID()) + pFiGradient = nullptr; + } + + // tdf#155479 preps for holding extra-MCGR infos + bool bSVGTransparencyColorStops(false); + basegfx::BColorStops aSVGTransparencyColorStops; + + // MCGR: tdf#155437 If we have identified a transparency gradient, + // check if VCL is able to handle it at all + if (nullptr != pFiGradient && pFiGradient->getFillGradient().cannotBeHandledByVCL()) + { + // If not, reset the pointer and do not make use of this special case. + // Adding a gradient in incomplete state that can not be handled by vcl + // makes no sense and will knowingly lead to errors, especially with + // MCGR extended possibilities. I checked what happens with the + // MetaFloatTransparentAction added by OutputDevice::DrawTransparent, but + // in most cases it gets converted to bitmap or even ignored, see e.g. + // - vcl/source/gdi/pdfwriter_impl2.cxx for PDF export + // - vcl/source/filter/wmf/wmfwr.cxx -> does ignore TransparenceGradient completely + // - vcl/source/filter/wmf/emfwr.cxx -> same + // - vcl/source/filter/eps/eps.cxx -> same + // NOTE: Theoretically it would be possible to make the new extended Gradient data + // available in metafiles, with the known limitations (not backward comp, all + // places using it would need adaption, ...), but combined with knowing that nearly + // all usages ignore or render it locally anyways makes that a non-option. + + // tdf#155479 Yepp, as already mentioned above we need to add + // some MCGR infos in case of SVG export, prepare that here + if (mpOutputDevice->GetConnectMetaFile()->getSVG()) + { + // for SVG, do not use decompose & prep extra data + bSVGTransparencyColorStops = true; + aSVGTransparencyColorStops = pFiGradient->getFillGradient().getColorStops(); + } + else + { + // use decomposition + pFiGradient = nullptr; + } + } + + if (nullptr != pFiGradient) + { + // this combination of Gradient can be expressed/handled by + // vcl/metafile, so add it directly. various content, create content-metafile + GDIMetaFile aContentMetafile; + + // tdf#155479 always forward propagate SVG flag for sub-content, + // it may contain cannotBeHandledByVCL gradients or transparencyGradients + aContentMetafile.setSVG(mpOutputDevice->GetConnectMetaFile()->getSVG()); + + const tools::Rectangle aPrimitiveRectangle(impDumpToMetaFile(rContent, aContentMetafile)); + + // re-create a VCL-gradient from FillGradientPrimitive2D + Gradient aVCLGradient; + impConvertFillGradientAttributeToVCLGradient(aVCLGradient, pFiGradient->getFillGradient(), + true); + + if (bSVGTransparencyColorStops) + { + // tdf#155479 create action directly & add extra + // MCGR infos to the metafile, do that by adding - ONLY in + // case of SVG export - to the MetaFileAction. For that + // reason, do what OutputDevice::DrawTransparent will do, + // but locally. + // NOTE: That would be good for this whole + // VclMetafileProcessor2D anyways to allow to get it + // completely independent from OutputDevice in the long run + GDIMetaFile* pMetaFile(mpOutputDevice->GetConnectMetaFile()); + rtl::Reference<::MetaFloatTransparentAction> pAction( + new MetaFloatTransparentAction(aContentMetafile, aPrimitiveRectangle.TopLeft(), + aPrimitiveRectangle.GetSize(), aVCLGradient)); + + pAction->addSVGTransparencyColorStops(aSVGTransparencyColorStops); + pMetaFile->AddAction(pAction); + } + else + { + // render it to VCL (creates MetaFloatTransparentAction) + mpOutputDevice->DrawTransparent(aContentMetafile, aPrimitiveRectangle.TopLeft(), + aPrimitiveRectangle.GetSize(), aVCLGradient); + } + return; + } + + // Here we need to create a correct replacement visualization for the + // TransparencePrimitive2D for the target metafile. + // I replaced the n'th iteration to convert-to-bitmap which was + // used here by using the existing tooling. The orig here was also producing + // transparency errors with test-file from tdf#155437 on the right part of the + // image. + // Just rely on existing tooling doing the right thing in one place, so also + // corrections/optimizations can be in one single place + + // Start by getting logic range of content, transform object-to-world, then world-to-view + // to get to discrete values ('pixels'). Matrix multiplication is right-to-left (and not + // commutative) + basegfx::B2DRange aLogicRange(rTransparenceCandidate.getB2DRange(getViewInformation2D())); + aLogicRange.transform(mpOutputDevice->GetViewTransformation() * maCurrentTransformation); + + // expand in discrete coordinates to next-bigger 'pixel' boundaries and remember + // created discrete range + aLogicRange.expand( + basegfx::B2DPoint(floor(aLogicRange.getMinX()), floor(aLogicRange.getMinY()))); + aLogicRange.expand(basegfx::B2DPoint(ceil(aLogicRange.getMaxX()), ceil(aLogicRange.getMaxY()))); + const basegfx::B2DRange aDiscreteRange(aLogicRange); + + // transform back from discrete to world coordinates: this creates the + // pixel-boundaries extended logic range we need to cover all content + // reliably + aLogicRange.transform(mpOutputDevice->GetInverseViewTransformation()); + + // create transform embedding for renderer. Goal is to translate what we + // want to paint to top/left 0/0 and the calculated discrete size + basegfx::B2DHomMatrix aEmbedding(basegfx::utils::createTranslateB2DHomMatrix( + -aLogicRange.getMinX(), -aLogicRange.getMinY())); + const double fLogicWidth( + basegfx::fTools::equalZero(aLogicRange.getWidth()) ? 1.0 : aLogicRange.getWidth()); + const double fLogicHeight( + basegfx::fTools::equalZero(aLogicRange.getHeight()) ? 1.0 : aLogicRange.getHeight()); + aEmbedding.scale(aDiscreteRange.getWidth() / fLogicWidth, + aDiscreteRange.getHeight() / fLogicHeight); + + // use the whole TransparencePrimitive2D as input (no need to create a new + // one with the sub-contents, these are ref-counted) and add to embedding + // primitive2d::TransparencePrimitive2D& rTrCand(); + primitive2d::Primitive2DContainer xEmbedSeq{ &const_cast<primitive2d::TransparencePrimitive2D&>( + rTransparenceCandidate) }; + + // tdf#158743 when embedding, do not forget to 1st apply the evtl. used + // CurrentTransformation (right-to-left, apply that 1st) + xEmbedSeq = primitive2d::Primitive2DContainer{ new primitive2d::TransformPrimitive2D( + aEmbedding * maCurrentTransformation, std::move(xEmbedSeq)) }; + + // use empty ViewInformation & a useful MaximumQuadraticPixels + // limitation to paint the content + const auto aViewInformation2D(geometry::createViewInformation2D({})); + const sal_uInt32 nMaximumQuadraticPixels(500000); + const BitmapEx aBitmapEx(convertToBitmapEx( + std::move(xEmbedSeq), aViewInformation2D, basegfx::fround(aDiscreteRange.getWidth()), + basegfx::fround(aDiscreteRange.getHeight()), nMaximumQuadraticPixels)); + + // add to target metafile (will create MetaFloatTransparentAction) + mpOutputDevice->DrawBitmapEx( + Point(basegfx::fround(aLogicRange.getMinX()), basegfx::fround(aLogicRange.getMinY())), + Size(basegfx::fround(aLogicRange.getWidth()), basegfx::fround(aLogicRange.getHeight())), + aBitmapEx); +} + +void VclMetafileProcessor2D::processStructureTagPrimitive2D( + const primitive2d::StructureTagPrimitive2D& rStructureTagCandidate) +{ + ::comphelper::ValueRestorationGuard const g(mpCurrentStructureTag, &rStructureTagCandidate); + + // structured tag primitive + const vcl::PDFWriter::StructElement& rTagElement(rStructureTagCandidate.getStructureElement()); + bool bTagUsed((vcl::PDFWriter::NonStructElement != rTagElement)); + ::std::optional<sal_Int32> oAnchorParent; + + if (!rStructureTagCandidate.isTaggedSdrObject()) + { + bTagUsed = false; + } + + if (mpPDFExtOutDevData && bTagUsed) + { + // foreground object: tag as regular structure element + if (!rStructureTagCandidate.isBackground()) + { + if (rStructureTagCandidate.GetAnchorStructureElementKey() != nullptr) + { + sal_Int32 const id = mpPDFExtOutDevData->EnsureStructureElement( + rStructureTagCandidate.GetAnchorStructureElementKey()); + oAnchorParent.emplace(mpPDFExtOutDevData->GetCurrentStructureElement()); + mpPDFExtOutDevData->SetCurrentStructureElement(id); + } + mpPDFExtOutDevData->WrapBeginStructureElement(rTagElement); + switch (rTagElement) + { + case vcl::PDFWriter::H1: + case vcl::PDFWriter::H2: + case vcl::PDFWriter::H3: + case vcl::PDFWriter::H4: + case vcl::PDFWriter::H5: + case vcl::PDFWriter::H6: + case vcl::PDFWriter::Paragraph: + case vcl::PDFWriter::Heading: + case vcl::PDFWriter::Caption: + case vcl::PDFWriter::BlockQuote: + case vcl::PDFWriter::Table: + case vcl::PDFWriter::TableRow: + case vcl::PDFWriter::Formula: + case vcl::PDFWriter::Figure: + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Placement, + vcl::PDFWriter::Block); + break; + case vcl::PDFWriter::TableData: + case vcl::PDFWriter::TableHeader: + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Placement, + vcl::PDFWriter::Inline); + break; + default: + break; + } + switch (rTagElement) + { + case vcl::PDFWriter::Table: + case vcl::PDFWriter::Formula: + case vcl::PDFWriter::Figure: + case vcl::PDFWriter::Annot: + { + auto const range(rStructureTagCandidate.getB2DRange(getViewInformation2D())); + tools::Rectangle const aLogicRect( + basegfx::fround(range.getMinX()), basegfx::fround(range.getMinY()), + basegfx::fround(range.getMaxX()), basegfx::fround(range.getMaxY())); + mpPDFExtOutDevData->SetStructureBoundingBox(aLogicRect); + break; + } + default: + break; + } + if (rTagElement == vcl::PDFWriter::Annot) + { + mpPDFExtOutDevData->SetStructureAnnotIds(rStructureTagCandidate.GetAnnotIds()); + } + if (rTagElement == vcl::PDFWriter::TableHeader) + { + mpPDFExtOutDevData->SetStructureAttribute(vcl::PDFWriter::Scope, + vcl::PDFWriter::Column); + } + } + // background object + else + { + // background image: tag as artifact + if (rStructureTagCandidate.isImage()) + mpPDFExtOutDevData->WrapBeginStructureElement(vcl::PDFWriter::NonStructElement); + // any other background object: do not tag + else + assert(false); + } + } + + // process children normally + process(rStructureTagCandidate.getChildren()); + + if (mpPDFExtOutDevData && bTagUsed) + { + // write end tag + mpPDFExtOutDevData->EndStructureElement(); + if (oAnchorParent) + { + mpPDFExtOutDevData->SetCurrentStructureElement(*oAnchorParent); + } + } +} + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx b/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx new file mode 100644 index 0000000000..c315281ebf --- /dev/null +++ b/drawinglayer/source/processor2d/vclmetafileprocessor2d.hxx @@ -0,0 +1,214 @@ +/* -*- 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 . + */ + +#pragma once + +#include <stack> + +#include "vclprocessor2d.hxx" +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <vcl/pdfextoutdevdata.hxx> // vcl::PDFExtOutDevData support +#include <vcl/lazydelete.hxx> + +class GDIMetaFile; +namespace tools +{ +class Rectangle; +} +class Gradient; +class SvtGraphicFill; +class SvtGraphicStroke; + +namespace drawinglayer::attribute +{ +class FillGradientAttribute; +class LineAttribute; +class StrokeAttribute; +class LineStartEndAttribute; +} + +namespace drawinglayer::primitive2d +{ +class GraphicPrimitive2D; +class ControlPrimitive2D; +class TextHierarchyFieldPrimitive2D; +class TextHierarchyLinePrimitive2D; +class TextHierarchyBulletPrimitive2D; +class TextHierarchyParagraphPrimitive2D; +class TextHierarchyBlockPrimitive2D; +class TextSimplePortionPrimitive2D; +class PolygonHairlinePrimitive2D; +class PolygonStrokePrimitive2D; +class PolygonStrokeArrowPrimitive2D; +class PolyPolygonGraphicPrimitive2D; +class PolyPolygonHatchPrimitive2D; +class PolyPolygonGradientPrimitive2D; +class PolyPolygonColorPrimitive2D; +class MaskPrimitive2D; +class UnifiedTransparencePrimitive2D; +class TransparencePrimitive2D; +class ObjectInfoPrimitive2D; +class StructureTagPrimitive2D; +} + +namespace basegfx +{ +class BColor; +} + +namespace drawinglayer::processor2d +{ +/** VclMetafileProcessor2D class + + This processor derived from VclProcessor2D is the base class for rendering + all fed primitives to a classical VCL-Metafile, including all over the + time grown extra data in comments and PDF exception data creations. Also + printing needs some exception stuff. + + All in all it is needed to emulate the old ::paint output from the old + Drawinglayer as long as exporters and/or filters still use the Metafile + and the extra-data added to it (which can be seen mostly as 'extensions' + or simply as 'hacks'). + */ +class VclMetafileProcessor2D : public VclProcessor2D +{ +private: + tools::Rectangle impDumpToMetaFile(const primitive2d::Primitive2DContainer& rContent, + GDIMetaFile& o_rContentMetafile); + void + impConvertFillGradientAttributeToVCLGradient(Gradient& o_rVCLGradient, + const attribute::FillGradientAttribute& rFiGrAtt, + bool bIsTransparenceGradient) const; + void impStartSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill); + void impEndSvtGraphicFill(SvtGraphicFill const* pSvtGraphicFill); + std::unique_ptr<SvtGraphicStroke> + impTryToCreateSvtGraphicStroke(const basegfx::B2DPolygon& rB2DPolygon, + const basegfx::BColor* pColor, + const attribute::LineAttribute* pLineAttribute, + const attribute::StrokeAttribute* pStrokeAttribute, + const attribute::LineStartEndAttribute* pStart, + const attribute::LineStartEndAttribute* pEnd); + void impStartSvtGraphicStroke(SvtGraphicStroke const* pSvtGraphicStroke); + void impEndSvtGraphicStroke(SvtGraphicStroke const* pSvtGraphicStroke); + void popStructureElement(vcl::PDFWriter::StructElement eElem); + void popListItem(); + void popList(); + + void processGraphicPrimitive2D(const primitive2d::GraphicPrimitive2D& rGraphicPrimitive); + void processControlPrimitive2D(const primitive2d::ControlPrimitive2D& rControlPrimitive); + void processTextHierarchyFieldPrimitive2D( + const primitive2d::TextHierarchyFieldPrimitive2D& rFieldPrimitive); + void processTextHierarchyLinePrimitive2D( + const primitive2d::TextHierarchyLinePrimitive2D& rLinePrimitive); + void processTextHierarchyBulletPrimitive2D( + const primitive2d::TextHierarchyBulletPrimitive2D& rBulletPrimitive); + void processTextHierarchyParagraphPrimitive2D( + const primitive2d::TextHierarchyParagraphPrimitive2D& rParagraphPrimitive); + void processTextHierarchyBlockPrimitive2D( + const primitive2d::TextHierarchyBlockPrimitive2D& rBlockPrimitive); + void processTextSimplePortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate); + void processPolygonHairlinePrimitive2D( + const primitive2d::PolygonHairlinePrimitive2D& rHairlinePrimitive); + void + processPolygonStrokePrimitive2D(const primitive2d::PolygonStrokePrimitive2D& rStrokePrimitive); + void processPolygonStrokeArrowPrimitive2D( + const primitive2d::PolygonStrokeArrowPrimitive2D& rStrokeArrowPrimitive); + void processPolyPolygonGraphicPrimitive2D( + const primitive2d::PolyPolygonGraphicPrimitive2D& rBitmapCandidate); + void processPolyPolygonHatchPrimitive2D( + const primitive2d::PolyPolygonHatchPrimitive2D& rHatchCandidate); + void processPolyPolygonGradientPrimitive2D( + const primitive2d::PolyPolygonGradientPrimitive2D& rGradientCandidate); + void processPolyPolygonColorPrimitive2D( + const primitive2d::PolyPolygonColorPrimitive2D& rPolygonCandidate); + void processMaskPrimitive2D(const primitive2d::MaskPrimitive2D& rMaskCandidate); + void processUnifiedTransparencePrimitive2D( + const primitive2d::UnifiedTransparencePrimitive2D& rUniTransparenceCandidate); + void processTransparencePrimitive2D( + const primitive2d::TransparencePrimitive2D& rTransparenceCandidate); + void + processObjectInfoPrimitive2D(const primitive2d::ObjectInfoPrimitive2D& rObjectInfoPrimitive2D); + void processStructureTagPrimitive2D( + const primitive2d::StructureTagPrimitive2D& rStructureTagCandidate); + + /// Convert the fWidth to the same space as its coordinates. + double getTransformedLineWidth(double fWidth) const; + + /// the current clipping tools::PolyPolygon from MaskPrimitive2D + basegfx::B2DPolyPolygon maClipPolyPolygon; + + /// the target MetaFile + GDIMetaFile* mpMetaFile; + + /* do not allow embedding SvtGraphicFills into each other, + use a counter to prevent that + */ + sal_uInt32 mnSvtGraphicFillCount; + + /// same for SvtGraphicStroke + sal_uInt32 mnSvtGraphicStrokeCount; + + /* hold the last unified transparence value to have it handy + on SvtGraphicStroke creation + */ + double mfCurrentUnifiedTransparence; + + /* break iterator support + made static so it only needs to be fetched once, even with many single + constructed VclMetafileProcessor2D. It's still incarnated on demand, + but exists for OOo runtime now by purpose. + */ + static vcl::DeleteOnDeinit<css::uno::Reference<css::i18n::XBreakIterator>> mxBreakIterator; + + /* vcl::PDFExtOutDevData support + For the first step, some extra actions at vcl::PDFExtOutDevData need to + be emulated with the VclMetafileProcessor2D. These are potentially temporarily + since PDF export may use PrimitiveSequences one day directly. + */ + vcl::PDFExtOutDevData* mpPDFExtOutDevData; + + // Remember the current OutlineLevel. This is used when tagged PDF export + // is used to create/write valid structured list entries using PDF statements + // like '/L', '/LI', 'LBody' instead of simple '/P' (Paragraph). + // The value -1 means 'no OutlineLevel' and values >= 0 express the level. + sal_Int16 mnCurrentOutlineLevel; + bool mbInListItem; + bool mbBulletPresent; + + std::stack<vcl::PDFWriter::StructElement> maListElements; + + primitive2d::StructureTagPrimitive2D const* mpCurrentStructureTag = nullptr; + +protected: + /* the local processor for BasePrimitive2D-Implementation based primitives, + called from the common process()-implementation + */ + virtual void processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) override; + +public: + /// constructor/destructor + VclMetafileProcessor2D(const geometry::ViewInformation2D& rViewInformation, + OutputDevice& rOutDev); + virtual ~VclMetafileProcessor2D() override; +}; +} // end of namespace processor2d::drawinglayer + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx new file mode 100644 index 0000000000..e71cda4a0b --- /dev/null +++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.cxx @@ -0,0 +1,1131 @@ +/* -*- 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 "vclpixelprocessor2d.hxx" +#include "vclhelperbufferdevice.hxx" +#include "helperwrongspellrenderer.hxx" +#include <comphelper/lok.hxx> + +#include <sal/log.hxx> +#include <vcl/outdev.hxx> +#include <vcl/hatch.hxx> +#include <vcl/canvastools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/utils/gradienttools.hxx> + +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/Tools.hxx> +#include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/glowprimitive2d.hxx> +#include <drawinglayer/primitive2d/wrongspellprimitive2d.hxx> +#include <drawinglayer/primitive2d/controlprimitive2d.hxx> +#include <drawinglayer/primitive2d/borderlineprimitive2d.hxx> +#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx> +#include <drawinglayer/primitive2d/backgroundcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/fillhatchprimitive2d.hxx> +#include <drawinglayer/primitive2d/epsprimitive2d.hxx> +#include <drawinglayer/primitive2d/shadowprimitive2d.hxx> +#include <drawinglayer/primitive2d/patternfillprimitive2d.hxx> + +#include <com/sun/star/awt/XWindow2.hpp> +#include <com/sun/star/awt/XControl.hpp> + +#include <svtools/optionsdrawinglayer.hxx> +#include <vcl/gradient.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::processor2d +{ +VclPixelProcessor2D::VclPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation, + OutputDevice& rOutDev, + const basegfx::BColorModifierStack& rInitStack) + : VclProcessor2D(rViewInformation, rOutDev, rInitStack) + , m_nOrigAntiAliasing(rOutDev.GetAntialiasing()) +{ + // prepare maCurrentTransformation matrix with viewTransformation to target directly to pixels + maCurrentTransformation = rViewInformation.getObjectToViewTransformation(); + + // prepare output directly to pixels + mpOutputDevice->Push(vcl::PushFlags::MAPMODE); + mpOutputDevice->SetMapMode(); + + // react on AntiAliasing settings + if (rViewInformation.getUseAntiAliasing()) + { + mpOutputDevice->SetAntialiasing(m_nOrigAntiAliasing | AntialiasingFlags::Enable); + } + else + { + mpOutputDevice->SetAntialiasing(m_nOrigAntiAliasing & ~AntialiasingFlags::Enable); + } +} + +VclPixelProcessor2D::~VclPixelProcessor2D() +{ + // restore MapMode + mpOutputDevice->Pop(); + + // restore AntiAliasing + mpOutputDevice->SetAntialiasing(m_nOrigAntiAliasing); +} + +void VclPixelProcessor2D::tryDrawPolyPolygonColorPrimitive2DDirect( + const drawinglayer::primitive2d::PolyPolygonColorPrimitive2D& rSource, double fTransparency) +{ + if (!rSource.getB2DPolyPolygon().count() || fTransparency < 0.0 || fTransparency >= 1.0) + { + // no geometry, done + return; + } + + const basegfx::BColor aPolygonColor( + maBColorModifierStack.getModifiedColor(rSource.getBColor())); + + mpOutputDevice->SetFillColor(Color(aPolygonColor)); + mpOutputDevice->SetLineColor(); + mpOutputDevice->DrawTransparent(maCurrentTransformation, rSource.getB2DPolyPolygon(), + fTransparency); +} + +bool VclPixelProcessor2D::tryDrawPolygonHairlinePrimitive2DDirect( + const drawinglayer::primitive2d::PolygonHairlinePrimitive2D& rSource, double fTransparency) +{ + const basegfx::B2DPolygon& rLocalPolygon(rSource.getB2DPolygon()); + + if (!rLocalPolygon.count() || fTransparency < 0.0 || fTransparency >= 1.0) + { + // no geometry, done + return true; + } + + const basegfx::BColor aLineColor(maBColorModifierStack.getModifiedColor(rSource.getBColor())); + + mpOutputDevice->SetFillColor(); + mpOutputDevice->SetLineColor(Color(aLineColor)); + //aLocalPolygon.transform(maCurrentTransformation); + + // try drawing; if it did not work, use standard fallback + return mpOutputDevice->DrawPolyLineDirect(maCurrentTransformation, rLocalPolygon, 0.0, + fTransparency); +} + +bool VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect( + const drawinglayer::primitive2d::PolygonStrokePrimitive2D& rSource, double fTransparency) +{ + const basegfx::B2DPolygon& rLocalPolygon(rSource.getB2DPolygon()); + + if (!rLocalPolygon.count() || fTransparency < 0.0 || fTransparency >= 1.0) + { + // no geometry, done + return true; + } + + if (basegfx::B2DLineJoin::NONE == rSource.getLineAttribute().getLineJoin() + && css::drawing::LineCap_BUTT != rSource.getLineAttribute().getLineCap()) + { + // better use decompose to get that combination done for now, see discussion + // at https://bugs.documentfoundation.org/show_bug.cgi?id=130478#c17 and ff + return false; + } + + // MM01: Radically change here - no dismantle/applyLineDashing, + // let that happen low-level at DrawPolyLineDirect implementations + // to open up for buffering and evtl. direct draw with sys-dep + // graphic systems. Check for stroke is in use + const bool bStrokeAttributeNotUsed(rSource.getStrokeAttribute().isDefault() + || 0.0 == rSource.getStrokeAttribute().getFullDotDashLen()); + + const basegfx::BColor aLineColor( + maBColorModifierStack.getModifiedColor(rSource.getLineAttribute().getColor())); + + mpOutputDevice->SetFillColor(); + mpOutputDevice->SetLineColor(Color(aLineColor)); + + // MM01 draw direct, hand over dash data if available + return mpOutputDevice->DrawPolyLineDirect( + maCurrentTransformation, rLocalPolygon, + // tdf#124848 use LineWidth direct, do not try to solve for zero-case (aka hairline) + rSource.getLineAttribute().getWidth(), fTransparency, + bStrokeAttributeNotUsed ? nullptr : &rSource.getStrokeAttribute().getDotDashArray(), + rSource.getLineAttribute().getLineJoin(), rSource.getLineAttribute().getLineCap(), + rSource.getLineAttribute().getMiterMinimumAngle()); +} + +void VclPixelProcessor2D::processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) +{ + switch (rCandidate.getPrimitive2DID()) + { + case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D: + { + processWrongSpellPrimitive2D( + static_cast<const primitive2d::WrongSpellPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D: + { + processTextSimplePortionPrimitive2D( + static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D: + { + processTextDecoratedPortionPrimitive2D( + static_cast<const primitive2d::TextSimplePortionPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D: + { + processPolygonHairlinePrimitive2D( + static_cast<const primitive2d::PolygonHairlinePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D: + { + // direct draw of transformed BitmapEx primitive + processBitmapPrimitive2D( + static_cast<const primitive2d::BitmapPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D: + { + // direct draw of fillBitmapPrimitive + RenderFillGraphicPrimitive2D( + static_cast<const primitive2d::FillGraphicPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D: + { + processPolyPolygonGradientPrimitive2D( + static_cast<const primitive2d::PolyPolygonGradientPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D: + { + // direct draw of bitmap + RenderPolyPolygonGraphicPrimitive2D( + static_cast<const primitive2d::PolyPolygonGraphicPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D: + { + processPolyPolygonColorPrimitive2D( + static_cast<const primitive2d::PolyPolygonColorPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D: + { + processMetaFilePrimitive2D(rCandidate); + break; + } + case PRIMITIVE2D_ID_MASKPRIMITIVE2D: + { + // mask group. + RenderMaskPrimitive2DPixel( + static_cast<const primitive2d::MaskPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D: + { + // modified color group. Force output to unified color. + RenderModifiedColorPrimitive2D( + static_cast<const primitive2d::ModifiedColorPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D: + { + processUnifiedTransparencePrimitive2D( + static_cast<const primitive2d::UnifiedTransparencePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D: + { + // sub-transparence group. Draw to VDev first. + RenderTransparencePrimitive2D( + static_cast<const primitive2d::TransparencePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D: + { + // transform group. + RenderTransformPrimitive2D( + static_cast<const primitive2d::TransformPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D: + { + // new XDrawPage for ViewInformation2D + RenderPagePreviewPrimitive2D( + static_cast<const primitive2d::PagePreviewPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D: + { + // marker array + RenderMarkerArrayPrimitive2D( + static_cast<const primitive2d::MarkerArrayPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D: + { + // point array + RenderPointArrayPrimitive2D( + static_cast<const primitive2d::PointArrayPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D: + { + processControlPrimitive2D( + static_cast<const primitive2d::ControlPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D: + { + processPolygonStrokePrimitive2D( + static_cast<const primitive2d::PolygonStrokePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D: + { + processFillHatchPrimitive2D( + static_cast<const primitive2d::FillHatchPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D: + { + processBackgroundColorPrimitive2D( + static_cast<const primitive2d::BackgroundColorPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_INVERTPRIMITIVE2D: + { + processInvertPrimitive2D(rCandidate); + break; + } + case PRIMITIVE2D_ID_EPSPRIMITIVE2D: + { + RenderEpsPrimitive2D(static_cast<const primitive2d::EpsPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D: + { + RenderSvgLinearAtomPrimitive2D( + static_cast<const primitive2d::SvgLinearAtomPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D: + { + RenderSvgRadialAtomPrimitive2D( + static_cast<const primitive2d::SvgRadialAtomPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D: + { + processBorderLinePrimitive2D( + static_cast<const drawinglayer::primitive2d::BorderLinePrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D: + { + processFillGradientPrimitive2D( + static_cast<const drawinglayer::primitive2d::FillGradientPrimitive2D&>(rCandidate)); + break; + } + case PRIMITIVE2D_ID_PATTERNFILLPRIMITIVE2D: + { + processPatternFillPrimitive2D( + static_cast<const drawinglayer::primitive2d::PatternFillPrimitive2D&>(rCandidate)); + break; + } + default: + { + SAL_INFO("drawinglayer", "default case for " << drawinglayer::primitive2d::idToString( + rCandidate.getPrimitive2DID())); + // process recursively + process(rCandidate); + break; + } + } +} + +void VclPixelProcessor2D::processWrongSpellPrimitive2D( + const primitive2d::WrongSpellPrimitive2D& rWrongSpellPrimitive) +{ + if (!renderWrongSpellPrimitive2D(rWrongSpellPrimitive, *mpOutputDevice, maCurrentTransformation, + maBColorModifierStack)) + { + // fallback to decomposition (MetaFile) + process(rWrongSpellPrimitive); + } +} + +void VclPixelProcessor2D::processTextSimplePortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rCandidate) +{ + // Adapt evtl. used special DrawMode + const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode()); + adaptTextToFillDrawMode(); + + if (SvtOptionsDrawinglayer::IsRenderSimpleTextDirect()) + { + RenderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate); + } + else + { + process(rCandidate); + } + + // restore DrawMode + mpOutputDevice->SetDrawMode(nOriginalDrawMode); +} + +void VclPixelProcessor2D::processTextDecoratedPortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rCandidate) +{ + // Adapt evtl. used special DrawMode + const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode()); + adaptTextToFillDrawMode(); + + if (SvtOptionsDrawinglayer::IsRenderDecoratedTextDirect()) + { + RenderTextSimpleOrDecoratedPortionPrimitive2D(rCandidate); + } + else + { + process(rCandidate); + } + + // restore DrawMode + mpOutputDevice->SetDrawMode(nOriginalDrawMode); +} + +void VclPixelProcessor2D::processPolygonHairlinePrimitive2D( + const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D) +{ + if (tryDrawPolygonHairlinePrimitive2DDirect(rPolygonHairlinePrimitive2D, 0.0)) + { + return; + } + + // direct draw of hairline + RenderPolygonHairlinePrimitive2D(rPolygonHairlinePrimitive2D, true); +} + +void VclPixelProcessor2D::processBitmapPrimitive2D( + const primitive2d::BitmapPrimitive2D& rBitmapCandidate) +{ + // check if graphic content is inside discrete local ViewPort + const basegfx::B2DRange& rDiscreteViewPort(getViewInformation2D().getDiscreteViewport()); + const basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation + * rBitmapCandidate.getTransform()); + + if (!rDiscreteViewPort.isEmpty()) + { + basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0); + + aUnitRange.transform(aLocalTransform); + + if (!aUnitRange.overlaps(rDiscreteViewPort)) + { + // content is outside discrete local ViewPort + return; + } + } + + RenderBitmapPrimitive2D(rBitmapCandidate); +} + +void VclPixelProcessor2D::processPolyPolygonGradientPrimitive2D( + const primitive2d::PolyPolygonGradientPrimitive2D& rPolygonCandidate) +{ + basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolygonCandidate.getB2DPolyPolygon()); + + // no geometry, no need to render, done + if (!aLocalPolyPolygon.count()) + return; + + // *try* direct draw (AKA using old VCL stuff) to render gradient + const attribute::FillGradientAttribute& rGradient(rPolygonCandidate.getFillGradient()); + + // MCGR: *many* - and not only GradientStops - cases cannot be handled by VCL + // so use decomposition + // NOTE: There may be even more reasons to detect, e.g. a ViewTransformation + // that uses shear/rotate/mirror (what VCL cannot handle at all), see + // other checks already in processFillGradientPrimitive2D + if (rGradient.cannotBeHandledByVCL()) + { + process(rPolygonCandidate); + return; + } + + basegfx::BColor aStartColor( + maBColorModifierStack.getModifiedColor(rGradient.getColorStops().front().getStopColor())); + basegfx::BColor aEndColor( + maBColorModifierStack.getModifiedColor(rGradient.getColorStops().back().getStopColor())); + + if (aStartColor == aEndColor) + { + // no gradient at all, draw as polygon in AA and non-AA case + aLocalPolyPolygon.transform(maCurrentTransformation); + mpOutputDevice->SetLineColor(); + mpOutputDevice->SetFillColor(Color(aStartColor)); + mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon); + return; + } + + // use the primitive decomposition + process(rPolygonCandidate); +} + +void VclPixelProcessor2D::processPolyPolygonColorPrimitive2D( + const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D) +{ + // try to use directly + basegfx::B2DPolyPolygon aLocalPolyPolygon; + + tryDrawPolyPolygonColorPrimitive2DDirect(rPolyPolygonColorPrimitive2D, 0.0); + // okay, done. In this case no gaps should have to be repaired, too + + // when AA is on and this filled polygons are the result of stroked line geometry, + // draw the geometry once extra as lines to avoid AA 'gaps' between partial polygons + // Caution: This is needed in both cases (!) + if (!(mnPolygonStrokePrimitive2D && getViewInformation2D().getUseAntiAliasing() + && (mpOutputDevice->GetAntialiasing() & AntialiasingFlags::Enable))) + return; + + const basegfx::BColor aPolygonColor( + maBColorModifierStack.getModifiedColor(rPolyPolygonColorPrimitive2D.getBColor())); + sal_uInt32 nCount(aLocalPolyPolygon.count()); + + if (!nCount) + { + aLocalPolyPolygon = rPolyPolygonColorPrimitive2D.getB2DPolyPolygon(); + aLocalPolyPolygon.transform(maCurrentTransformation); + nCount = aLocalPolyPolygon.count(); + } + + mpOutputDevice->SetFillColor(); + mpOutputDevice->SetLineColor(Color(aPolygonColor)); + + for (sal_uInt32 a(0); a < nCount; a++) + { + mpOutputDevice->DrawPolyLine(aLocalPolyPolygon.getB2DPolygon(a), 0.0); + } +} + +void VclPixelProcessor2D::processUnifiedTransparencePrimitive2D( + const primitive2d::UnifiedTransparencePrimitive2D& rUniTransparenceCandidate) +{ + // Detect if a single PolyPolygonColorPrimitive2D is contained; in that case, + // use the faster OutputDevice::DrawTransparent method + const primitive2d::Primitive2DContainer& rContent = rUniTransparenceCandidate.getChildren(); + + if (rContent.empty()) + return; + + if (0.0 == rUniTransparenceCandidate.getTransparence()) + { + // not transparent at all, use content + process(rUniTransparenceCandidate.getChildren()); + } + else if (rUniTransparenceCandidate.getTransparence() > 0.0 + && rUniTransparenceCandidate.getTransparence() < 1.0) + { + bool bDrawTransparentUsed(false); + + if (1 == rContent.size()) + { + const primitive2d::BasePrimitive2D* pBasePrimitive = rContent[0].get(); + + switch (pBasePrimitive->getPrimitive2DID()) + { + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D: + { + // single transparent tools::PolyPolygon identified, use directly + const primitive2d::PolyPolygonColorPrimitive2D* pPoPoColor + = static_cast<const primitive2d::PolyPolygonColorPrimitive2D*>( + pBasePrimitive); + SAL_WARN_IF(!pPoPoColor, "drawinglayer", + "OOps, PrimitiveID and PrimitiveType do not match (!)"); + bDrawTransparentUsed = true; + tryDrawPolyPolygonColorPrimitive2DDirect( + *pPoPoColor, rUniTransparenceCandidate.getTransparence()); + break; + } + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D: + { + // single transparent PolygonHairlinePrimitive2D identified, use directly + const primitive2d::PolygonHairlinePrimitive2D* pPoHair + = static_cast<const primitive2d::PolygonHairlinePrimitive2D*>( + pBasePrimitive); + SAL_WARN_IF(!pPoHair, "drawinglayer", + "OOps, PrimitiveID and PrimitiveType do not match (!)"); + + // do no tallow by default - problem is that self-overlapping parts of this geometry will + // not be in an all-same transparency but will already alpha-cover themselves with blending. + // This is not what the UnifiedTransparencePrimitive2D defines: It requires all its + // content to be uniformly transparent. + // For hairline the effect is pretty minimal, but still not correct. + bDrawTransparentUsed = false; + break; + } + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D: + { + // single transparent PolygonStrokePrimitive2D identified, use directly + const primitive2d::PolygonStrokePrimitive2D* pPoStroke + = static_cast<const primitive2d::PolygonStrokePrimitive2D*>(pBasePrimitive); + SAL_WARN_IF(!pPoStroke, "drawinglayer", + "OOps, PrimitiveID and PrimitiveType do not match (!)"); + + // do no tallow by default - problem is that self-overlapping parts of this geometry will + // not be in an all-same transparency but will already alpha-cover themselves with blending. + // This is not what the UnifiedTransparencePrimitive2D defines: It requires all its + // content to be uniformly transparent. + // To check, activate and draw a wide transparent self-crossing line/curve + bDrawTransparentUsed = false; + break; + } + default: + SAL_INFO("drawinglayer", + "default case for " << drawinglayer::primitive2d::idToString( + rUniTransparenceCandidate.getPrimitive2DID())); + break; + } + } + + if (!bDrawTransparentUsed) + { + // unified sub-transparence. Draw to VDev first. + RenderUnifiedTransparencePrimitive2D(rUniTransparenceCandidate); + } + } +} + +void VclPixelProcessor2D::processControlPrimitive2D( + const primitive2d::ControlPrimitive2D& rControlPrimitive) +{ + // control primitive + const uno::Reference<awt::XControl>& rXControl(rControlPrimitive.getXControl()); + + try + { + // remember old graphics and create new + uno::Reference<awt::XView> xControlView(rXControl, uno::UNO_QUERY_THROW); + const uno::Reference<awt::XGraphics> xOriginalGraphics(xControlView->getGraphics()); + const uno::Reference<awt::XGraphics> xNewGraphics(mpOutputDevice->CreateUnoGraphics()); + + if (xNewGraphics.is()) + { + // find out if the control is already visualized as a VCL-ChildWindow. If yes, + // it does not need to be painted at all. + uno::Reference<awt::XWindow2> xControlWindow(rXControl, uno::UNO_QUERY_THROW); + bool bControlIsVisibleAsChildWindow(rXControl->getPeer().is() + && xControlWindow->isVisible()); + + // tdf#131281 The FormControls are not painted when using the Tiled Rendering for a simple + // reason: when e.g. bControlIsVisibleAsChildWindow is true. This is the case because the + // office is in non-layout mode (default for controls at startup). For the common office + // this means that there exists a real VCL-System-Window for the control, so it is *not* + // painted here due to being exactly obscured by that real Window (and creates danger of + // flickering, too). + // Tiled Rendering clients usually do *not* have real VCL-Windows for the controls, but + // exactly that would be needed on each client displaying the tiles (what would be hard + // to do but also would have advantages - the clients would have real controls in the + // shape of their target system which could be interacted with...). It is also what the + // office does. + // For now, fallback to just render these controls when Tiled Rendering is active to just + // have them displayed on all clients. + if (bControlIsVisibleAsChildWindow && comphelper::LibreOfficeKit::isActive()) + { + // Do force paint when we are in Tiled Renderer and FormControl is 'visible' + bControlIsVisibleAsChildWindow = false; + } + + if (!bControlIsVisibleAsChildWindow) + { + // Needs to be drawn. Link new graphics and view + xControlView->setGraphics(xNewGraphics); + + // get position + const basegfx::B2DHomMatrix aObjectToPixel(maCurrentTransformation + * rControlPrimitive.getTransform()); + const basegfx::B2DPoint aTopLeftPixel(aObjectToPixel * basegfx::B2DPoint(0.0, 0.0)); + + // Do not forget to use the evtl. offsetted origin of the target device, + // e.g. when used with mask/transparence buffer device + const Point aOrigin(mpOutputDevice->GetMapMode().GetOrigin()); + xControlView->draw(aOrigin.X() + basegfx::fround(aTopLeftPixel.getX()), + aOrigin.Y() + basegfx::fround(aTopLeftPixel.getY())); + + // restore original graphics + xControlView->setGraphics(xOriginalGraphics); + } + } + } + catch (const uno::Exception&) + { + // #i116763# removing since there is a good alternative when the xControlView + // is not found and it is allowed to happen + // DBG_UNHANDLED_EXCEPTION(); + + // process recursively and use the decomposition as Bitmap + process(rControlPrimitive); + } +} + +void VclPixelProcessor2D::processPolygonStrokePrimitive2D( + const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokePrimitive2D) +{ + // try to use directly + if (tryDrawPolygonStrokePrimitive2DDirect(rPolygonStrokePrimitive2D, 0.0)) + { + return; + } + + // the stroke primitive may be decomposed to filled polygons. To keep + // evtl. set DrawModes aka DrawModeFlags::BlackLine, DrawModeFlags::GrayLine, + // DrawModeFlags::GhostedLine, DrawModeFlags::WhiteLine or DrawModeFlags::SettingsLine + // working, these need to be copied to the corresponding fill modes + const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode()); + adaptLineToFillDrawMode(); + + // polygon stroke primitive + + // Lines with 1 and 2 pixel width without AA need special treatment since their visualization + // as filled polygons is geometrically correct but looks wrong since polygon filling avoids + // the right and bottom pixels. The used method evaluates that and takes the correct action, + // including calling recursively with decomposition if line is wide enough + RenderPolygonStrokePrimitive2D(rPolygonStrokePrimitive2D); + + // restore DrawMode + mpOutputDevice->SetDrawMode(nOriginalDrawMode); +} + +void VclPixelProcessor2D::processFillHatchPrimitive2D( + const primitive2d::FillHatchPrimitive2D& rFillHatchPrimitive) +{ + if (getViewInformation2D().getUseAntiAliasing()) + { + // if AA is used (or ignore smoothing is on), there is no need to smooth + // hatch painting, use decomposition + process(rFillHatchPrimitive); + } + else + { + // without AA, use VCL to draw the hatch. It snaps hatch distances to the next pixel + // and forces hatch distance to be >= 3 pixels to make the hatch display look smoother. + // This is wrong in principle, but looks nicer. This could also be done here directly + // without VCL usage if needed + const attribute::FillHatchAttribute& rFillHatchAttributes + = rFillHatchPrimitive.getFillHatch(); + + // create hatch polygon in range size and discrete coordinates + basegfx::B2DRange aHatchRange(rFillHatchPrimitive.getOutputRange()); + aHatchRange.transform(maCurrentTransformation); + const basegfx::B2DPolygon aHatchPolygon(basegfx::utils::createPolygonFromRect(aHatchRange)); + + if (rFillHatchAttributes.isFillBackground()) + { + // #i111846# background fill is active; draw fill polygon + const basegfx::BColor aPolygonColor( + maBColorModifierStack.getModifiedColor(rFillHatchPrimitive.getBColor())); + + mpOutputDevice->SetFillColor(Color(aPolygonColor)); + mpOutputDevice->SetLineColor(); + mpOutputDevice->DrawPolygon(aHatchPolygon); + } + + // set hatch line color + const basegfx::BColor aHatchColor( + maBColorModifierStack.getModifiedColor(rFillHatchPrimitive.getBColor())); + mpOutputDevice->SetFillColor(); + mpOutputDevice->SetLineColor(Color(aHatchColor)); + + // get hatch style + HatchStyle eHatchStyle(HatchStyle::Single); + + switch (rFillHatchAttributes.getStyle()) + { + default: // HatchStyle::Single + { + break; + } + case attribute::HatchStyle::Double: + { + eHatchStyle = HatchStyle::Double; + break; + } + case attribute::HatchStyle::Triple: + { + eHatchStyle = HatchStyle::Triple; + break; + } + } + + // create hatch + const basegfx::B2DVector aDiscreteDistance( + maCurrentTransformation * basegfx::B2DVector(rFillHatchAttributes.getDistance(), 0.0)); + const sal_uInt32 nDistance(basegfx::fround(aDiscreteDistance.getLength())); + const sal_uInt32 nAngle10( + basegfx::rad2deg<10>(basegfx::fround(rFillHatchAttributes.getAngle()))); + ::Hatch aVCLHatch(eHatchStyle, Color(rFillHatchAttributes.getColor()), nDistance, + Degree10(nAngle10)); + + // draw hatch using VCL + mpOutputDevice->DrawHatch(::tools::PolyPolygon(::tools::Polygon(aHatchPolygon)), aVCLHatch); + } +} + +void VclPixelProcessor2D::processBackgroundColorPrimitive2D( + const primitive2d::BackgroundColorPrimitive2D& rPrimitive) +{ + // #i98404# Handle directly, especially when AA is active + const AntialiasingFlags nOriginalAA(mpOutputDevice->GetAntialiasing()); + + // switch AA off in all cases + mpOutputDevice->SetAntialiasing(mpOutputDevice->GetAntialiasing() & ~AntialiasingFlags::Enable); + + // create color for fill + const basegfx::BColor aPolygonColor( + maBColorModifierStack.getModifiedColor(rPrimitive.getBColor())); + Color aFillColor(aPolygonColor); + aFillColor.SetAlpha(255 - sal_uInt8((rPrimitive.getTransparency() * 255.0) + 0.5)); + mpOutputDevice->SetFillColor(aFillColor); + mpOutputDevice->SetLineColor(); + + // create rectangle for fill + const basegfx::B2DRange& aViewport(getViewInformation2D().getDiscreteViewport()); + const ::tools::Rectangle aRectangle(static_cast<sal_Int32>(floor(aViewport.getMinX())), + static_cast<sal_Int32>(floor(aViewport.getMinY())), + static_cast<sal_Int32>(ceil(aViewport.getMaxX())), + static_cast<sal_Int32>(ceil(aViewport.getMaxY()))); + mpOutputDevice->DrawRect(aRectangle); + + // restore AA setting + mpOutputDevice->SetAntialiasing(nOriginalAA); +} + +void VclPixelProcessor2D::processBorderLinePrimitive2D( + const drawinglayer::primitive2d::BorderLinePrimitive2D& rBorder) +{ + // Process recursively, but switch off AntiAliasing for + // horizontal/vertical lines (*not* diagonal lines). + // Checked using AntialiasingFlags::PixelSnapHairline instead, + // but with AntiAliasing on the display really is too 'ghosty' when + // using fine stroking. Correct, but 'ghosty'. + + // It has shown that there are quite some problems here: + // - vcl OutDev renderer methods still use fallbacks to paint + // multiple single lines between discrete sizes of < 3.5 what + // looks bad and does not match + // - mix of filled Polygons and Lines is bad when AA switched off + // - Alignment of AA with non-AA may be bad in diverse different + // renderers + // + // Due to these reasons I change the strategy: Always draw AAed, but + // allow fallback to test/check and if needed. The normal case + // where BorderLines will be system-dependently snapped to have at + // least a single discrete width per partial line (there may be up to + // three) works well nowadays due to most renderers moving the AA stuff + // by 0.5 pixels (discrete units) to match well with the non-AAed parts. + // + // Env-Switch for steering this, default is off. + // Enable by setting at all (and to something) + static const char* pSwitchOffAntiAliasingForHorVerBorderlines( + getenv("SAL_SWITCH_OFF_ANTIALIASING_FOR_HOR_VER_BORTDERLINES")); + static bool bSwitchOffAntiAliasingForHorVerBorderlines( + nullptr != pSwitchOffAntiAliasingForHorVerBorderlines); + + if (bSwitchOffAntiAliasingForHorVerBorderlines + && rBorder.isHorizontalOrVertical(getViewInformation2D())) + { + AntialiasingFlags nAntiAliasing = mpOutputDevice->GetAntialiasing(); + mpOutputDevice->SetAntialiasing(nAntiAliasing & ~AntialiasingFlags::Enable); + process(rBorder); + mpOutputDevice->SetAntialiasing(nAntiAliasing); + } + else + { + process(rBorder); + } +} + +void VclPixelProcessor2D::processInvertPrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) +{ + // invert primitive (currently only used for HighContrast fallback for selection in SW and SC). + // (Not true, also used at least for the drawing of dragged column and row boundaries in SC.) + // Set OutDev to XOR and switch AA off (XOR does not work with AA) + mpOutputDevice->Push(); + mpOutputDevice->SetRasterOp(RasterOp::Xor); + const AntialiasingFlags nAntiAliasing(mpOutputDevice->GetAntialiasing()); + mpOutputDevice->SetAntialiasing(nAntiAliasing & ~AntialiasingFlags::Enable); + + // process content recursively + process(rCandidate); + + // restore OutDev + mpOutputDevice->Pop(); + mpOutputDevice->SetAntialiasing(nAntiAliasing); +} + +void VclPixelProcessor2D::processMetaFilePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) +{ + // #i98289# + const bool bForceLineSnap(getViewInformation2D().getPixelSnapHairline()); + const AntialiasingFlags nOldAntiAliase(mpOutputDevice->GetAntialiasing()); + + if (bForceLineSnap) + { + mpOutputDevice->SetAntialiasing(nOldAntiAliase | AntialiasingFlags::PixelSnapHairline); + } + + process(rCandidate); + + if (bForceLineSnap) + { + mpOutputDevice->SetAntialiasing(nOldAntiAliase); + } +} + +void VclPixelProcessor2D::processFillGradientPrimitive2D( + const primitive2d::FillGradientPrimitive2D& rPrimitive) +{ + const attribute::FillGradientAttribute& rFillGradient = rPrimitive.getFillGradient(); + bool useDecompose(false); + + // MCGR: *many* - and not only GradientStops - cases cannot be handled by VCL + // so use decomposition + if (rFillGradient.cannotBeHandledByVCL()) + { + useDecompose = true; + } + + // tdf#149754 VCL gradient draw is not capable to handle all primitive gradient definitions, + // what should be clear due to being developed to extend/replace them in + // capabilities & precision. + // It is e.g. not capable to correctly paint if the OutputRange is not completely + // inside the DefinitionRange, thus forcing to paint gradient parts *outside* the + // DefinitionRange. + // This happens for Writer with Frames anchored in Frames (and was broken due to + // falling back to VCL Gradient paint here), and for the new SlideBackgroundFill + // Fill mode for objects using it that reach outside the page (which is the + // DefinitionRange in that case). + // I see no real reason to fallback here to OutputDevice::DrawGradient and VCL + // gradient paint at all (system-dependent renderers wouldn't in the future), but + // will for convenience only add that needed additional correcting case + if (!useDecompose && !rPrimitive.getDefinitionRange().isInside(rPrimitive.getOutputRange())) + { + useDecompose = true; + } + + // tdf#151081 need to use regular primitive decomposition when the gradient + // is transformed in any other way then just translate & scale + if (!useDecompose) + { + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + + maCurrentTransformation.decompose(aScale, aTranslate, fRotate, fShearX); + + // detect if transformation is rotated, sheared or mirrored in X and/or Y + if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX) + || aScale.getX() < 0.0 || aScale.getY() < 0.0) + { + useDecompose = true; + } + } + + if (useDecompose) + { + // default is to use the direct render below. For security, + // keep the (simple) fallback to decompose in place here + static bool bTryDirectRender(true); + + if (bTryDirectRender) + { + // MCGR: Avoid one level of primitive creation, use FillGradientPrimitive2D + // tooling to directly create needed geometry & color for getting better + // performance (partially compensate for potentially more expensive multi + // color gradients). + // To handle a primitive that needs paint, either use decompose, or - when you + // do not want that for any reason, e.g. extra primitives created - implement + // a direct handling in your primitive renderer. This is always possible + // since primitives by definition are self-contained what means they have all + // needed data locally available to do so. + // The question is the complexity to invest - the implemented decompose + // is always a good hint of what is needed to do this. In this case I decided + // to add some tooling methods to the primitive itself to support this. These + // are used in decompose and can be used - as here now - for direct handling, + // too. This is always a possibility in primitive handling - you can, but do not + // have to. + mpOutputDevice->SetFillColor( + Color(maBColorModifierStack.getModifiedColor(rPrimitive.getOuterColor()))); + mpOutputDevice->SetLineColor(); + mpOutputDevice->DrawTransparent( + maCurrentTransformation, + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect(rPrimitive.getOutputRange())), + 0.0); + + // paint solid fill steps by providing callback as lambda + auto aCallback([&rPrimitive, this](const basegfx::B2DHomMatrix& rMatrix, + const basegfx::BColor& rColor) { + // create part polygon + basegfx::B2DPolygon aNewPoly(rPrimitive.getUnitPolygon()); + aNewPoly.transform(rMatrix); + + // create solid fill + mpOutputDevice->SetFillColor(Color(maBColorModifierStack.getModifiedColor(rColor))); + mpOutputDevice->DrawTransparent(maCurrentTransformation, + basegfx::B2DPolyPolygon(aNewPoly), 0.0); + }); + + // call value generator to trigger callbacks + rPrimitive.generateMatricesAndColors(aCallback); + } + else + { + // use the decompose + process(rPrimitive); + } + + return; + } + + // try to use vcl - since vcl uses the old gradient paint mechanisms this may + // create wrong geometries. If so, add another case above for useDecompose + Gradient aGradient(rFillGradient.getStyle(), + Color(rFillGradient.getColorStops().front().getStopColor()), + Color(rFillGradient.getColorStops().back().getStopColor())); + + aGradient.SetAngle(Degree10(static_cast<int>(basegfx::rad2deg<10>(rFillGradient.getAngle())))); + aGradient.SetBorder(rFillGradient.getBorder() * 100); + aGradient.SetOfsX(rFillGradient.getOffsetX() * 100.0); + aGradient.SetOfsY(rFillGradient.getOffsetY() * 100.0); + aGradient.SetSteps(rFillGradient.getSteps()); + + basegfx::B2DRange aOutputRange(rPrimitive.getOutputRange()); + aOutputRange.transform(maCurrentTransformation); + basegfx::B2DRange aFullRange(rPrimitive.getDefinitionRange()); + aFullRange.transform(maCurrentTransformation); + + const tools::Rectangle aOutputRectangle( + std::floor(aOutputRange.getMinX()), std::floor(aOutputRange.getMinY()), + std::ceil(aOutputRange.getMaxX()), std::ceil(aOutputRange.getMaxY())); + const tools::Rectangle aFullRectangle( + std::floor(aFullRange.getMinX()), std::floor(aFullRange.getMinY()), + std::ceil(aFullRange.getMaxX()), std::ceil(aFullRange.getMaxY())); + + mpOutputDevice->Push(vcl::PushFlags::CLIPREGION); + mpOutputDevice->IntersectClipRegion(aOutputRectangle); + mpOutputDevice->DrawGradient(aFullRectangle, aGradient); + mpOutputDevice->Pop(); +} + +void VclPixelProcessor2D::processPatternFillPrimitive2D( + const primitive2d::PatternFillPrimitive2D& rPrimitive) +{ + const basegfx::B2DRange& rReferenceRange = rPrimitive.getReferenceRange(); + if (rReferenceRange.isEmpty() || rReferenceRange.getWidth() <= 0.0 + || rReferenceRange.getHeight() <= 0.0) + return; + + basegfx::B2DPolyPolygon aMask = rPrimitive.getMask(); + aMask.transform(maCurrentTransformation); + const basegfx::B2DRange aMaskRange(aMask.getB2DRange()); + + if (aMaskRange.isEmpty() || aMaskRange.getWidth() <= 0.0 || aMaskRange.getHeight() <= 0.0) + return; + + sal_uInt32 nTileWidth, nTileHeight; + rPrimitive.getTileSize(nTileWidth, nTileHeight, getViewInformation2D()); + if (nTileWidth == 0 || nTileHeight == 0) + return; + BitmapEx aTileImage = rPrimitive.createTileImage(nTileWidth, nTileHeight); + tools::Rectangle aMaskRect = vcl::unotools::rectangleFromB2DRectangle(aMaskRange); + + // Unless smooth edges are needed, simply use clipping. + if (basegfx::utils::isRectangle(aMask) || !getViewInformation2D().getUseAntiAliasing()) + { + mpOutputDevice->Push(vcl::PushFlags::CLIPREGION); + mpOutputDevice->IntersectClipRegion(vcl::Region(aMask)); + Wallpaper aWallpaper(aTileImage); + aWallpaper.SetColor(COL_TRANSPARENT); + mpOutputDevice->DrawWallpaper(aMaskRect, aWallpaper); + mpOutputDevice->Pop(); + return; + } + + impBufferDevice aBufferDevice(*mpOutputDevice, aMaskRange); + + if (!aBufferDevice.isVisible()) + return; + + // remember last OutDev and set to content + OutputDevice* pLastOutputDevice = mpOutputDevice; + mpOutputDevice = &aBufferDevice.getContent(); + + // if the tile is a single pixel big, just flood fill with that pixel color + if (nTileWidth == 1 && nTileHeight == 1) + { + Color col = aTileImage.GetPixelColor(0, 0); + mpOutputDevice->SetLineColor(col); + mpOutputDevice->SetFillColor(col); + mpOutputDevice->DrawRect(aMaskRect); + } + else + { + Wallpaper aWallpaper(aTileImage); + aWallpaper.SetColor(COL_TRANSPARENT); + mpOutputDevice->DrawWallpaper(aMaskRect, aWallpaper); + } + + // back to old OutDev + mpOutputDevice = pLastOutputDevice; + + // draw mask + VirtualDevice& rMask = aBufferDevice.getTransparence(); + rMask.SetLineColor(); + rMask.SetFillColor(COL_BLACK); + rMask.DrawPolyPolygon(aMask); + + // dump buffer to outdev + aBufferDevice.paint(); +} + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx new file mode 100644 index 0000000000..c144ba9647 --- /dev/null +++ b/drawinglayer/source/processor2d/vclpixelprocessor2d.hxx @@ -0,0 +1,109 @@ +/* -*- 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 . + */ + +#pragma once + +#include "vclprocessor2d.hxx" +#include <vcl/outdev.hxx> + +#include <memory> + +namespace drawinglayer::primitive2d +{ +class PolyPolygonColorPrimitive2D; +class PolygonHairlinePrimitive2D; +class PolygonStrokePrimitive2D; +class WrongSpellPrimitive2D; +class TextSimplePortionPrimitive; +class BitmapPrimitive2D; +class PolyPolygonGradientPrimitive2D; +class UnifiedTransparencePrimitive2D; +class ControlPrimitive2D; +class PolygonStrokePrimitive2D; +class FillHatchPrimitive2D; +class BackgroundColorPrimitive2D; +class BorderLinePrimitive2D; +class FillGradientPrimitive2D; +class PatternFillPrimitive2D; +} + +namespace drawinglayer::processor2d +{ +/** VclPixelProcessor2D class + + This processor derived from VclProcessor2D is the base class for rendering + all fed primitives to a VCL Window. It is the currently used renderer + for all VCL editing output from the DrawingLayer. + */ +class VclPixelProcessor2D final : public VclProcessor2D +{ + AntialiasingFlags m_nOrigAntiAliasing; + + /* the local processor for BasePrimitive2D-Implementation based primitives, + called from the common process()-implementation + */ + virtual void processBasePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate) override; + + // some helpers to try direct paints (shortcuts) + void tryDrawPolyPolygonColorPrimitive2DDirect( + const primitive2d::PolyPolygonColorPrimitive2D& rSource, double fTransparency); + bool + tryDrawPolygonHairlinePrimitive2DDirect(const primitive2d::PolygonHairlinePrimitive2D& rSource, + double fTransparency); + bool tryDrawPolygonStrokePrimitive2DDirect(const primitive2d::PolygonStrokePrimitive2D& rSource, + double fTransparency); + + void + processWrongSpellPrimitive2D(const primitive2d::WrongSpellPrimitive2D& rWrongSpellPrimitive); + void processTextSimplePortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rCandidate); + void processTextDecoratedPortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rCandidate); + void processPolygonHairlinePrimitive2D( + const primitive2d::PolygonHairlinePrimitive2D& rPolygonHairlinePrimitive2D); + void processBitmapPrimitive2D(const primitive2d::BitmapPrimitive2D& rBitmapCandidate); + void processPolyPolygonGradientPrimitive2D( + const primitive2d::PolyPolygonGradientPrimitive2D& rPolygonCandidate); + void processPolyPolygonColorPrimitive2D( + const primitive2d::PolyPolygonColorPrimitive2D& rPolyPolygonColorPrimitive2D); + void processUnifiedTransparencePrimitive2D( + const primitive2d::UnifiedTransparencePrimitive2D& rUniTransparenceCandidate); + void processControlPrimitive2D(const primitive2d::ControlPrimitive2D& rControlPrimitive); + void processPolygonStrokePrimitive2D( + const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokePrimitive2D); + void processFillHatchPrimitive2D(const primitive2d::FillHatchPrimitive2D& rFillHatchPrimitive); + void + processBackgroundColorPrimitive2D(const primitive2d::BackgroundColorPrimitive2D& rPrimitive); + void + processBorderLinePrimitive2D(const drawinglayer::primitive2d::BorderLinePrimitive2D& rBorder); + void processInvertPrimitive2D(const primitive2d::BasePrimitive2D& rCandidate); + void processMetaFilePrimitive2D(const primitive2d::BasePrimitive2D& rCandidate); + void processFillGradientPrimitive2D(const primitive2d::FillGradientPrimitive2D& rPrimitive); + void processPatternFillPrimitive2D(const primitive2d::PatternFillPrimitive2D& rPrimitive); + +public: + /// constructor/destructor + VclPixelProcessor2D(const geometry::ViewInformation2D& rViewInformation, OutputDevice& rOutDev, + const basegfx::BColorModifierStack& rInitStack + = basegfx::BColorModifierStack()); + virtual ~VclPixelProcessor2D() override; +}; +} // end of namespace drawinglayer::processor2d + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclprocessor2d.cxx b/drawinglayer/source/processor2d/vclprocessor2d.cxx new file mode 100644 index 0000000000..2c3521ace0 --- /dev/null +++ b/drawinglayer/source/processor2d/vclprocessor2d.cxx @@ -0,0 +1,1560 @@ +/* -*- 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 "vclprocessor2d.hxx" + +#include "getdigitlanguage.hxx" +#include "vclhelperbufferdevice.hxx" +#include <cmath> +#include <comphelper/string.hxx> +#include <comphelper/lok.hxx> +#include <svtools/optionsdrawinglayer.hxx> +#include <tools/debug.hxx> +#include <tools/fract.hxx> +#include <utility> +#include <vcl/glyphitemcache.hxx> +#include <vcl/graph.hxx> +#include <vcl/kernarray.hxx> +#include <vcl/outdev.hxx> +#include <rtl/ustrbuf.hxx> +#include <sal/log.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <basegfx/color/bcolor.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/pagepreviewprimitive2d.hxx> +#include <drawinglayer/primitive2d/textenumsprimitive2d.hxx> +#include <drawinglayer/primitive2d/svggradientprimitive2d.hxx> +// for support of Title/Description in all apps when embedding pictures +#include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx> +// control support +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> + +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/epsprimitive2d.hxx> + +using namespace com::sun::star; + +namespace +{ +sal_uInt32 calculateStepsForSvgGradient(const basegfx::BColor& rColorA, + const basegfx::BColor& rColorB, double fDelta, + double fDiscreteUnit) +{ + // use color distance, assume to do every color step + sal_uInt32 nSteps(basegfx::fround(rColorA.getDistance(rColorB) * 255.0)); + + if (nSteps) + { + // calc discrete length to change color each discrete unit (pixel) + const sal_uInt32 nDistSteps(basegfx::fround(fDelta / fDiscreteUnit)); + + nSteps = std::min(nSteps, nDistSteps); + } + + // reduce quality to 3 discrete units or every 3rd color step for rendering + nSteps /= 2; + + // roughly cut when too big or too small (not full quality, reduce complexity) + nSteps = std::min(nSteps, sal_uInt32(255)); + nSteps = std::max(nSteps, sal_uInt32(1)); + + return nSteps; +} +} + +namespace +{ +/** helper to convert a MapMode to a transformation */ +basegfx::B2DHomMatrix getTransformFromMapMode(const MapMode& rMapMode) +{ + basegfx::B2DHomMatrix aMapping; + const Fraction aNoScale(1, 1); + const Point& rOrigin(rMapMode.GetOrigin()); + + if (0 != rOrigin.X() || 0 != rOrigin.Y()) + { + aMapping.translate(rOrigin.X(), rOrigin.Y()); + } + + if (rMapMode.GetScaleX() != aNoScale || rMapMode.GetScaleY() != aNoScale) + { + aMapping.scale(double(rMapMode.GetScaleX()), double(rMapMode.GetScaleY())); + } + + return aMapping; +} +} + +namespace drawinglayer::processor2d +{ +// rendering support + +// directdraw of text simple portion or decorated portion primitive. When decorated, all the extra +// information is translated to VCL parameters and set at the font. +// Acceptance is restricted to no shearing and positive scaling in X and Y (no font mirroring +// for VCL) +void VclProcessor2D::RenderTextSimpleOrDecoratedPortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate) +{ + // decompose matrix to have position and size of text + basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation + * rTextCandidate.getTextTransform()); + basegfx::B2DVector aFontScaling, aTranslate; + double fRotate, fShearX; + aLocalTransform.decompose(aFontScaling, aTranslate, fRotate, fShearX); + + bool bPrimitiveAccepted(false); + + // tdf#95581: Assume tiny shears are rounding artefacts or whatever and can be ignored, + // especially if the effect is less than a pixel. + if (std::abs(aFontScaling.getY() * fShearX) < 1) + { + if (basegfx::fTools::less(aFontScaling.getX(), 0.0) + && basegfx::fTools::less(aFontScaling.getY(), 0.0)) + { + // handle special case: If scale is negative in (x,y) (3rd quadrant), it can + // be expressed as rotation by PI. Use this since the Font rendering will not + // apply the negative scales in any form + aFontScaling = basegfx::absolute(aFontScaling); + fRotate += M_PI; + } + + if (basegfx::fTools::more(aFontScaling.getX(), 0.0) + && basegfx::fTools::more(aFontScaling.getY(), 0.0)) + { + double fIgnoreRotate, fIgnoreShearX; + + basegfx::B2DVector aFontSize, aTextTranslate; + rTextCandidate.getTextTransform().decompose(aFontSize, aTextTranslate, fIgnoreRotate, + fIgnoreShearX); + + // tdf#153092 Ideally we don't have to scale the font and dxarray, but we might have + // to nevertheless if dealing with non integer sizes + const bool bScaleFont(aFontSize.getY() != std::round(aFontSize.getY()) + || comphelper::LibreOfficeKit::isActive()); + vcl::Font aFont; + + // Get the VCL font + if (!bScaleFont) + { + aFont = primitive2d::getVclFontFromFontAttribute( + rTextCandidate.getFontAttribute(), aFontSize.getX(), aFontSize.getY(), fRotate, + rTextCandidate.getLocale()); + } + else + { + aFont = primitive2d::getVclFontFromFontAttribute( + rTextCandidate.getFontAttribute(), aFontScaling.getX(), aFontScaling.getY(), + fRotate, rTextCandidate.getLocale()); + } + + // Don't draw fonts without height + if (aFont.GetFontHeight() <= 0) + return; + + // set FillColor Attribute + const Color aFillColor(rTextCandidate.getTextFillColor()); + aFont.SetTransparent(aFillColor.IsTransparent()); + aFont.SetFillColor(aFillColor); + + // handle additional font attributes + const primitive2d::TextDecoratedPortionPrimitive2D* pTCPP = nullptr; + if (rTextCandidate.getPrimitive2DID() == PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D) + pTCPP = static_cast<const primitive2d::TextDecoratedPortionPrimitive2D*>( + &rTextCandidate); + + if (pTCPP != nullptr) + { + // set the color of text decorations + const basegfx::BColor aTextlineColor + = maBColorModifierStack.getModifiedColor(pTCPP->getTextlineColor()); + mpOutputDevice->SetTextLineColor(Color(aTextlineColor)); + + // set Overline attribute + const FontLineStyle eFontOverline( + primitive2d::mapTextLineToFontLineStyle(pTCPP->getFontOverline())); + if (eFontOverline != LINESTYLE_NONE) + { + aFont.SetOverline(eFontOverline); + const basegfx::BColor aOverlineColor + = maBColorModifierStack.getModifiedColor(pTCPP->getOverlineColor()); + mpOutputDevice->SetOverlineColor(Color(aOverlineColor)); + if (pTCPP->getWordLineMode()) + aFont.SetWordLineMode(true); + } + + // set Underline attribute + const FontLineStyle eFontLineStyle( + primitive2d::mapTextLineToFontLineStyle(pTCPP->getFontUnderline())); + if (eFontLineStyle != LINESTYLE_NONE) + { + aFont.SetUnderline(eFontLineStyle); + if (pTCPP->getWordLineMode()) + aFont.SetWordLineMode(true); + } + + // set Strikeout attribute + const FontStrikeout eFontStrikeout( + primitive2d::mapTextStrikeoutToFontStrikeout(pTCPP->getTextStrikeout())); + + if (eFontStrikeout != STRIKEOUT_NONE) + aFont.SetStrikeout(eFontStrikeout); + + // set EmphasisMark attribute + FontEmphasisMark eFontEmphasisMark = FontEmphasisMark::NONE; + switch (pTCPP->getTextEmphasisMark()) + { + default: + SAL_WARN("drawinglayer", + "Unknown EmphasisMark style " << pTCPP->getTextEmphasisMark()); + [[fallthrough]]; + case primitive2d::TEXT_FONT_EMPHASIS_MARK_NONE: + eFontEmphasisMark = FontEmphasisMark::NONE; + break; + case primitive2d::TEXT_FONT_EMPHASIS_MARK_DOT: + eFontEmphasisMark = FontEmphasisMark::Dot; + break; + case primitive2d::TEXT_FONT_EMPHASIS_MARK_CIRCLE: + eFontEmphasisMark = FontEmphasisMark::Circle; + break; + case primitive2d::TEXT_FONT_EMPHASIS_MARK_DISC: + eFontEmphasisMark = FontEmphasisMark::Disc; + break; + case primitive2d::TEXT_FONT_EMPHASIS_MARK_ACCENT: + eFontEmphasisMark = FontEmphasisMark::Accent; + break; + } + + if (eFontEmphasisMark != FontEmphasisMark::NONE) + { + DBG_ASSERT((pTCPP->getEmphasisMarkAbove() != pTCPP->getEmphasisMarkBelow()), + "DrawingLayer: Bad EmphasisMark position!"); + if (pTCPP->getEmphasisMarkAbove()) + eFontEmphasisMark |= FontEmphasisMark::PosAbove; + else + eFontEmphasisMark |= FontEmphasisMark::PosBelow; + aFont.SetEmphasisMark(eFontEmphasisMark); + } + + // set Relief attribute + FontRelief eFontRelief = FontRelief::NONE; + switch (pTCPP->getTextRelief()) + { + default: + SAL_WARN("drawinglayer", "Unknown Relief style " << pTCPP->getTextRelief()); + [[fallthrough]]; + case primitive2d::TEXT_RELIEF_NONE: + eFontRelief = FontRelief::NONE; + break; + case primitive2d::TEXT_RELIEF_EMBOSSED: + eFontRelief = FontRelief::Embossed; + break; + case primitive2d::TEXT_RELIEF_ENGRAVED: + eFontRelief = FontRelief::Engraved; + break; + } + + if (eFontRelief != FontRelief::NONE) + aFont.SetRelief(eFontRelief); + + // set Shadow attribute + if (pTCPP->getShadow()) + aFont.SetShadow(true); + } + + // create integer DXArray + KernArray aDXArray; + + if (!rTextCandidate.getDXArray().empty()) + { + double fPixelVectorFactor(1.0); + if (bScaleFont) + { + const basegfx::B2DVector aPixelVector(maCurrentTransformation + * basegfx::B2DVector(1.0, 0.0)); + fPixelVectorFactor = aPixelVector.getLength(); + } + + aDXArray.reserve(rTextCandidate.getDXArray().size()); + for (auto const& elem : rTextCandidate.getDXArray()) + aDXArray.push_back(basegfx::fround(elem * fPixelVectorFactor)); + } + + // set parameters and paint text snippet + const basegfx::BColor aRGBFontColor( + maBColorModifierStack.getModifiedColor(rTextCandidate.getFontColor())); + const vcl::text::ComplexTextLayoutFlags nOldLayoutMode(mpOutputDevice->GetLayoutMode()); + + if (rTextCandidate.getFontAttribute().getRTL()) + { + vcl::text::ComplexTextLayoutFlags nRTLLayoutMode( + nOldLayoutMode & ~vcl::text::ComplexTextLayoutFlags::BiDiStrong); + nRTLLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl + | vcl::text::ComplexTextLayoutFlags::TextOriginLeft; + mpOutputDevice->SetLayoutMode(nRTLLayoutMode); + } + + mpOutputDevice->SetTextColor(Color(aRGBFontColor)); + + OUString aText(rTextCandidate.getText()); + sal_Int32 nPos = rTextCandidate.getTextPosition(); + sal_Int32 nLen = rTextCandidate.getTextLength(); + + // this contraption is used in editeng, with format paragraph used to + // set a tab with a tab-fill character + if (rTextCandidate.isFilled()) + { + tools::Long nWidthToFill = rTextCandidate.getWidthToFill(); + + tools::Long nWidth + = mpOutputDevice->GetTextArray(rTextCandidate.getText(), &aDXArray, 0, 1); + sal_Int32 nChars = 2; + if (nWidth) + nChars = nWidthToFill / nWidth; + + OUStringBuffer aFilled(nChars); + comphelper::string::padToLength(aFilled, nChars, aText[0]); + aText = aFilled.makeStringAndClear(); + nPos = 0; + nLen = nChars; + + if (!aDXArray.empty()) + { + sal_Int32 nDX = aDXArray[0]; + aDXArray.resize(nLen); + for (sal_Int32 i = 1; i < nLen; ++i) + aDXArray.set(i, aDXArray[i - 1] + nDX); + } + } + + Point aStartPoint; + bool bChangeMapMode(false); + if (!bScaleFont) + { + basegfx::B2DHomMatrix aCombinedTransform( + getTransformFromMapMode(mpOutputDevice->GetMapMode()) + * maCurrentTransformation); + + basegfx::B2DVector aCurrentScaling, aCurrentTranslate; + double fCurrentRotate; + aCombinedTransform.decompose(aCurrentScaling, aCurrentTranslate, fCurrentRotate, + fIgnoreShearX); + + const Point aOrigin( + basegfx::fround(aCurrentTranslate.getX() / aCurrentScaling.getX()), + basegfx::fround(aCurrentTranslate.getY() / aCurrentScaling.getY())); + + Fraction aScaleX(aCurrentScaling.getX()); + if (!aScaleX.IsValid()) + { + SAL_WARN("drawinglayer", "invalid X Scale"); + return; + } + + Fraction aScaleY(aCurrentScaling.getY()); + if (!aScaleY.IsValid()) + { + SAL_WARN("drawinglayer", "invalid Y Scale"); + return; + } + + MapMode aMapMode(mpOutputDevice->GetMapMode().GetMapUnit(), aOrigin, aScaleX, + aScaleY); + + if (fCurrentRotate) + aTextTranslate *= basegfx::utils::createRotateB2DHomMatrix(fCurrentRotate); + aStartPoint = Point(aTextTranslate.getX(), aTextTranslate.getY()); + + bChangeMapMode = aMapMode != mpOutputDevice->GetMapMode(); + if (bChangeMapMode) + { + mpOutputDevice->Push(vcl::PushFlags::MAPMODE); + mpOutputDevice->SetRelativeMapMode(aMapMode); + } + } + else + { + const basegfx::B2DPoint aPoint(aLocalTransform * basegfx::B2DPoint(0.0, 0.0)); + aStartPoint = Point(basegfx::fround(aPoint.getX()), basegfx::fround(aPoint.getY())); + } + + // tdf#152990 set the font after the MapMode is (potentially) set so canvas uses the desired + // font size + mpOutputDevice->SetFont(aFont); + + if (!aDXArray.empty()) + { + const SalLayoutGlyphs* pGlyphs = SalLayoutGlyphsCache::self()->GetLayoutGlyphs( + mpOutputDevice, aText, nPos, nLen); + mpOutputDevice->DrawTextArray(aStartPoint, aText, aDXArray, + rTextCandidate.getKashidaArray(), nPos, nLen, + SalLayoutFlags::NONE, pGlyphs); + } + else + { + mpOutputDevice->DrawText(aStartPoint, aText, nPos, nLen); + } + + if (rTextCandidate.getFontAttribute().getRTL()) + { + mpOutputDevice->SetLayoutMode(nOldLayoutMode); + } + + if (bChangeMapMode) + mpOutputDevice->Pop(); + + bPrimitiveAccepted = true; + } + } + + if (!bPrimitiveAccepted) + { + // let break down + process(rTextCandidate); + } +} + +// direct draw of hairline +void VclProcessor2D::RenderPolygonHairlinePrimitive2D( + const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate, bool bPixelBased) +{ + const basegfx::BColor aHairlineColor( + maBColorModifierStack.getModifiedColor(rPolygonCandidate.getBColor())); + mpOutputDevice->SetLineColor(Color(aHairlineColor)); + mpOutputDevice->SetFillColor(); + + basegfx::B2DPolygon aLocalPolygon(rPolygonCandidate.getB2DPolygon()); + aLocalPolygon.transform(maCurrentTransformation); + + if (bPixelBased && getViewInformation2D().getPixelSnapHairline()) + { + // #i98289# + // when a Hairline is painted and AntiAliasing is on the option SnapHorVerLinesToDiscrete + // allows to suppress AntiAliasing for pure horizontal or vertical lines. This is done since + // not-AntiAliased such lines look more pleasing to the eye (e.g. 2D chart content). This + // NEEDS to be done in discrete coordinates, so only useful for pixel based rendering. + aLocalPolygon = basegfx::utils::snapPointsOfHorizontalOrVerticalEdges(aLocalPolygon); + } + + mpOutputDevice->DrawPolyLine(aLocalPolygon, 0.0); +} + +// direct draw of transformed BitmapEx primitive +void VclProcessor2D::RenderBitmapPrimitive2D(const primitive2d::BitmapPrimitive2D& rBitmapCandidate) +{ + BitmapEx aBitmapEx(rBitmapCandidate.getBitmap()); + const basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation + * rBitmapCandidate.getTransform()); + + if (maBColorModifierStack.count()) + { + aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack); + + if (aBitmapEx.IsEmpty()) + { + // color gets completely replaced, get it + const basegfx::BColor aModifiedColor( + maBColorModifierStack.getModifiedColor(basegfx::BColor())); + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + aPolygon.transform(aLocalTransform); + + mpOutputDevice->SetFillColor(Color(aModifiedColor)); + mpOutputDevice->SetLineColor(); + mpOutputDevice->DrawPolygon(aPolygon); + + return; + } + } + + // #122923# do no longer add Alpha channel here; the right place to do this is when really + // the own transformer is used (see OutputDevice::DrawTransformedBitmapEx). + + // draw using OutputDevice'sDrawTransformedBitmapEx + mpOutputDevice->DrawTransformedBitmapEx(aLocalTransform, aBitmapEx); +} + +void VclProcessor2D::RenderFillGraphicPrimitive2D( + const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate) +{ + bool bPrimitiveAccepted = RenderFillGraphicPrimitive2DImpl(rFillBitmapCandidate); + + if (!bPrimitiveAccepted) + { + // do not accept, use decomposition + process(rFillBitmapCandidate); + } +} + +bool VclProcessor2D::RenderFillGraphicPrimitive2DImpl( + const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate) +{ + const attribute::FillGraphicAttribute& rFillGraphicAttribute( + rFillBitmapCandidate.getFillGraphic()); + + // #121194# when tiling is used and content is bitmap-based, do direct tiling in the + // renderer on pixel base to ensure tight fitting. Do not do this when + // the fill is rotated or sheared. + if (!rFillGraphicAttribute.getTiling()) + return false; + + // content is bitmap(ex) + // + // for Vector Graphic Data (SVG, EMF+) support, force decomposition when present. This will lead to use + // the primitive representation of the vector data directly. + // + // when graphic is animated, force decomposition to use the correct graphic, else + // fill style will not be animated + if (GraphicType::Bitmap != rFillGraphicAttribute.getGraphic().GetType() + || rFillGraphicAttribute.getGraphic().getVectorGraphicData() + || rFillGraphicAttribute.getGraphic().IsAnimated()) + return false; + + // decompose matrix to check for shear, rotate and mirroring + basegfx::B2DHomMatrix aLocalTransform(maCurrentTransformation + * rFillBitmapCandidate.getTransformation()); + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + aLocalTransform.decompose(aScale, aTranslate, fRotate, fShearX); + + // when nopt rotated/sheared + if (!basegfx::fTools::equalZero(fRotate) || !basegfx::fTools::equalZero(fShearX)) + return false; + + // no shear or rotate, draw direct in pixel coordinates + + // transform object range to device coordinates (pixels). Use + // the device transformation for better accuracy + basegfx::B2DRange aObjectRange(aTranslate, aTranslate + aScale); + aObjectRange.transform(mpOutputDevice->GetViewTransformation()); + + // extract discrete size of object + const sal_Int32 nOWidth(basegfx::fround(aObjectRange.getWidth())); + const sal_Int32 nOHeight(basegfx::fround(aObjectRange.getHeight())); + + // only do something when object has a size in discrete units + if (nOWidth <= 0 || nOHeight <= 0) + return true; + + // transform graphic range to device coordinates (pixels). Use + // the device transformation for better accuracy + basegfx::B2DRange aGraphicRange(rFillGraphicAttribute.getGraphicRange()); + aGraphicRange.transform(mpOutputDevice->GetViewTransformation() * aLocalTransform); + + // extract discrete size of graphic + // caution: when getting to zero, nothing would be painted; thus, do not allow this + const sal_Int32 nBWidth(std::max(sal_Int32(1), basegfx::fround(aGraphicRange.getWidth()))); + const sal_Int32 nBHeight(std::max(sal_Int32(1), basegfx::fround(aGraphicRange.getHeight()))); + + // nBWidth, nBHeight is the pixel size of the needed bitmap. To not need to scale it + // in vcl many times, create a size-optimized version + const Size aNeededBitmapSizePixel(nBWidth, nBHeight); + BitmapEx aBitmapEx(rFillGraphicAttribute.getGraphic().GetBitmapEx()); + const bool bPreScaled(nBWidth * nBHeight < (250 * 250)); + + // ... but only up to a maximum size, else it gets too expensive + if (bPreScaled) + { + // if color depth is below 24bit, expand before scaling for better quality. + // This is even needed for low colors, else the scale will produce + // a bitmap in gray or Black/White (!) + if (isPalettePixelFormat(aBitmapEx.getPixelFormat())) + { + aBitmapEx.Convert(BmpConversion::N24Bit); + } + + aBitmapEx.Scale(aNeededBitmapSizePixel, BmpScaleFlag::Interpolate); + } + + if (maBColorModifierStack.count()) + { + // when color modifier, apply to bitmap + aBitmapEx = aBitmapEx.ModifyBitmapEx(maBColorModifierStack); + + // ModifyBitmapEx uses empty bitmap as sign to return that + // the content will be completely replaced to mono color, use shortcut + if (aBitmapEx.IsEmpty()) + { + // color gets completely replaced, get it + const basegfx::BColor aModifiedColor( + maBColorModifierStack.getModifiedColor(basegfx::BColor())); + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + aPolygon.transform(aLocalTransform); + + mpOutputDevice->SetFillColor(Color(aModifiedColor)); + mpOutputDevice->SetLineColor(); + mpOutputDevice->DrawPolygon(aPolygon); + + return true; + } + } + + sal_Int32 nBLeft(basegfx::fround(aGraphicRange.getMinX())); + sal_Int32 nBTop(basegfx::fround(aGraphicRange.getMinY())); + const sal_Int32 nOLeft(basegfx::fround(aObjectRange.getMinX())); + const sal_Int32 nOTop(basegfx::fround(aObjectRange.getMinY())); + sal_Int32 nPosX(0); + sal_Int32 nPosY(0); + + if (nBLeft > nOLeft) + { + const sal_Int32 nDiff((nBLeft / nBWidth) + 1); + + nPosX -= nDiff; + nBLeft -= nDiff * nBWidth; + } + + if (nBLeft + nBWidth <= nOLeft) + { + const sal_Int32 nDiff(-nBLeft / nBWidth); + + nPosX += nDiff; + nBLeft += nDiff * nBWidth; + } + + if (nBTop > nOTop) + { + const sal_Int32 nDiff((nBTop / nBHeight) + 1); + + nPosY -= nDiff; + nBTop -= nDiff * nBHeight; + } + + if (nBTop + nBHeight <= nOTop) + { + const sal_Int32 nDiff(-nBTop / nBHeight); + + nPosY += nDiff; + nBTop += nDiff * nBHeight; + } + + // prepare OutDev + const Point aEmptyPoint(0, 0); + // the visible rect, in pixels + const ::tools::Rectangle aVisiblePixel(aEmptyPoint, mpOutputDevice->GetOutputSizePixel()); + const bool bWasEnabled(mpOutputDevice->IsMapModeEnabled()); + mpOutputDevice->EnableMapMode(false); + + // check if offset is used + const sal_Int32 nOffsetX(basegfx::fround(rFillGraphicAttribute.getOffsetX() * nBWidth)); + + if (nOffsetX) + { + // offset in X, so iterate over Y first and draw lines + for (sal_Int32 nYPos(nBTop); nYPos < nOTop + nOHeight; nYPos += nBHeight, nPosY++) + { + for (sal_Int32 nXPos((nPosY % 2) ? nBLeft - nBWidth + nOffsetX : nBLeft); + nXPos < nOLeft + nOWidth; nXPos += nBWidth) + { + const ::tools::Rectangle aOutRectPixel(Point(nXPos, nYPos), aNeededBitmapSizePixel); + + if (aOutRectPixel.Overlaps(aVisiblePixel)) + { + if (bPreScaled) + { + mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(), aBitmapEx); + } + else + { + mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(), + aNeededBitmapSizePixel, aBitmapEx); + } + } + } + } + } + else + { + // check if offset is used + const sal_Int32 nOffsetY(basegfx::fround(rFillGraphicAttribute.getOffsetY() * nBHeight)); + + // possible offset in Y, so iterate over X first and draw columns + for (sal_Int32 nXPos(nBLeft); nXPos < nOLeft + nOWidth; nXPos += nBWidth, nPosX++) + { + for (sal_Int32 nYPos((nPosX % 2) ? nBTop - nBHeight + nOffsetY : nBTop); + nYPos < nOTop + nOHeight; nYPos += nBHeight) + { + const ::tools::Rectangle aOutRectPixel(Point(nXPos, nYPos), aNeededBitmapSizePixel); + + if (aOutRectPixel.Overlaps(aVisiblePixel)) + { + if (bPreScaled) + { + mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(), aBitmapEx); + } + else + { + mpOutputDevice->DrawBitmapEx(aOutRectPixel.TopLeft(), + aNeededBitmapSizePixel, aBitmapEx); + } + } + } + } + } + + // restore OutDev + mpOutputDevice->EnableMapMode(bWasEnabled); + return true; +} + +// direct draw of Graphic +void VclProcessor2D::RenderPolyPolygonGraphicPrimitive2D( + const primitive2d::PolyPolygonGraphicPrimitive2D& rPolygonCandidate) +{ + bool bDone(false); + const basegfx::B2DPolyPolygon& rPolyPolygon = rPolygonCandidate.getB2DPolyPolygon(); + + // #121194# Todo: check if this works + if (!rPolyPolygon.count()) + { + // empty polyPolygon, done + bDone = true; + } + else + { + const attribute::FillGraphicAttribute& rFillGraphicAttribute + = rPolygonCandidate.getFillGraphic(); + + // try to catch cases where the graphic will be color-modified to a single + // color (e.g. shadow) + switch (rFillGraphicAttribute.getGraphic().GetType()) + { + case GraphicType::GdiMetafile: + { + // metafiles are potentially transparent, cannot optimize, not done + break; + } + case GraphicType::Bitmap: + { + if (!rFillGraphicAttribute.getGraphic().IsTransparent() + && !rFillGraphicAttribute.getGraphic().IsAlpha()) + { + // bitmap is not transparent and has no alpha + const sal_uInt32 nBColorModifierStackCount(maBColorModifierStack.count()); + + if (nBColorModifierStackCount) + { + const basegfx::BColorModifierSharedPtr& rTopmostModifier + = maBColorModifierStack.getBColorModifier(nBColorModifierStackCount + - 1); + const basegfx::BColorModifier_replace* pReplacer + = dynamic_cast<const basegfx::BColorModifier_replace*>( + rTopmostModifier.get()); + + if (pReplacer) + { + // the bitmap fill is in unified color, so we can replace it with + // a single polygon fill. The form of the fill depends on tiling + if (rFillGraphicAttribute.getTiling()) + { + // with tiling, fill the whole tools::PolyPolygon with the modifier color + basegfx::B2DPolyPolygon aLocalPolyPolygon(rPolyPolygon); + + aLocalPolyPolygon.transform(maCurrentTransformation); + mpOutputDevice->SetLineColor(); + mpOutputDevice->SetFillColor(Color(pReplacer->getBColor())); + mpOutputDevice->DrawPolyPolygon(aLocalPolyPolygon); + } + else + { + // without tiling, only the area common to the bitmap tile and the + // tools::PolyPolygon is filled. Create the bitmap tile area in object + // coordinates. For this, the object transformation needs to be created + // from the already scaled PolyPolygon. The tile area in object + // coordinates will always be non-rotated, so it's not necessary to + // work with a polygon here + basegfx::B2DRange aTileRange( + rFillGraphicAttribute.getGraphicRange()); + const basegfx::B2DRange aPolyPolygonRange( + rPolyPolygon.getB2DRange()); + const basegfx::B2DHomMatrix aNewObjectTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aPolyPolygonRange.getRange(), + aPolyPolygonRange.getMinimum())); + + aTileRange.transform(aNewObjectTransform); + + // now clip the object polyPolygon against the tile range + // to get the common area + basegfx::B2DPolyPolygon aTarget + = basegfx::utils::clipPolyPolygonOnRange( + rPolyPolygon, aTileRange, true, false); + + if (aTarget.count()) + { + aTarget.transform(maCurrentTransformation); + mpOutputDevice->SetLineColor(); + mpOutputDevice->SetFillColor(Color(pReplacer->getBColor())); + mpOutputDevice->DrawPolyPolygon(aTarget); + } + } + + // simplified output executed, we are done + bDone = true; + } + } + } + break; + } + default: //GraphicType::NONE, GraphicType::Default + { + // empty graphic, we are done + bDone = true; + break; + } + } + } + + if (!bDone) + { + // use default decomposition + process(rPolygonCandidate); + } +} + +// mask group +void VclProcessor2D::RenderMaskPrimitive2DPixel(const primitive2d::MaskPrimitive2D& rMaskCandidate) +{ + if (rMaskCandidate.getChildren().empty()) + return; + + basegfx::B2DPolyPolygon aMask(rMaskCandidate.getMask()); + + if (!aMask.count()) + return; + + aMask.transform(maCurrentTransformation); + + // Unless smooth edges are needed, simply use clipping. + if (basegfx::utils::isRectangle(aMask) || !getViewInformation2D().getUseAntiAliasing()) + { + mpOutputDevice->Push(vcl::PushFlags::CLIPREGION); + mpOutputDevice->IntersectClipRegion(vcl::Region(aMask)); + process(rMaskCandidate.getChildren()); + mpOutputDevice->Pop(); + return; + } + + const basegfx::B2DRange aRange(basegfx::utils::getRange(aMask)); + impBufferDevice aBufferDevice(*mpOutputDevice, aRange); + + if (!aBufferDevice.isVisible()) + return; + + // remember last OutDev and set to content + OutputDevice* pLastOutputDevice = mpOutputDevice; + mpOutputDevice = &aBufferDevice.getContent(); + + // paint to it + process(rMaskCandidate.getChildren()); + + // back to old OutDev + mpOutputDevice = pLastOutputDevice; + + // draw mask + VirtualDevice& rMask = aBufferDevice.getTransparence(); + rMask.SetLineColor(); + rMask.SetFillColor(COL_BLACK); + rMask.DrawPolyPolygon(aMask); + + // dump buffer to outdev + aBufferDevice.paint(); +} + +// modified color group. Force output to unified color. +void VclProcessor2D::RenderModifiedColorPrimitive2D( + const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate) +{ + if (!rModifiedCandidate.getChildren().empty()) + { + maBColorModifierStack.push(rModifiedCandidate.getColorModifier()); + process(rModifiedCandidate.getChildren()); + maBColorModifierStack.pop(); + } +} + +// unified sub-transparence. Draw to VDev first. +void VclProcessor2D::RenderUnifiedTransparencePrimitive2D( + const primitive2d::UnifiedTransparencePrimitive2D& rTransCandidate) +{ + if (rTransCandidate.getChildren().empty()) + return; + + if (0.0 == rTransCandidate.getTransparence()) + { + // no transparence used, so just use the content + process(rTransCandidate.getChildren()); + } + else if (rTransCandidate.getTransparence() > 0.0 && rTransCandidate.getTransparence() < 1.0) + { + // transparence is in visible range + basegfx::B2DRange aRange(rTransCandidate.getChildren().getB2DRange(getViewInformation2D())); + aRange.transform(maCurrentTransformation); + impBufferDevice aBufferDevice(*mpOutputDevice, aRange); + + if (aBufferDevice.isVisible()) + { + // remember last OutDev and set to content + OutputDevice* pLastOutputDevice = mpOutputDevice; + mpOutputDevice = &aBufferDevice.getContent(); + + // paint content to it + process(rTransCandidate.getChildren()); + + // back to old OutDev + mpOutputDevice = pLastOutputDevice; + + // dump buffer to outdev using given transparence + aBufferDevice.paint(rTransCandidate.getTransparence()); + } + } +} + +// sub-transparence group. Draw to VDev first. +void VclProcessor2D::RenderTransparencePrimitive2D( + const primitive2d::TransparencePrimitive2D& rTransCandidate) +{ + if (rTransCandidate.getChildren().empty()) + return; + + basegfx::B2DRange aRange(rTransCandidate.getChildren().getB2DRange(getViewInformation2D())); + aRange.transform(maCurrentTransformation); + impBufferDevice aBufferDevice(*mpOutputDevice, aRange); + + if (!aBufferDevice.isVisible()) + return; + + // remember last OutDev and set to content + OutputDevice* pLastOutputDevice = mpOutputDevice; + mpOutputDevice = &aBufferDevice.getContent(); + + // paint content to it + process(rTransCandidate.getChildren()); + + // set to mask + mpOutputDevice = &aBufferDevice.getTransparence(); + + // when painting transparence masks, reset the color stack + basegfx::BColorModifierStack aLastBColorModifierStack(maBColorModifierStack); + maBColorModifierStack = basegfx::BColorModifierStack(); + + // paint mask to it (always with transparence intensities, evtl. with AA) + process(rTransCandidate.getTransparence()); + + // back to old color stack + maBColorModifierStack = aLastBColorModifierStack; + + // back to old OutDev + mpOutputDevice = pLastOutputDevice; + + // dump buffer to outdev + aBufferDevice.paint(); +} + +// transform group. +void VclProcessor2D::RenderTransformPrimitive2D( + const primitive2d::TransformPrimitive2D& rTransformCandidate) +{ + // remember current transformation and ViewInformation + const basegfx::B2DHomMatrix aLastCurrentTransformation(maCurrentTransformation); + const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); + + // create new transformations for CurrentTransformation + // and for local ViewInformation2D + maCurrentTransformation = maCurrentTransformation * rTransformCandidate.getTransformation(); + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setObjectTransformation(getViewInformation2D().getObjectTransformation() + * rTransformCandidate.getTransformation()); + updateViewInformation(aViewInformation2D); + + // process content + process(rTransformCandidate.getChildren()); + + // restore transformations + maCurrentTransformation = aLastCurrentTransformation; + updateViewInformation(aLastViewInformation2D); +} + +// new XDrawPage for ViewInformation2D +void VclProcessor2D::RenderPagePreviewPrimitive2D( + const primitive2d::PagePreviewPrimitive2D& rPagePreviewCandidate) +{ + // remember current transformation and ViewInformation + const geometry::ViewInformation2D aLastViewInformation2D(getViewInformation2D()); + + // create new local ViewInformation2D + geometry::ViewInformation2D aViewInformation2D(getViewInformation2D()); + aViewInformation2D.setVisualizedPage(rPagePreviewCandidate.getXDrawPage()); + updateViewInformation(aViewInformation2D); + + // process decomposed content + process(rPagePreviewCandidate); + + // restore transformations + updateViewInformation(aLastViewInformation2D); +} + +// marker +void VclProcessor2D::RenderMarkerArrayPrimitive2D( + const primitive2d::MarkerArrayPrimitive2D& rMarkArrayCandidate) +{ + // get data + const std::vector<basegfx::B2DPoint>& rPositions = rMarkArrayCandidate.getPositions(); + const sal_uInt32 nCount(rPositions.size()); + + if (!nCount || rMarkArrayCandidate.getMarker().IsEmpty()) + return; + + // get pixel size + const BitmapEx& rMarker(rMarkArrayCandidate.getMarker()); + const Size aBitmapSize(rMarker.GetSizePixel()); + + if (!(aBitmapSize.Width() && aBitmapSize.Height())) + return; + + // get discrete half size + const basegfx::B2DVector aDiscreteHalfSize((aBitmapSize.getWidth() - 1.0) * 0.5, + (aBitmapSize.getHeight() - 1.0) * 0.5); + const bool bWasEnabled(mpOutputDevice->IsMapModeEnabled()); + + // do not forget evtl. moved origin in target device MapMode when + // switching it off; it would be missing and lead to wrong positions. + // All his could be done using logic sizes and coordinates, too, but + // we want a 1:1 bitmap rendering here, so it's more safe and faster + // to work with switching off MapMode usage completely. + const Point aOrigin(mpOutputDevice->GetMapMode().GetOrigin()); + + mpOutputDevice->EnableMapMode(false); + + for (auto const& pos : rPositions) + { + const basegfx::B2DPoint aDiscreteTopLeft((maCurrentTransformation * pos) + - aDiscreteHalfSize); + const Point aDiscretePoint(basegfx::fround(aDiscreteTopLeft.getX()), + basegfx::fround(aDiscreteTopLeft.getY())); + + mpOutputDevice->DrawBitmapEx(aDiscretePoint + aOrigin, rMarker); + } + + mpOutputDevice->EnableMapMode(bWasEnabled); +} + +// point +void VclProcessor2D::RenderPointArrayPrimitive2D( + const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate) +{ + const std::vector<basegfx::B2DPoint>& rPositions = rPointArrayCandidate.getPositions(); + const basegfx::BColor aRGBColor( + maBColorModifierStack.getModifiedColor(rPointArrayCandidate.getRGBColor())); + const Color aVCLColor(aRGBColor); + + for (auto const& pos : rPositions) + { + const basegfx::B2DPoint aViewPosition(maCurrentTransformation * pos); + const Point aPos(basegfx::fround(aViewPosition.getX()), + basegfx::fround(aViewPosition.getY())); + + mpOutputDevice->DrawPixel(aPos, aVCLColor); + } +} + +void VclProcessor2D::RenderPolygonStrokePrimitive2D( + const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate) +{ + // #i101491# method restructured to clearly use the DrawPolyLine + // calls starting from a defined line width + const attribute::LineAttribute& rLineAttribute = rPolygonStrokeCandidate.getLineAttribute(); + const double fLineWidth(rLineAttribute.getWidth()); + bool bDone(false); + + if (basegfx::fTools::more(fLineWidth, 0.0)) + { + const basegfx::B2DVector aDiscreteUnit(maCurrentTransformation + * basegfx::B2DVector(fLineWidth, 0.0)); + const double fDiscreteLineWidth(aDiscreteUnit.getLength()); + const attribute::StrokeAttribute& rStrokeAttribute + = rPolygonStrokeCandidate.getStrokeAttribute(); + const basegfx::BColor aHairlineColor( + maBColorModifierStack.getModifiedColor(rLineAttribute.getColor())); + basegfx::B2DPolyPolygon aHairlinePolyPolygon; + + mpOutputDevice->SetLineColor(Color(aHairlineColor)); + mpOutputDevice->SetFillColor(); + + if (0.0 == rStrokeAttribute.getFullDotDashLen()) + { + // no line dashing, just copy + aHairlinePolyPolygon.append(rPolygonStrokeCandidate.getB2DPolygon()); + } + else + { + // else apply LineStyle + basegfx::utils::applyLineDashing( + rPolygonStrokeCandidate.getB2DPolygon(), rStrokeAttribute.getDotDashArray(), + &aHairlinePolyPolygon, nullptr, rStrokeAttribute.getFullDotDashLen()); + } + + const sal_uInt32 nCount(aHairlinePolyPolygon.count()); + + if (nCount) + { + const bool bAntiAliased(getViewInformation2D().getUseAntiAliasing()); + aHairlinePolyPolygon.transform(maCurrentTransformation); + + if (bAntiAliased) + { + if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 1.0)) + { + // line in range ]0.0 .. 1.0[ + // paint as simple hairline + for (sal_uInt32 a(0); a < nCount; a++) + { + mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a), 0.0); + } + + bDone = true; + } + else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 2.0)) + { + // line in range [1.0 .. 2.0[ + // paint as 2x2 with dynamic line distance + basegfx::B2DHomMatrix aMat; + const double fDistance(fDiscreteLineWidth - 1.0); + const double fHalfDistance(fDistance * 0.5); + + for (sal_uInt32 a(0); a < nCount; a++) + { + basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a)); + + aMat.set(0, 2, -fHalfDistance); + aMat.set(1, 2, -fHalfDistance); + aCandidate.transform(aMat); + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, fDistance); + aMat.set(1, 2, 0.0); + aCandidate.transform(aMat); + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, 0.0); + aMat.set(1, 2, fDistance); + aCandidate.transform(aMat); + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, -fDistance); + aMat.set(1, 2, 0.0); + aCandidate.transform(aMat); + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + } + + bDone = true; + } + else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 3.0)) + { + // line in range [2.0 .. 3.0] + // paint as cross in a 3x3 with dynamic line distance + basegfx::B2DHomMatrix aMat; + const double fDistance((fDiscreteLineWidth - 1.0) * 0.5); + + for (sal_uInt32 a(0); a < nCount; a++) + { + basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a)); + + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, -fDistance); + aMat.set(1, 2, 0.0); + aCandidate.transform(aMat); + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, fDistance); + aMat.set(1, 2, -fDistance); + aCandidate.transform(aMat); + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, fDistance); + aMat.set(1, 2, fDistance); + aCandidate.transform(aMat); + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, -fDistance); + aMat.set(1, 2, fDistance); + aCandidate.transform(aMat); + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + } + + bDone = true; + } + else + { + // #i101491# line width above 3.0 + } + } + else + { + if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 1.5)) + { + // line width below 1.5, draw the basic hairline polygon + for (sal_uInt32 a(0); a < nCount; a++) + { + mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a), 0.0); + } + + bDone = true; + } + else if (basegfx::fTools::lessOrEqual(fDiscreteLineWidth, 2.5)) + { + // line width is in range ]1.5 .. 2.5], use four hairlines + // drawn in a square + for (sal_uInt32 a(0); a < nCount; a++) + { + basegfx::B2DPolygon aCandidate(aHairlinePolyPolygon.getB2DPolygon(a)); + basegfx::B2DHomMatrix aMat; + + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, 1.0); + aMat.set(1, 2, 0.0); + aCandidate.transform(aMat); + + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, 0.0); + aMat.set(1, 2, 1.0); + aCandidate.transform(aMat); + + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + + aMat.set(0, 2, -1.0); + aMat.set(1, 2, 0.0); + aCandidate.transform(aMat); + + mpOutputDevice->DrawPolyLine(aCandidate, 0.0); + } + + bDone = true; + } + else + { + // #i101491# line width is above 2.5 + } + } + + if (!bDone && rPolygonStrokeCandidate.getB2DPolygon().count() > 1000) + { + // #i101491# If the polygon complexity uses more than a given amount, do + // use OutputDevice::DrawPolyLine directly; this will avoid buffering all + // decompositions in primitives (memory) and fallback to old line painting + // for very complex polygons, too + for (sal_uInt32 a(0); a < nCount; a++) + { + mpOutputDevice->DrawPolyLine(aHairlinePolyPolygon.getB2DPolygon(a), + fDiscreteLineWidth, rLineAttribute.getLineJoin(), + rLineAttribute.getLineCap(), + rLineAttribute.getMiterMinimumAngle()); + } + + bDone = true; + } + } + } + + if (!bDone) + { + // remember that we enter a PolygonStrokePrimitive2D decomposition, + // used for AA thick line drawing + mnPolygonStrokePrimitive2D++; + + // line width is big enough for standard filled polygon visualisation or zero + process(rPolygonStrokeCandidate); + + // leave PolygonStrokePrimitive2D + mnPolygonStrokePrimitive2D--; + } +} + +void VclProcessor2D::RenderEpsPrimitive2D(const primitive2d::EpsPrimitive2D& rEpsPrimitive2D) +{ + // The new decomposition of Metafiles made it necessary to add an Eps + // primitive to handle embedded Eps data. On some devices, this can be + // painted directly (mac, printer). + // To be able to handle the replacement correctly, i need to handle it myself + // since DrawEPS will not be able e.g. to rotate the replacement. To be able + // to do that, i added a boolean return to OutputDevice::DrawEPS(..) + // to know when EPS was handled directly already. + basegfx::B2DRange aRange(0.0, 0.0, 1.0, 1.0); + aRange.transform(maCurrentTransformation * rEpsPrimitive2D.getEpsTransform()); + + if (aRange.isEmpty()) + return; + + const ::tools::Rectangle aRectangle(static_cast<sal_Int32>(floor(aRange.getMinX())), + static_cast<sal_Int32>(floor(aRange.getMinY())), + static_cast<sal_Int32>(ceil(aRange.getMaxX())), + static_cast<sal_Int32>(ceil(aRange.getMaxY()))); + + if (aRectangle.IsEmpty()) + return; + + bool bWillReallyRender = mpOutputDevice->IsDeviceOutputNecessary(); + // try to paint EPS directly without fallback visualisation + const bool bEPSPaintedDirectly + = bWillReallyRender + && mpOutputDevice->DrawEPS(aRectangle.TopLeft(), aRectangle.GetSize(), + rEpsPrimitive2D.getGfxLink()); + + if (!bEPSPaintedDirectly) + { + // use the decomposition which will correctly handle the + // fallback visualisation using full transformation (e.g. rotation) + process(rEpsPrimitive2D); + } +} + +void VclProcessor2D::RenderSvgLinearAtomPrimitive2D( + const primitive2d::SvgLinearAtomPrimitive2D& rCandidate) +{ + const double fDelta(rCandidate.getOffsetB() - rCandidate.getOffsetA()); + + if (!basegfx::fTools::more(fDelta, 0.0)) + return; + + const basegfx::BColor aColorA(maBColorModifierStack.getModifiedColor(rCandidate.getColorA())); + const basegfx::BColor aColorB(maBColorModifierStack.getModifiedColor(rCandidate.getColorB())); + + // calculate discrete unit in WorldCoordinates; use diagonal (1.0, 1.0) and divide by sqrt(2) + const basegfx::B2DVector aDiscreteVector( + getViewInformation2D().getInverseObjectToViewTransformation() + * basegfx::B2DVector(1.0, 1.0)); + const double fDiscreteUnit(aDiscreteVector.getLength() * (1.0 / M_SQRT2)); + + // use color distance and discrete lengths to calculate step count + const sal_uInt32 nSteps(calculateStepsForSvgGradient(aColorA, aColorB, fDelta, fDiscreteUnit)); + + // switch off line painting + mpOutputDevice->SetLineColor(); + + // prepare polygon in needed width at start position (with discrete overlap) + const basegfx::B2DPolygon aPolygon(basegfx::utils::createPolygonFromRect( + basegfx::B2DRange(rCandidate.getOffsetA() - fDiscreteUnit, 0.0, + rCandidate.getOffsetA() + (fDelta / nSteps) + fDiscreteUnit, 1.0))); + + // prepare loop ([0.0 .. 1.0[) + double fUnitScale(0.0); + const double fUnitStep(1.0 / nSteps); + + // loop and paint + for (sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep) + { + basegfx::B2DPolygon aNew(aPolygon); + + aNew.transform(maCurrentTransformation + * basegfx::utils::createTranslateB2DHomMatrix(fDelta * fUnitScale, 0.0)); + mpOutputDevice->SetFillColor(Color(basegfx::interpolate(aColorA, aColorB, fUnitScale))); + mpOutputDevice->DrawPolyPolygon(basegfx::B2DPolyPolygon(aNew)); + } +} + +void VclProcessor2D::RenderSvgRadialAtomPrimitive2D( + const primitive2d::SvgRadialAtomPrimitive2D& rCandidate) +{ + const double fDeltaScale(rCandidate.getScaleB() - rCandidate.getScaleA()); + + if (!basegfx::fTools::more(fDeltaScale, 0.0)) + return; + + const basegfx::BColor aColorA(maBColorModifierStack.getModifiedColor(rCandidate.getColorA())); + const basegfx::BColor aColorB(maBColorModifierStack.getModifiedColor(rCandidate.getColorB())); + + // calculate discrete unit in WorldCoordinates; use diagonal (1.0, 1.0) and divide by sqrt(2) + const basegfx::B2DVector aDiscreteVector( + getViewInformation2D().getInverseObjectToViewTransformation() + * basegfx::B2DVector(1.0, 1.0)); + const double fDiscreteUnit(aDiscreteVector.getLength() * (1.0 / M_SQRT2)); + + // use color distance and discrete lengths to calculate step count + const sal_uInt32 nSteps( + calculateStepsForSvgGradient(aColorA, aColorB, fDeltaScale, fDiscreteUnit)); + + // switch off line painting + mpOutputDevice->SetLineColor(); + + // prepare loop ([0.0 .. 1.0[, full polygons, no polypolygons with holes) + double fUnitScale(0.0); + const double fUnitStep(1.0 / nSteps); + + for (sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep) + { + basegfx::B2DHomMatrix aTransform; + const double fEndScale(rCandidate.getScaleB() - (fDeltaScale * fUnitScale)); + + if (rCandidate.isTranslateSet()) + { + const basegfx::B2DVector aTranslate(basegfx::interpolate( + rCandidate.getTranslateB(), rCandidate.getTranslateA(), fUnitScale)); + + aTransform = basegfx::utils::createScaleTranslateB2DHomMatrix( + fEndScale, fEndScale, aTranslate.getX(), aTranslate.getY()); + } + else + { + aTransform = basegfx::utils::createScaleB2DHomMatrix(fEndScale, fEndScale); + } + + basegfx::B2DPolygon aNew(basegfx::utils::createPolygonFromUnitCircle()); + + aNew.transform(maCurrentTransformation * aTransform); + mpOutputDevice->SetFillColor(Color(basegfx::interpolate(aColorB, aColorA, fUnitScale))); + mpOutputDevice->DrawPolyPolygon(basegfx::B2DPolyPolygon(aNew)); + } +} + +void VclProcessor2D::adaptLineToFillDrawMode() const +{ + const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode()); + + if (!(nOriginalDrawMode + & (DrawModeFlags::BlackLine | DrawModeFlags::GrayLine | DrawModeFlags::WhiteLine + | DrawModeFlags::SettingsLine))) + return; + + DrawModeFlags nAdaptedDrawMode(nOriginalDrawMode); + + if (nOriginalDrawMode & DrawModeFlags::BlackLine) + { + nAdaptedDrawMode |= DrawModeFlags::BlackFill; + } + else + { + nAdaptedDrawMode &= ~DrawModeFlags::BlackFill; + } + + if (nOriginalDrawMode & DrawModeFlags::GrayLine) + { + nAdaptedDrawMode |= DrawModeFlags::GrayFill; + } + else + { + nAdaptedDrawMode &= ~DrawModeFlags::GrayFill; + } + + if (nOriginalDrawMode & DrawModeFlags::WhiteLine) + { + nAdaptedDrawMode |= DrawModeFlags::WhiteFill; + } + else + { + nAdaptedDrawMode &= ~DrawModeFlags::WhiteFill; + } + + if (nOriginalDrawMode & DrawModeFlags::SettingsLine) + { + nAdaptedDrawMode |= DrawModeFlags::SettingsFill; + } + else + { + nAdaptedDrawMode &= ~DrawModeFlags::SettingsFill; + } + + mpOutputDevice->SetDrawMode(nAdaptedDrawMode); +} + +void VclProcessor2D::adaptTextToFillDrawMode() const +{ + const DrawModeFlags nOriginalDrawMode(mpOutputDevice->GetDrawMode()); + if (!(nOriginalDrawMode + & (DrawModeFlags::BlackText | DrawModeFlags::GrayText | DrawModeFlags::WhiteText + | DrawModeFlags::SettingsText))) + return; + + DrawModeFlags nAdaptedDrawMode(nOriginalDrawMode); + + if (nOriginalDrawMode & DrawModeFlags::BlackText) + { + nAdaptedDrawMode |= DrawModeFlags::BlackFill; + } + else + { + nAdaptedDrawMode &= ~DrawModeFlags::BlackFill; + } + + if (nOriginalDrawMode & DrawModeFlags::GrayText) + { + nAdaptedDrawMode |= DrawModeFlags::GrayFill; + } + else + { + nAdaptedDrawMode &= ~DrawModeFlags::GrayFill; + } + + if (nOriginalDrawMode & DrawModeFlags::WhiteText) + { + nAdaptedDrawMode |= DrawModeFlags::WhiteFill; + } + else + { + nAdaptedDrawMode &= ~DrawModeFlags::WhiteFill; + } + + if (nOriginalDrawMode & DrawModeFlags::SettingsText) + { + nAdaptedDrawMode |= DrawModeFlags::SettingsFill; + } + else + { + nAdaptedDrawMode &= ~DrawModeFlags::SettingsFill; + } + + mpOutputDevice->SetDrawMode(nAdaptedDrawMode); +} + +// process support + +VclProcessor2D::VclProcessor2D(const geometry::ViewInformation2D& rViewInformation, + OutputDevice& rOutDev, basegfx::BColorModifierStack aInitStack) + : BaseProcessor2D(rViewInformation) + , mpOutputDevice(&rOutDev) + , maBColorModifierStack(std::move(aInitStack)) + , mnPolygonStrokePrimitive2D(0) +{ + // set digit language, derived from SvtCTLOptions to have the correct + // number display for arabic/hindi numerals + rOutDev.SetDigitLanguage(drawinglayer::detail::getDigitLanguage()); +} + +VclProcessor2D::~VclProcessor2D() {} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/processor2d/vclprocessor2d.hxx b/drawinglayer/source/processor2d/vclprocessor2d.hxx new file mode 100644 index 0000000000..d2f0a69157 --- /dev/null +++ b/drawinglayer/source/processor2d/vclprocessor2d.hxx @@ -0,0 +1,123 @@ +/* -*- 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 . + */ + +#pragma once + +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/color/bcolormodifier.hxx> +#include <vcl/vclptr.hxx> + +class OutputDevice; + +namespace drawinglayer::primitive2d +{ +class TextSimplePortionPrimitive2D; +class PolygonHairlinePrimitive2D; +class BitmapPrimitive2D; +class FillGraphicPrimitive2D; +class PolyPolygonGradientPrimitive2D; +class PolyPolygonGraphicPrimitive2D; +class MetafilePrimitive2D; +class MaskPrimitive2D; +class UnifiedTransparencePrimitive2D; +class TransparencePrimitive2D; +class TransformPrimitive2D; +class MarkerArrayPrimitive2D; +class PointArrayPrimitive2D; +class ModifiedColorPrimitive2D; +class PolygonStrokePrimitive2D; +class ControlPrimitive2D; +class PagePreviewPrimitive2D; +class EpsPrimitive2D; +class SvgLinearAtomPrimitive2D; +class SvgRadialAtomPrimitive2D; +} + +namespace drawinglayer::processor2d +{ +/** VclProcessor2D class + + This processor is the base class for VCL-Based processors. It has no + processBasePrimitive2D implementation and thus is not usable directly. + */ +class VclProcessor2D : public BaseProcessor2D +{ +protected: + // the destination OutDev + VclPtr<OutputDevice> mpOutputDevice; + + // the modifiedColorPrimitive stack + basegfx::BColorModifierStack maBColorModifierStack; + + // the current transformation. Since VCL pixel renderer transforms to pixels + // and VCL MetaFile renderer to World (logic) coordinates, the local + // ViewInformation2D cannot directly be used, but needs to be kept up to date + basegfx::B2DHomMatrix maCurrentTransformation; + + // stack value (increment and decrement) to count how deep we are in + // PolygonStrokePrimitive2D's decompositions (normally only one) + sal_uInt32 mnPolygonStrokePrimitive2D; + + // common VCL rendering support + void RenderTextSimpleOrDecoratedPortionPrimitive2D( + const primitive2d::TextSimplePortionPrimitive2D& rTextCandidate); + void RenderPolygonHairlinePrimitive2D( + const primitive2d::PolygonHairlinePrimitive2D& rPolygonCandidate, bool bPixelBased); + void RenderBitmapPrimitive2D(const primitive2d::BitmapPrimitive2D& rBitmapCandidate); + void + RenderFillGraphicPrimitive2D(const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate); + void RenderPolyPolygonGraphicPrimitive2D( + const primitive2d::PolyPolygonGraphicPrimitive2D& rPolygonCandidate); + void RenderMaskPrimitive2DPixel(const primitive2d::MaskPrimitive2D& rMaskCandidate); + void + RenderModifiedColorPrimitive2D(const primitive2d::ModifiedColorPrimitive2D& rModifiedCandidate); + void RenderUnifiedTransparencePrimitive2D( + const primitive2d::UnifiedTransparencePrimitive2D& rTransCandidate); + void RenderTransparencePrimitive2D(const primitive2d::TransparencePrimitive2D& rTransCandidate); + void RenderTransformPrimitive2D(const primitive2d::TransformPrimitive2D& rTransformCandidate); + void + RenderPagePreviewPrimitive2D(const primitive2d::PagePreviewPrimitive2D& rPagePreviewCandidate); + void + RenderMarkerArrayPrimitive2D(const primitive2d::MarkerArrayPrimitive2D& rMarkerArrayCandidate); + void + RenderPointArrayPrimitive2D(const primitive2d::PointArrayPrimitive2D& rPointArrayCandidate); + void RenderPolygonStrokePrimitive2D( + const primitive2d::PolygonStrokePrimitive2D& rPolygonStrokeCandidate); + void RenderEpsPrimitive2D(const primitive2d::EpsPrimitive2D& rEpsPrimitive2D); + void RenderSvgLinearAtomPrimitive2D(const primitive2d::SvgLinearAtomPrimitive2D& rCandidate); + void RenderSvgRadialAtomPrimitive2D(const primitive2d::SvgRadialAtomPrimitive2D& rCandidate); + + // DrawMode adaptation support + void adaptLineToFillDrawMode() const; + void adaptTextToFillDrawMode() const; + +public: + // constructor/destructor + VclProcessor2D(const geometry::ViewInformation2D& rViewInformation, OutputDevice& rOutDev, + basegfx::BColorModifierStack aInitStack = basegfx::BColorModifierStack()); + virtual ~VclProcessor2D() override; + +private: + bool RenderFillGraphicPrimitive2DImpl( + const primitive2d::FillGraphicPrimitive2D& rFillBitmapCandidate); +}; +} // end of namespace drawinglayer::processor2d + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |