diff options
Diffstat (limited to 'drawinglayer/source/primitive2d')
64 files changed, 12715 insertions, 0 deletions
diff --git a/drawinglayer/source/primitive2d/PolyPolygonColorPrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonColorPrimitive2D.cxx new file mode 100644 index 000000000..aa48a965e --- /dev/null +++ b/drawinglayer/source/primitive2d/PolyPolygonColorPrimitive2D.cxx @@ -0,0 +1,66 @@ +/* -*- 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/PolyPolygonColorPrimitive2D.hxx> + +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +PolyPolygonColorPrimitive2D::PolyPolygonColorPrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::BColor& rBColor) + : BasePrimitive2D() + , maPolyPolygon(rPolyPolygon) + , maBColor(rBColor) +{ +} + +bool PolyPolygonColorPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BasePrimitive2D::operator==(rPrimitive)) + { + const PolyPolygonColorPrimitive2D& rCompare + = static_cast<const PolyPolygonColorPrimitive2D&>(rPrimitive); + + return (getB2DPolyPolygon() == rCompare.getB2DPolyPolygon() + && getBColor() == rCompare.getBColor()); + } + + return false; +} + +basegfx::B2DRange PolyPolygonColorPrimitive2D::getB2DRange( + const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + // return range + return basegfx::utils::getRange(getB2DPolyPolygon()); +} + +// provide unique ID +sal_uInt32 PolyPolygonColorPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D; +} + +} // end drawinglayer::primitive2d namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/PolyPolygonGradientPrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonGradientPrimitive2D.cxx new file mode 100644 index 000000000..076436b40 --- /dev/null +++ b/drawinglayer/source/primitive2d/PolyPolygonGradientPrimitive2D.cxx @@ -0,0 +1,90 @@ +/* -*- 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/PolyPolygonGradientPrimitive2D.hxx> + +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +void PolyPolygonGradientPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + if (!getFillGradient().isDefault()) + { + // create SubSequence with FillGradientPrimitive2D + const basegfx::B2DRange aPolyPolygonRange(getB2DPolyPolygon().getB2DRange()); + FillGradientPrimitive2D* pNewGradient = new FillGradientPrimitive2D( + aPolyPolygonRange, getDefinitionRange(), getFillGradient()); + const Primitive2DReference xSubRef(pNewGradient); + const Primitive2DContainer aSubSequence{ xSubRef }; + + // create mask primitive + rContainer.push_back(new MaskPrimitive2D(getB2DPolyPolygon(), aSubSequence)); + } +} + +PolyPolygonGradientPrimitive2D::PolyPolygonGradientPrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, + const attribute::FillGradientAttribute& rFillGradient) + : BufferedDecompositionPrimitive2D() + , maPolyPolygon(rPolyPolygon) + , maDefinitionRange(rPolyPolygon.getB2DRange()) + , maFillGradient(rFillGradient) +{ +} + +PolyPolygonGradientPrimitive2D::PolyPolygonGradientPrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::B2DRange& rDefinitionRange, + const attribute::FillGradientAttribute& rFillGradient) + : BufferedDecompositionPrimitive2D() + , maPolyPolygon(rPolyPolygon) + , maDefinitionRange(rDefinitionRange) + , maFillGradient(rFillGradient) +{ +} + +bool PolyPolygonGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PolyPolygonGradientPrimitive2D& rCompare + = static_cast<const PolyPolygonGradientPrimitive2D&>(rPrimitive); + + return (getB2DPolyPolygon() == rCompare.getB2DPolyPolygon() + && getDefinitionRange() == rCompare.getDefinitionRange() + && getFillGradient() == rCompare.getFillGradient()); + } + + return false; +} + +// provide unique ID +sal_uInt32 PolyPolygonGradientPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D; +} + +} // end drawinglayer::primitive2d namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/PolyPolygonGraphicPrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonGraphicPrimitive2D.cxx new file mode 100644 index 000000000..2a1aefafa --- /dev/null +++ b/drawinglayer/source/primitive2d/PolyPolygonGraphicPrimitive2D.cxx @@ -0,0 +1,131 @@ +/* -*- 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/PolyPolygonGraphicPrimitive2D.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <vcl/graph.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +void PolyPolygonGraphicPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + if (getFillGraphic().isDefault()) + return; + + const Graphic& rGraphic = getFillGraphic().getGraphic(); + const GraphicType aType(rGraphic.GetType()); + + // is there a bitmap or a metafile (do we have content)? + if (!(GraphicType::Bitmap == aType || GraphicType::GdiMetafile == aType)) + return; + + const Size aPrefSize(rGraphic.GetPrefSize()); + + // does content have a size? + if (!(aPrefSize.Width() && aPrefSize.Height())) + return; + + // create SubSequence with FillGraphicPrimitive2D based on polygon range + const basegfx::B2DRange aOutRange(getB2DPolyPolygon().getB2DRange()); + const basegfx::B2DHomMatrix aNewObjectTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix(aOutRange.getRange(), + aOutRange.getMinimum())); + Primitive2DReference xSubRef; + + if (aOutRange != getDefinitionRange()) + { + // we want to paint (tiled) content which is defined relative to DefinitionRange + // with the same tiling and offset(s) in the target range of the geometry (the + // polygon). The range given in the local FillGraphicAttribute defines the position + // of the graphic in unit coordinates relative to the DefinitionRange. Transform + // this using DefinitionRange to get to the global definition and then with the + // inverse transformation from the target range to go to unit coordinates relative + // to that target coordinate system. + basegfx::B2DRange aAdaptedRange(getFillGraphic().getGraphicRange()); + + const basegfx::B2DHomMatrix aFromDefinitionRangeToGlobal( + basegfx::utils::createScaleTranslateB2DHomMatrix(getDefinitionRange().getRange(), + getDefinitionRange().getMinimum())); + + aAdaptedRange.transform(aFromDefinitionRangeToGlobal); + + basegfx::B2DHomMatrix aFromGlobalToOutRange( + basegfx::utils::createScaleTranslateB2DHomMatrix(aOutRange.getRange(), + aOutRange.getMinimum())); + aFromGlobalToOutRange.invert(); + + aAdaptedRange.transform(aFromGlobalToOutRange); + + const drawinglayer::attribute::FillGraphicAttribute aAdaptedFillGraphicAttribute( + getFillGraphic().getGraphic(), aAdaptedRange, getFillGraphic().getTiling(), + getFillGraphic().getOffsetX(), getFillGraphic().getOffsetY()); + + xSubRef = new FillGraphicPrimitive2D(aNewObjectTransform, aAdaptedFillGraphicAttribute); + } + else + { + xSubRef = new FillGraphicPrimitive2D(aNewObjectTransform, getFillGraphic()); + } + + // embed to mask primitive + rContainer.push_back(new MaskPrimitive2D(getB2DPolyPolygon(), Primitive2DContainer{ xSubRef })); +} + +PolyPolygonGraphicPrimitive2D::PolyPolygonGraphicPrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::B2DRange& rDefinitionRange, + const attribute::FillGraphicAttribute& rFillGraphic) + : BufferedDecompositionPrimitive2D() + , maPolyPolygon(rPolyPolygon) + , maDefinitionRange(rDefinitionRange) + , maFillGraphic(rFillGraphic) +{ +} + +bool PolyPolygonGraphicPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PolyPolygonGraphicPrimitive2D& rCompare + = static_cast<const PolyPolygonGraphicPrimitive2D&>(rPrimitive); + + return (getB2DPolyPolygon() == rCompare.getB2DPolyPolygon() + && getDefinitionRange() == rCompare.getDefinitionRange() + && getFillGraphic() == rCompare.getFillGraphic()); + } + + return false; +} + +// provide unique ID +sal_uInt32 PolyPolygonGraphicPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D; +} + +} // end drawinglayer::primitive2d namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/PolyPolygonHairlinePrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonHairlinePrimitive2D.cxx new file mode 100644 index 000000000..e4c73f919 --- /dev/null +++ b/drawinglayer/source/primitive2d/PolyPolygonHairlinePrimitive2D.cxx @@ -0,0 +1,83 @@ +/* -*- 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/PolyPolygonHairlinePrimitive2D.hxx> + +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +void PolyPolygonHairlinePrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + const basegfx::B2DPolyPolygon aPolyPolygon(getB2DPolyPolygon()); + const sal_uInt32 nCount(aPolyPolygon.count()); + + if (nCount) + { + for (sal_uInt32 a(0); a < nCount; a++) + { + rContainer.push_back( + new PolygonHairlinePrimitive2D(aPolyPolygon.getB2DPolygon(a), getBColor())); + } + } +} + +PolyPolygonHairlinePrimitive2D::PolyPolygonHairlinePrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::BColor& rBColor) + : BufferedDecompositionPrimitive2D() + , maPolyPolygon(rPolyPolygon) + , maBColor(rBColor) +{ +} + +bool PolyPolygonHairlinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PolyPolygonHairlinePrimitive2D& rCompare + = static_cast<const PolyPolygonHairlinePrimitive2D&>(rPrimitive); + + return (getB2DPolyPolygon() == rCompare.getB2DPolyPolygon() + && getBColor() == rCompare.getBColor()); + } + + return false; +} + +basegfx::B2DRange PolyPolygonHairlinePrimitive2D::getB2DRange( + const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + // return range + return basegfx::utils::getRange(getB2DPolyPolygon()); +} + +// provide unique ID +sal_uInt32 PolyPolygonHairlinePrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYPOLYGONHAIRLINEPRIMITIVE2D; +} + +} // end drawinglayer::primitive2d namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/PolyPolygonHatchPrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonHatchPrimitive2D.cxx new file mode 100644 index 000000000..ad85c02f2 --- /dev/null +++ b/drawinglayer/source/primitive2d/PolyPolygonHatchPrimitive2D.cxx @@ -0,0 +1,93 @@ +/* -*- 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/PolyPolygonHatchPrimitive2D.hxx> + +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/fillhatchprimitive2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +void PolyPolygonHatchPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + if (!getFillHatch().isDefault()) + { + // create SubSequence with FillHatchPrimitive2D + const basegfx::B2DRange aPolyPolygonRange(getB2DPolyPolygon().getB2DRange()); + FillHatchPrimitive2D* pNewHatch = new FillHatchPrimitive2D( + aPolyPolygonRange, getDefinitionRange(), getBackgroundColor(), getFillHatch()); + const Primitive2DReference xSubRef(pNewHatch); + const Primitive2DContainer aSubSequence{ xSubRef }; + + // create mask primitive + rContainer.push_back(new MaskPrimitive2D(getB2DPolyPolygon(), aSubSequence)); + } +} + +PolyPolygonHatchPrimitive2D::PolyPolygonHatchPrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::BColor& rBackgroundColor, + const attribute::FillHatchAttribute& rFillHatch) + : BufferedDecompositionPrimitive2D() + , maPolyPolygon(rPolyPolygon) + , maDefinitionRange(rPolyPolygon.getB2DRange()) + , maBackgroundColor(rBackgroundColor) + , maFillHatch(rFillHatch) +{ +} + +PolyPolygonHatchPrimitive2D::PolyPolygonHatchPrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::B2DRange& rDefinitionRange, + const basegfx::BColor& rBackgroundColor, const attribute::FillHatchAttribute& rFillHatch) + : BufferedDecompositionPrimitive2D() + , maPolyPolygon(rPolyPolygon) + , maDefinitionRange(rDefinitionRange) + , maBackgroundColor(rBackgroundColor) + , maFillHatch(rFillHatch) +{ +} + +bool PolyPolygonHatchPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PolyPolygonHatchPrimitive2D& rCompare + = static_cast<const PolyPolygonHatchPrimitive2D&>(rPrimitive); + + return (getB2DPolyPolygon() == rCompare.getB2DPolyPolygon() + && getDefinitionRange() == rCompare.getDefinitionRange() + && getBackgroundColor() == rCompare.getBackgroundColor() + && getFillHatch() == rCompare.getFillHatch()); + } + + return false; +} + +// provide unique ID +sal_uInt32 PolyPolygonHatchPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D; +} + +} // end drawinglayer::primitive2d namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/PolyPolygonMarkerPrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonMarkerPrimitive2D.cxx new file mode 100644 index 000000000..b545ec946 --- /dev/null +++ b/drawinglayer/source/primitive2d/PolyPolygonMarkerPrimitive2D.cxx @@ -0,0 +1,89 @@ +/* -*- 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/PolyPolygonMarkerPrimitive2D.hxx> + +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +void PolyPolygonMarkerPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + const basegfx::B2DPolyPolygon aPolyPolygon(getB2DPolyPolygon()); + const sal_uInt32 nCount(aPolyPolygon.count()); + + if (nCount) + { + for (sal_uInt32 a(0); a < nCount; a++) + { + rContainer.push_back(new PolygonMarkerPrimitive2D(aPolyPolygon.getB2DPolygon(a), + getRGBColorA(), getRGBColorB(), + getDiscreteDashLength())); + } + } +} + +PolyPolygonMarkerPrimitive2D::PolyPolygonMarkerPrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::BColor& rRGBColorA, + const basegfx::BColor& rRGBColorB, double fDiscreteDashLength) + : BufferedDecompositionPrimitive2D() + , maPolyPolygon(rPolyPolygon) + , maRGBColorA(rRGBColorA) + , maRGBColorB(rRGBColorB) + , mfDiscreteDashLength(fDiscreteDashLength) +{ +} + +bool PolyPolygonMarkerPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PolyPolygonMarkerPrimitive2D& rCompare + = static_cast<const PolyPolygonMarkerPrimitive2D&>(rPrimitive); + + return (getB2DPolyPolygon() == rCompare.getB2DPolyPolygon() + && getRGBColorA() == rCompare.getRGBColorA() + && getRGBColorB() == rCompare.getRGBColorB() + && getDiscreteDashLength() == rCompare.getDiscreteDashLength()); + } + + return false; +} + +basegfx::B2DRange PolyPolygonMarkerPrimitive2D::getB2DRange( + const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + // return range + return basegfx::utils::getRange(getB2DPolyPolygon()); +} + +// provide unique ID +sal_uInt32 PolyPolygonMarkerPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYPOLYGONMARKERPRIMITIVE2D; +} + +} // end drawinglayer::primitive2d namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/PolyPolygonSelectionPrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonSelectionPrimitive2D.cxx new file mode 100644 index 000000000..7be684c2f --- /dev/null +++ b/drawinglayer/source/primitive2d/PolyPolygonSelectionPrimitive2D.cxx @@ -0,0 +1,126 @@ +/* -*- 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/PolyPolygonSelectionPrimitive2D.hxx> + +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonStrokePrimitive2D.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +void PolyPolygonSelectionPrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + if (getTransparence() >= 1.0 || !getB2DPolyPolygon().count()) + return; + + Primitive2DContainer aRetval; + + if (getFill() && getB2DPolyPolygon().isClosed()) + { + // create fill primitive + const Primitive2DReference aFill( + new PolyPolygonColorPrimitive2D(getB2DPolyPolygon(), getColor())); + + aRetval = Primitive2DContainer{ aFill }; + } + + if (getDiscreteGrow() > 0.0) + { + const attribute::LineAttribute aLineAttribute(getColor(), + getDiscreteGrow() * getDiscreteUnit() * 2.0); + const Primitive2DReference aFatLine( + new PolyPolygonStrokePrimitive2D(getB2DPolyPolygon(), aLineAttribute)); + + aRetval.push_back(aFatLine); + } + + // embed filled to transparency (if used) + if (!aRetval.empty() && getTransparence() > 0.0) + { + const Primitive2DReference aTrans( + new UnifiedTransparencePrimitive2D(aRetval, getTransparence())); + + aRetval = Primitive2DContainer{ aTrans }; + } + + rContainer.insert(rContainer.end(), aRetval.begin(), aRetval.end()); +} + +PolyPolygonSelectionPrimitive2D::PolyPolygonSelectionPrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const basegfx::BColor& rColor, + double fTransparence, double fDiscreteGrow, bool bFill) + : DiscreteMetricDependentPrimitive2D() + , maPolyPolygon(rPolyPolygon) + , maColor(rColor) + , mfTransparence(fTransparence) + , mfDiscreteGrow(fabs(fDiscreteGrow)) + , mbFill(bFill) +{ +} + +bool PolyPolygonSelectionPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (DiscreteMetricDependentPrimitive2D::operator==(rPrimitive)) + { + const PolyPolygonSelectionPrimitive2D& rCompare + = static_cast<const PolyPolygonSelectionPrimitive2D&>(rPrimitive); + + return ( + getB2DPolyPolygon() == rCompare.getB2DPolyPolygon() && getColor() == rCompare.getColor() + && getTransparence() == rCompare.getTransparence() + && getDiscreteGrow() == rCompare.getDiscreteGrow() && getFill() == rCompare.getFill()); + } + + return false; +} + +basegfx::B2DRange PolyPolygonSelectionPrimitive2D::getB2DRange( + const geometry::ViewInformation2D& rViewInformation) const +{ + basegfx::B2DRange aRetval(basegfx::utils::getRange(getB2DPolyPolygon())); + + if (getDiscreteGrow() > 0.0) + { + // get the current DiscreteUnit (not sure if getDiscreteUnit() is updated here, better go safe way) + const double fDiscreteUnit( + (rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0)) + .getLength()); + + aRetval.grow(fDiscreteUnit * getDiscreteGrow()); + } + + return aRetval; +} + +// provide unique ID +sal_uInt32 PolyPolygonSelectionPrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYPOLYGONSELECTIONPRIMITIVE2D; +} + +} // end drawinglayer::primitive2d namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/PolyPolygonStrokePrimitive2D.cxx b/drawinglayer/source/primitive2d/PolyPolygonStrokePrimitive2D.cxx new file mode 100644 index 000000000..53abec113 --- /dev/null +++ b/drawinglayer/source/primitive2d/PolyPolygonStrokePrimitive2D.cxx @@ -0,0 +1,103 @@ +/* -*- 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/PolyPolygonStrokePrimitive2D.hxx> + +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +void PolyPolygonStrokePrimitive2D::create2DDecomposition( + Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + const basegfx::B2DPolyPolygon aPolyPolygon(getB2DPolyPolygon()); + const sal_uInt32 nCount(aPolyPolygon.count()); + + if (nCount) + { + for (sal_uInt32 a(0); a < nCount; a++) + { + rContainer.push_back(new PolygonStrokePrimitive2D( + aPolyPolygon.getB2DPolygon(a), getLineAttribute(), getStrokeAttribute())); + } + } +} + +PolyPolygonStrokePrimitive2D::PolyPolygonStrokePrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const attribute::LineAttribute& rLineAttribute, + const attribute::StrokeAttribute& rStrokeAttribute) + : BufferedDecompositionPrimitive2D() + , maPolyPolygon(rPolyPolygon) + , maLineAttribute(rLineAttribute) + , maStrokeAttribute(rStrokeAttribute) +{ +} + +PolyPolygonStrokePrimitive2D::PolyPolygonStrokePrimitive2D( + const basegfx::B2DPolyPolygon& rPolyPolygon, const attribute::LineAttribute& rLineAttribute) + : BufferedDecompositionPrimitive2D() + , maPolyPolygon(rPolyPolygon) + , maLineAttribute(rLineAttribute) + , maStrokeAttribute() +{ +} + +bool PolyPolygonStrokePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PolyPolygonStrokePrimitive2D& rCompare + = static_cast<const PolyPolygonStrokePrimitive2D&>(rPrimitive); + + return (getB2DPolyPolygon() == rCompare.getB2DPolyPolygon() + && getLineAttribute() == rCompare.getLineAttribute() + && getStrokeAttribute() == rCompare.getStrokeAttribute()); + } + + return false; +} + +basegfx::B2DRange PolyPolygonStrokePrimitive2D::getB2DRange( + const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + // get range of it (subdivided) + basegfx::B2DRange aRetval(basegfx::utils::getRange(getB2DPolyPolygon())); + + // if width, grow by line width + if (getLineAttribute().getWidth()) + { + aRetval.grow(getLineAttribute().getWidth() / 2.0); + } + + return aRetval; +} + +// provide unique ID +sal_uInt32 PolyPolygonStrokePrimitive2D::getPrimitive2DID() const +{ + return PRIMITIVE2D_ID_POLYPOLYGONSTROKEPRIMITIVE2D; +} + +} // end drawinglayer::primitive2d namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/Primitive2DContainer.cxx b/drawinglayer/source/primitive2d/Primitive2DContainer.cxx new file mode 100644 index 000000000..8f1e7d31e --- /dev/null +++ b/drawinglayer/source/primitive2d/Primitive2DContainer.cxx @@ -0,0 +1,129 @@ +/* -*- 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 <drawinglayer/primitive2d/Primitive2DContainer.hxx> +#include <drawinglayer/primitive2d/baseprimitive2d.hxx> +#include <drawinglayer/primitive2d/Tools.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <basegfx/utils/canvastools.hxx> + +using namespace css; + +namespace drawinglayer::primitive2d +{ +Primitive2DContainer Primitive2DContainer::maybeInvert(bool bInvert) const +{ + const sal_uInt32 nSize(size()); + Primitive2DContainer aRetval; + + aRetval.resize(nSize); + + for (sal_uInt32 a(0); a < nSize; a++) + { + aRetval[bInvert ? nSize - 1 - a : a] = (*this)[a]; + } + + // all entries taken over to Uno References as owners. To avoid + // errors with users of this mechanism to delete pointers to BasePrimitive2D + // itself, clear given vector + const_cast<Primitive2DContainer&>(*this).clear(); + + return aRetval; +} + +// get B2DRange from a given Primitive2DSequence +basegfx::B2DRange +Primitive2DContainer::getB2DRange(const geometry::ViewInformation2D& aViewInformation) const +{ + basegfx::B2DRange aRetval; + + if (!empty()) + { + const sal_Int32 nCount(size()); + + for (sal_Int32 a(0); a < nCount; a++) + { + aRetval.expand(getB2DRangeFromPrimitive2DReference((*this)[a], aViewInformation)); + } + } + + return aRetval; +} + +bool Primitive2DContainer::operator==(const Primitive2DContainer& rB) const +{ + const bool bAHasElements(!empty()); + + if (bAHasElements != !rB.empty()) + { + return false; + } + + if (!bAHasElements) + { + return true; + } + + const size_t nCount(size()); + + if (nCount != rB.size()) + { + return false; + } + + for (size_t a(0); a < nCount; a++) + { + if (!arePrimitive2DReferencesEqual((*this)[a], rB[a])) + { + return false; + } + } + + return true; +} + +Primitive2DContainer::~Primitive2DContainer() {} + +void Primitive2DContainer::append(const Primitive2DReference& rSource) { push_back(rSource); } + +void Primitive2DContainer::append(const Primitive2DContainer& rSource) +{ + insert(end(), rSource.begin(), rSource.end()); +} + +void Primitive2DContainer::append(Primitive2DContainer&& rSource) +{ + size_t n = size(); + resize(n + rSource.size()); + for (size_t i = 0; i < rSource.size(); ++i) + { + (*this)[n + i] = std::move(rSource[i]); + } +} + +void Primitive2DContainer::append(const Primitive2DSequence& rSource) +{ + std::copy(rSource.begin(), rSource.end(), std::back_inserter(*this)); +} + +} // end of namespace drawinglayer::primitive2d + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/Tools.cxx b/drawinglayer/source/primitive2d/Tools.cxx new file mode 100644 index 000000000..7db3a94c8 --- /dev/null +++ b/drawinglayer/source/primitive2d/Tools.cxx @@ -0,0 +1,242 @@ +/* -*- 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/Tools.hxx> +#include <drawinglayer/primitive2d/baseprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <basegfx/utils/canvastools.hxx> + +using namespace css; + +namespace drawinglayer::primitive2d +{ +// get B2DRange from a given Primitive2DReference +basegfx::B2DRange +getB2DRangeFromPrimitive2DReference(const Primitive2DReference& rCandidate, + const geometry::ViewInformation2D& aViewInformation) +{ + basegfx::B2DRange aRetval; + + if (rCandidate.is()) + { + // try to get C++ implementation base + const BasePrimitive2D* pCandidate(dynamic_cast<BasePrimitive2D*>(rCandidate.get())); + + if (pCandidate) + { + // use it if possible + aRetval.expand(pCandidate->getB2DRange(aViewInformation)); + } + else + { + // use UNO API call instead + const uno::Sequence<beans::PropertyValue>& rViewParameters( + aViewInformation.getViewInformationSequence()); + aRetval.expand(basegfx::unotools::b2DRectangleFromRealRectangle2D( + rCandidate->getRange(rViewParameters))); + } + } + + return aRetval; +} + +bool arePrimitive2DReferencesEqual(const Primitive2DReference& rxA, const Primitive2DReference& rxB) +{ + const bool bAIs(rxA.is()); + + if (bAIs != rxB.is()) + { + return false; + } + + if (!bAIs) + { + return true; + } + + const BasePrimitive2D* pA(dynamic_cast<const BasePrimitive2D*>(rxA.get())); + const BasePrimitive2D* pB(dynamic_cast<const BasePrimitive2D*>(rxB.get())); + + if (!pA || !pB) + { + return false; + } + + return pA->operator==(*pB); +} + +OUString idToString(sal_uInt32 nId) +{ + switch (nId) + { + case PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D: + return "TRANSPARENCE"; + case PRIMITIVE2D_ID_ANIMATEDSWITCHPRIMITIVE2D: + return "ANIMATEDSWITCH"; + case PRIMITIVE2D_ID_ANIMATEDBLINKPRIMITIVE2D: + return "ANIMATEDBLINK"; + case PRIMITIVE2D_ID_ANIMATEDINTERPOLATEPRIMITIVE2D: + return "ANIMATEDINTERPOLATE"; + case PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D: + return "BACKGROUNDCOLOR"; + case PRIMITIVE2D_ID_BITMAPPRIMITIVE2D: + return "BITMAP"; + case PRIMITIVE2D_ID_CONTROLPRIMITIVE2D: + return "CONTROL"; + case PRIMITIVE2D_ID_EMBEDDED3DPRIMITIVE2D: + return "EMBEDDED3D"; + case PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D: + return "FILLGRAPHIC"; + case PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D: + return "FILLGRADIENT"; + case PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D: + return "FILLHATCH"; + case PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D: + return "GRAPHIC"; + case PRIMITIVE2D_ID_GRIDPRIMITIVE2D: + return "GRID"; + case PRIMITIVE2D_ID_GROUPPRIMITIVE2D: + return "GROUP"; + case PRIMITIVE2D_ID_HELPLINEPRIMITIVE2D: + return "HELPLINE"; + case PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D: + return "MARKERARRAY"; + case PRIMITIVE2D_ID_MASKPRIMITIVE2D: + return "MASK"; + case PRIMITIVE2D_ID_MEDIAPRIMITIVE2D: + return "MEDIA"; + case PRIMITIVE2D_ID_METAFILEPRIMITIVE2D: + return "METAFILE"; + case PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D: + return "MODIFIEDCOLOR"; + case PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D: + return "POLYGONHAIRLINE"; + case PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D: + return "POLYGONMARKER"; + case PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D: + return "POLYGONSTROKE"; + case PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D: + return "POLYGONSTROKEARROW"; + case PRIMITIVE2D_ID_POLYPOLYGONSTROKEPRIMITIVE2D: + return "POLYPOLYGONSTROKE"; + case PRIMITIVE2D_ID_POLYPOLYGONSTROKEARROWPRIMITIVE2D: + return "POLYPOLYGONSTROKEARROW"; + case PRIMITIVE2D_ID_POLYPOLYGONCOLORPRIMITIVE2D: + return "POLYPOLYGONCOLOR"; + case PRIMITIVE2D_ID_POLYPOLYGONGRADIENTPRIMITIVE2D: + return "POLYPOLYGONGRADIENT"; + case PRIMITIVE2D_ID_POLYPOLYGONHATCHPRIMITIVE2D: + return "POLYPOLYGONHATCH"; + case PRIMITIVE2D_ID_POLYPOLYGONGRAPHICPRIMITIVE2D: + return "POLYPOLYGONGRAPHIC"; + case PRIMITIVE2D_ID_SCENEPRIMITIVE2D: + return "SCENE"; + case PRIMITIVE2D_ID_SHADOWPRIMITIVE2D: + return "SHADOW"; + case PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D: + return "TEXTSIMPLEPORTION"; + case PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D: + return "TEXTDECORATEDPORTION"; + case PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D: + return "TRANSFORM"; + case PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D: + return "UNIFIEDTRANSPARENCE"; + case PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D: + return "POINTARRAY"; + case PRIMITIVE2D_ID_TEXTHIERARCHYFIELDPRIMITIVE2D: + return "TEXTHIERARCHYFIELD"; + case PRIMITIVE2D_ID_TEXTHIERARCHYLINEPRIMITIVE2D: + return "TEXTHIERARCHYLINE"; + case PRIMITIVE2D_ID_TEXTHIERARCHYPARAGRAPHPRIMITIVE2D: + return "TEXTHIERARCHYPARAGRAPH"; + case PRIMITIVE2D_ID_TEXTHIERARCHYBLOCKPRIMITIVE2D: + return "TEXTHIERARCHYBLOCK"; + case PRIMITIVE2D_ID_TEXTHIERARCHYEDITPRIMITIVE2D: + return "TEXTHIERARCHYEDIT"; + case PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D: + return "POLYGONWAVE"; + case PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D: + return "WRONGSPELL"; + case PRIMITIVE2D_ID_TEXTEFFECTPRIMITIVE2D: + return "TEXTEFFECT"; + case PRIMITIVE2D_ID_TEXTHIERARCHYBULLETPRIMITIVE2D: + return "TEXTHIERARCHYBULLET"; + case PRIMITIVE2D_ID_POLYPOLYGONHAIRLINEPRIMITIVE2D: + return "POLYPOLYGONHAIRLINE"; + case PRIMITIVE2D_ID_EXECUTEPRIMITIVE2D: + return "EXECUTE"; + case PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D: + return "PAGEPREVIEW"; + case PRIMITIVE2D_ID_STRUCTURETAGPRIMITIVE2D: + return "STRUCTURETAG"; + case PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D: + return "BORDERLINE"; + case PRIMITIVE2D_ID_POLYPOLYGONMARKERPRIMITIVE2D: + return "POLYPOLYGONMARKER"; + case PRIMITIVE2D_ID_HITTESTPRIMITIVE2D: + return "HITTEST"; + case PRIMITIVE2D_ID_INVERTPRIMITIVE2D: + return "INVERT"; + case PRIMITIVE2D_ID_DISCRETEBITMAPPRIMITIVE2D: + return "DISCRETEBITMAP"; + case PRIMITIVE2D_ID_WALLPAPERBITMAPPRIMITIVE2D: + return "WALLPAPERBITMAP"; + case PRIMITIVE2D_ID_TEXTLINEPRIMITIVE2D: + return "TEXTLINE"; + case PRIMITIVE2D_ID_TEXTCHARACTERSTRIKEOUTPRIMITIVE2D: + return "TEXTCHARACTERSTRIKEOUT"; + case PRIMITIVE2D_ID_TEXTGEOMETRYSTRIKEOUTPRIMITIVE2D: + return "TEXTGEOMETRYSTRIKEOUT"; + case PRIMITIVE2D_ID_EPSPRIMITIVE2D: + return "EPS"; + case PRIMITIVE2D_ID_DISCRETESHADOWPRIMITIVE2D: + return "DISCRETESHADOW"; + case PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D: + return "HIDDENGEOMETRY"; + case PRIMITIVE2D_ID_SVGLINEARGRADIENTPRIMITIVE2D: + return "SVGLINEARGRADIENT"; + case PRIMITIVE2D_ID_SVGRADIALGRADIENTPRIMITIVE2D: + return "SVGRADIALGRADIENT"; + case PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D: + return "SVGLINEARATOM"; + case PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D: + return "SVGRADIALATOM"; + case PRIMITIVE2D_ID_CROPPRIMITIVE2D: + return "CROP"; + case PRIMITIVE2D_ID_PATTERNFILLPRIMITIVE2D: + return "PATTERNFILL"; + case PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D: + return "OBJECTINFO"; + case PRIMITIVE2D_ID_POLYPOLYGONSELECTIONPRIMITIVE2D: + return "POLYPOLYGONSELECTION"; + case PRIMITIVE2D_ID_PAGEHIERARCHYPRIMITIVE2D: + return "PAGEHIERARCHY"; + case PRIMITIVE2D_ID_GLOWPRIMITIVE2D: + return "GLOWPRIMITIVE"; + case PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D: + return "SOFTEDGEPRIMITIVE"; + default: + return OUString::number((nId >> 16) & 0xFF) + "|" + OUString::number(nId & 0xFF); + } +} + +} // end of namespace drawinglayer::primitive2d + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/animatedprimitive2d.cxx b/drawinglayer/source/primitive2d/animatedprimitive2d.cxx new file mode 100644 index 000000000..d2c8d4a65 --- /dev/null +++ b/drawinglayer/source/primitive2d/animatedprimitive2d.cxx @@ -0,0 +1,196 @@ +/* -*- 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/animatedprimitive2d.hxx> +#include <drawinglayer/animation/animationtiming.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void AnimatedSwitchPrimitive2D::setAnimationEntry(const animation::AnimationEntry& rNew) + { + // clone given animation description + mpAnimationEntry = rNew.clone(); + } + + AnimatedSwitchPrimitive2D::AnimatedSwitchPrimitive2D( + const animation::AnimationEntry& rAnimationEntry, + const Primitive2DContainer& rChildren, + bool bIsTextAnimation) + : GroupPrimitive2D(rChildren), + mbIsTextAnimation(bIsTextAnimation) + { + // clone given animation description + mpAnimationEntry = rAnimationEntry.clone(); + } + + AnimatedSwitchPrimitive2D::~AnimatedSwitchPrimitive2D() + { + } + + bool AnimatedSwitchPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const AnimatedSwitchPrimitive2D& rCompare = static_cast< const AnimatedSwitchPrimitive2D& >(rPrimitive); + + return (getAnimationEntry() == rCompare.getAnimationEntry()); + } + + return false; + } + + void AnimatedSwitchPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + if(getChildren().empty()) + return; + + const double fState(getAnimationEntry().getStateAtTime(rViewInformation.getViewTime())); + const sal_uInt32 nLen(getChildren().size()); + sal_uInt32 nIndex(basegfx::fround(fState * static_cast<double>(nLen))); + + if(nIndex >= nLen) + { + nIndex = nLen - 1; + } + + const Primitive2DReference xRef(getChildren()[nIndex], uno::UNO_SET_THROW); + rVisitor.append(xRef); + } + + // provide unique ID + ImplPrimitive2DIDBlock(AnimatedSwitchPrimitive2D, PRIMITIVE2D_ID_ANIMATEDSWITCHPRIMITIVE2D) + +} // end of namespace drawinglayer::primitive2d + + +namespace drawinglayer::primitive2d +{ + AnimatedBlinkPrimitive2D::AnimatedBlinkPrimitive2D( + const animation::AnimationEntry& rAnimationEntry, + const Primitive2DContainer& rChildren) + : AnimatedSwitchPrimitive2D(rAnimationEntry, rChildren, true/*bIsTextAnimation*/) + { + } + + void AnimatedBlinkPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + if(!getChildren().empty()) + { + const double fState(getAnimationEntry().getStateAtTime(rViewInformation.getViewTime())); + + if(fState < 0.5) + { + getChildren(rVisitor); + } + } + } + + // provide unique ID + ImplPrimitive2DIDBlock(AnimatedBlinkPrimitive2D, PRIMITIVE2D_ID_ANIMATEDBLINKPRIMITIVE2D) + +} // end of namespace drawinglayer::primitive2d + + +namespace drawinglayer::primitive2d +{ + AnimatedInterpolatePrimitive2D::AnimatedInterpolatePrimitive2D( + const std::vector< basegfx::B2DHomMatrix >& rmMatrixStack, + const animation::AnimationEntry& rAnimationEntry, + const Primitive2DContainer& rChildren) + : AnimatedSwitchPrimitive2D(rAnimationEntry, rChildren, true/*bIsTextAnimation*/), + maMatrixStack() + { + // copy matrices to locally pre-decomposed matrix stack + const sal_uInt32 nCount(rmMatrixStack.size()); + maMatrixStack.reserve(nCount); + + for(sal_uInt32 a(0); a < nCount; a++) + { + maMatrixStack.emplace_back(rmMatrixStack[a]); + } + } + + void AnimatedInterpolatePrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + const sal_uInt32 nSize(maMatrixStack.size()); + + if(nSize) + { + double fState(getAnimationEntry().getStateAtTime(rViewInformation.getViewTime())); + + if(fState < 0.0) + { + fState = 0.0; + } + else if(fState > 1.0) + { + fState = 1.0; + } + + const double fIndex(fState * static_cast<double>(nSize - 1)); + const sal_uInt32 nIndA(sal_uInt32(floor(fIndex))); + const double fOffset(fIndex - static_cast<double>(nIndA)); + basegfx::B2DHomMatrix aTargetTransform; + std::vector< basegfx::utils::B2DHomMatrixBufferedDecompose >::const_iterator aMatA(maMatrixStack.begin() + nIndA); + + if(basegfx::fTools::equalZero(fOffset)) + { + // use matrix from nIndA directly + aTargetTransform = aMatA->getB2DHomMatrix(); + } + else + { + // interpolate. Get involved buffered decomposed matrices + const sal_uInt32 nIndB((nIndA + 1) % nSize); + std::vector< basegfx::utils::B2DHomMatrixBufferedDecompose >::const_iterator aMatB(maMatrixStack.begin() + nIndB); + + // interpolate for fOffset [0.0 .. 1.0[ + const basegfx::B2DVector aScale(basegfx::interpolate(aMatA->getScale(), aMatB->getScale(), fOffset)); + const basegfx::B2DVector aTranslate(basegfx::interpolate(aMatA->getTranslate(), aMatB->getTranslate(), fOffset)); + const double fRotate(((aMatB->getRotate() - aMatA->getRotate()) * fOffset) + aMatA->getRotate()); + const double fShearX(((aMatB->getShearX() - aMatA->getShearX()) * fOffset) + aMatA->getShearX()); + + // build matrix for state + aTargetTransform = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aScale, fShearX, fRotate, aTranslate); + } + + // create new transform primitive reference, return new sequence + const Primitive2DReference xRef(new TransformPrimitive2D(aTargetTransform, getChildren())); + rVisitor.append(xRef); + } + else + { + getChildren(rVisitor); + } + } + + // provide unique ID + ImplPrimitive2DIDBlock(AnimatedInterpolatePrimitive2D, PRIMITIVE2D_ID_ANIMATEDINTERPOLATEPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/backgroundcolorprimitive2d.cxx b/drawinglayer/source/primitive2d/backgroundcolorprimitive2d.cxx new file mode 100644 index 000000000..ba0f47193 --- /dev/null +++ b/drawinglayer/source/primitive2d/backgroundcolorprimitive2d.cxx @@ -0,0 +1,95 @@ +/* -*- 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/backgroundcolorprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void BackgroundColorPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + if(!rViewInformation.getViewport().isEmpty()) + { + const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(rViewInformation.getViewport())); + rContainer.push_back(new PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aOutline), getBColor())); + } + } + + BackgroundColorPrimitive2D::BackgroundColorPrimitive2D( + const basegfx::BColor& rBColor, + double fTransparency) + : BufferedDecompositionPrimitive2D(), + maBColor(rBColor), + mfTransparency(fTransparency), + maLastViewport() + { + } + + bool BackgroundColorPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const BackgroundColorPrimitive2D& rCompare = static_cast<const BackgroundColorPrimitive2D&>(rPrimitive); + + return (getBColor() == rCompare.getBColor() && getTransparency() == rCompare.getTransparency()); + } + + return false; + } + + basegfx::B2DRange BackgroundColorPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + // always as big as the view + return rViewInformation.getViewport(); + } + + void BackgroundColorPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if(!getBuffered2DDecomposition().empty() && (maLastViewport != rViewInformation.getViewport())) + { + // conditions of last local decomposition have changed, delete + const_cast< BackgroundColorPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + + if(getBuffered2DDecomposition().empty()) + { + // remember ViewRange + const_cast< BackgroundColorPrimitive2D* >(this)->maLastViewport = rViewInformation.getViewport(); + } + + // use parent implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + // provide unique ID + ImplPrimitive2DIDBlock(BackgroundColorPrimitive2D, PRIMITIVE2D_ID_BACKGROUNDCOLORPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/baseprimitive2d.cxx b/drawinglayer/source/primitive2d/baseprimitive2d.cxx new file mode 100644 index 000000000..ef7055ebd --- /dev/null +++ b/drawinglayer/source/primitive2d/baseprimitive2d.cxx @@ -0,0 +1,141 @@ +/* -*- 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 <iterator> +#include <utility> + +#include <drawinglayer/primitive2d/baseprimitive2d.hxx> +#include <drawinglayer/primitive2d/Tools.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <comphelper/sequence.hxx> + +using namespace css; + +namespace drawinglayer::primitive2d +{ +BasePrimitive2D::BasePrimitive2D() + : BasePrimitive2DImplBase(m_aMutex) +{ +} + +BasePrimitive2D::~BasePrimitive2D() {} + +bool BasePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + return (getPrimitive2DID() == rPrimitive.getPrimitive2DID()); +} + +namespace +{ +// Visitor class to get the B2D range from a tree of Primitive2DReference's +// +class B2DRangeVisitor : public Primitive2DDecompositionVisitor +{ +public: + const geometry::ViewInformation2D& mrViewInformation; + basegfx::B2DRange maRetval; + B2DRangeVisitor(const geometry::ViewInformation2D& rViewInformation) + : mrViewInformation(rViewInformation) + { + } + virtual void append(const Primitive2DReference& r) override + { + maRetval.expand(getB2DRangeFromPrimitive2DReference(r, mrViewInformation)); + } + virtual void append(const Primitive2DContainer& r) override + { + maRetval.expand(r.getB2DRange(mrViewInformation)); + } + virtual void append(Primitive2DContainer&& r) override + { + maRetval.expand(r.getB2DRange(mrViewInformation)); + } +}; +} + +basegfx::B2DRange +BasePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const +{ + B2DRangeVisitor aVisitor(rViewInformation); + get2DDecomposition(aVisitor, rViewInformation); + return aVisitor.maRetval; +} + +void BasePrimitive2D::get2DDecomposition( + Primitive2DDecompositionVisitor& /*rVisitor*/, + const geometry::ViewInformation2D& /*rViewInformation*/) const +{ +} + +css::uno::Sequence<::css::uno::Reference<::css::graphic::XPrimitive2D>> SAL_CALL +BasePrimitive2D::getDecomposition(const uno::Sequence<beans::PropertyValue>& rViewParameters) +{ + const geometry::ViewInformation2D aViewInformation(rViewParameters); + Primitive2DContainer aContainer; + get2DDecomposition(aContainer, aViewInformation); + return comphelper::containerToSequence(aContainer); +} + +css::geometry::RealRectangle2D SAL_CALL +BasePrimitive2D::getRange(const uno::Sequence<beans::PropertyValue>& rViewParameters) +{ + const geometry::ViewInformation2D aViewInformation(rViewParameters); + return basegfx::unotools::rectangle2DFromB2DRectangle(getB2DRange(aViewInformation)); +} + +sal_Int64 SAL_CALL BasePrimitive2D::estimateUsage() +{ + return 0; // for now ignore the objects themselves +} + +void BufferedDecompositionPrimitive2D::create2DDecomposition( + Primitive2DContainer& /*rContainer*/, + const geometry::ViewInformation2D& /*rViewInformation*/) const +{ +} + +BufferedDecompositionPrimitive2D::BufferedDecompositionPrimitive2D() + : BasePrimitive2D() + , maBuffered2DDecomposition() +{ +} + +void BufferedDecompositionPrimitive2D::get2DDecomposition( + Primitive2DDecompositionVisitor& rVisitor, + const geometry::ViewInformation2D& rViewInformation) const +{ + ::osl::MutexGuard aGuard(m_aMutex); + + if (getBuffered2DDecomposition().empty()) + { + Primitive2DContainer aNewSequence; + create2DDecomposition(aNewSequence, rViewInformation); + const_cast<BufferedDecompositionPrimitive2D*>(this)->setBuffered2DDecomposition( + aNewSequence); + } + + rVisitor.append(getBuffered2DDecomposition()); +} + +} // end of namespace drawinglayer::primitive2d + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/bitmapprimitive2d.cxx b/drawinglayer/source/primitive2d/bitmapprimitive2d.cxx new file mode 100644 index 000000000..f495d531d --- /dev/null +++ b/drawinglayer/source/primitive2d/bitmapprimitive2d.cxx @@ -0,0 +1,78 @@ +/* -*- 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/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <com/sun/star/awt/XBitmap.hpp> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +BitmapPrimitive2D::BitmapPrimitive2D(const css::uno::Reference<css::awt::XBitmap>& rXBitmap, + const basegfx::B2DHomMatrix& rTransform) + : BasePrimitive2D() + , maXBitmap(rXBitmap) + , maTransform(rTransform) +{ +} + +bool BitmapPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BasePrimitive2D::operator==(rPrimitive)) + { + const BitmapPrimitive2D& rCompare = static_cast<const BitmapPrimitive2D&>(rPrimitive); + + return (getXBitmap() == rCompare.getXBitmap() && getTransform() == rCompare.getTransform()); + } + + return false; +} + +basegfx::B2DRange +BitmapPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(maTransform); + return aRetval; +} + +sal_Int64 SAL_CALL BitmapPrimitive2D::estimateUsage() +{ + if (!getXBitmap().is()) + { + return 0; + } + + uno::Reference<util::XAccounting> const xAcc(getXBitmap(), uno::UNO_QUERY); + + if (!xAcc.is()) + { + return 0; + } + + return xAcc->estimateUsage(); +} + +// provide unique ID +ImplPrimitive2DIDBlock(BitmapPrimitive2D, PRIMITIVE2D_ID_BITMAPPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/borderlineprimitive2d.cxx b/drawinglayer/source/primitive2d/borderlineprimitive2d.cxx new file mode 100644 index 000000000..b264e2c02 --- /dev/null +++ b/drawinglayer/source/primitive2d/borderlineprimitive2d.cxx @@ -0,0 +1,428 @@ +/* -*- 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/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/borderlineprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <rtl/math.hxx> + +#include <algorithm> + + +namespace drawinglayer::primitive2d +{ + BorderLine::BorderLine( + const drawinglayer::attribute::LineAttribute& rLineAttribute, + double fStartLeft, + double fStartRight, + double fEndLeft, + double fEndRight) + : maLineAttribute(rLineAttribute), + mfStartLeft(fStartLeft), + mfStartRight(fStartRight), + mfEndLeft(fEndLeft), + mfEndRight(fEndRight), + mbIsGap(false) + { + } + + BorderLine::BorderLine( + double fWidth) + : maLineAttribute(basegfx::BColor(), fWidth), + mfStartLeft(0.0), + mfStartRight(0.0), + mfEndLeft(0.0), + mfEndRight(0.0), + mbIsGap(true) + { + } + + BorderLine::~BorderLine() + { + } + + bool BorderLine::operator==(const BorderLine& rBorderLine) const + { + return getLineAttribute() == rBorderLine.getLineAttribute() + && getStartLeft() == rBorderLine.getStartLeft() + && getStartRight() == rBorderLine.getStartRight() + && getEndLeft() == rBorderLine.getEndLeft() + && getEndRight() == rBorderLine.getEndRight() + && isGap() == rBorderLine.isGap(); + } + + // helper to add a centered, maybe stroked line primitive to rContainer + static void addPolygonStrokePrimitive2D( + Primitive2DContainer& rContainer, + const basegfx::B2DPoint& rStart, + const basegfx::B2DPoint& rEnd, + const attribute::LineAttribute& rLineAttribute, + const attribute::StrokeAttribute& rStrokeAttribute) + { + basegfx::B2DPolygon aPolygon; + + aPolygon.append(rStart); + aPolygon.append(rEnd); + + if (rStrokeAttribute.isDefault()) + { + rContainer.push_back( + new PolygonStrokePrimitive2D( + aPolygon, + rLineAttribute)); + } + else + { + rContainer.push_back( + new PolygonStrokePrimitive2D( + aPolygon, + rLineAttribute, + rStrokeAttribute)); + } + } + + double BorderLinePrimitive2D::getFullWidth() const + { + double fRetval(0.0); + + for(const auto& candidate : maBorderLines) + { + fRetval += candidate.getLineAttribute().getWidth(); + } + + return fRetval; + } + + void BorderLinePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if (getStart().equal(getEnd()) || getBorderLines().empty()) + return; + + // get data and vectors + basegfx::B2DVector aVector(getEnd() - getStart()); + aVector.normalize(); + const basegfx::B2DVector aPerpendicular(basegfx::getPerpendicular(aVector)); + const double fFullWidth(getFullWidth()); + double fOffset(fFullWidth * -0.5); + + for(const auto& candidate : maBorderLines) + { + const double fWidth(candidate.getLineAttribute().getWidth()); + + if(!candidate.isGap()) + { + const basegfx::B2DVector aDeltaY(aPerpendicular * (fOffset + (fWidth * 0.5))); + const basegfx::B2DPoint aStart(getStart() + aDeltaY); + const basegfx::B2DPoint aEnd(getEnd() + aDeltaY); + const bool bStartPerpendicular(rtl::math::approxEqual(candidate.getStartLeft(), candidate.getStartRight())); + const bool bEndPerpendicular(rtl::math::approxEqual(candidate.getEndLeft(), candidate.getEndRight())); + + if(bStartPerpendicular && bEndPerpendicular) + { + // start and end extends lead to an edge perpendicular to the line, so we can just use + // a PolygonStrokePrimitive2D for representation + addPolygonStrokePrimitive2D( + rContainer, + aStart - (aVector * candidate.getStartLeft()), + aEnd + (aVector * candidate.getEndLeft()), + candidate.getLineAttribute(), + getStrokeAttribute()); + } + else + { + // start and/or end extensions lead to a lineStart/End that is *not* + // perpendicular to the line itself + if(getStrokeAttribute().isDefault() || 0.0 == getStrokeAttribute().getFullDotDashLen()) + { + // without stroke, we can simply represent that using a filled polygon + const basegfx::B2DVector aHalfLineOffset(aPerpendicular * (candidate.getLineAttribute().getWidth() * 0.5)); + basegfx::B2DPolygon aPolygon; + + aPolygon.append(aStart - aHalfLineOffset - (aVector * candidate.getStartLeft())); + aPolygon.append(aEnd - aHalfLineOffset + (aVector * candidate.getEndLeft())); + aPolygon.append(aEnd + aHalfLineOffset + (aVector * candidate.getEndRight())); + aPolygon.append(aStart + aHalfLineOffset - (aVector * candidate.getStartRight())); + + rContainer.push_back( + new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aPolygon), + candidate.getLineAttribute().getColor())); + } + else + { + // with stroke, we have a problem - a filled polygon would lose the + // stroke. Let's represent the start and/or end as triangles, the main + // line still as PolygonStrokePrimitive2D. + // Fill default line Start/End for stroke, so we need no adaptations in else paths + basegfx::B2DPoint aStrokeStart(aStart - (aVector * candidate.getStartLeft())); + basegfx::B2DPoint aStrokeEnd(aEnd + (aVector * candidate.getEndLeft())); + const basegfx::B2DVector aHalfLineOffset(aPerpendicular * (candidate.getLineAttribute().getWidth() * 0.5)); + + if(!bStartPerpendicular) + { + const double fMin(std::min(candidate.getStartLeft(), candidate.getStartRight())); + const double fMax(std::max(candidate.getStartLeft(), candidate.getStartRight())); + basegfx::B2DPolygon aPolygon; + + // create a triangle with min/max values for LineStart and add + if(rtl::math::approxEqual(candidate.getStartLeft(), fMax)) + { + aPolygon.append(aStart - aHalfLineOffset - (aVector * candidate.getStartLeft())); + } + + aPolygon.append(aStart - aHalfLineOffset - (aVector * fMin)); + aPolygon.append(aStart + aHalfLineOffset - (aVector * fMin)); + + if(rtl::math::approxEqual(candidate.getStartRight(), fMax)) + { + aPolygon.append(aStart + aHalfLineOffset - (aVector * candidate.getStartRight())); + } + + rContainer.push_back( + new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aPolygon), + candidate.getLineAttribute().getColor())); + + // Adapt StrokeStart accordingly + aStrokeStart = aStart - (aVector * fMin); + } + + if(!bEndPerpendicular) + { + const double fMin(std::min(candidate.getEndLeft(), candidate.getEndRight())); + const double fMax(std::max(candidate.getEndLeft(), candidate.getEndRight())); + basegfx::B2DPolygon aPolygon; + + // create a triangle with min/max values for LineEnd and add + if(rtl::math::approxEqual(candidate.getEndLeft(), fMax)) + { + aPolygon.append(aEnd - aHalfLineOffset + (aVector * candidate.getEndLeft())); + } + + if(rtl::math::approxEqual(candidate.getEndRight(), fMax)) + { + aPolygon.append(aEnd + aHalfLineOffset + (aVector * candidate.getEndRight())); + } + + aPolygon.append(aEnd + aHalfLineOffset + (aVector * fMin)); + aPolygon.append(aEnd - aHalfLineOffset + (aVector * fMin)); + + rContainer.push_back( + new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aPolygon), + candidate.getLineAttribute().getColor())); + + // Adapt StrokeEnd accordingly + aStrokeEnd = aEnd + (aVector * fMin); + } + + addPolygonStrokePrimitive2D( + rContainer, + aStrokeStart, + aStrokeEnd, + candidate.getLineAttribute(), + getStrokeAttribute()); + } + } + } + + fOffset += fWidth; + } + } + + bool BorderLinePrimitive2D::isHorizontalOrVertical(const geometry::ViewInformation2D& rViewInformation) const + { + if (!getStart().equal(getEnd())) + { + const basegfx::B2DHomMatrix& rOTVT = rViewInformation.getObjectToViewTransformation(); + const basegfx::B2DVector aVector(rOTVT * getEnd() - rOTVT * getStart()); + + return basegfx::fTools::equalZero(aVector.getX()) || basegfx::fTools::equalZero(aVector.getY()); + } + + return false; + } + + BorderLinePrimitive2D::BorderLinePrimitive2D( + const basegfx::B2DPoint& rStart, + const basegfx::B2DPoint& rEnd, + const std::vector< BorderLine >& rBorderLines, + const drawinglayer::attribute::StrokeAttribute& rStrokeAttribute) + : BufferedDecompositionPrimitive2D(), + maStart(rStart), + maEnd(rEnd), + maBorderLines(rBorderLines), + maStrokeAttribute(rStrokeAttribute) + { + } + + bool BorderLinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const BorderLinePrimitive2D& rCompare = static_cast<const BorderLinePrimitive2D&>(rPrimitive); + + if (getStart() == rCompare.getStart() + && getEnd() == rCompare.getEnd() + && getStrokeAttribute() == rCompare.getStrokeAttribute()) + { + if (getBorderLines().size() == rCompare.getBorderLines().size()) + { + for (size_t a(0); a < getBorderLines().size(); a++) + { + if (!(getBorderLines()[a] == rCompare.getBorderLines()[a])) + { + return false; + } + } + } + } + } + + return false; + } + + // provide unique ID + ImplPrimitive2DIDBlock(BorderLinePrimitive2D, PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D) + + Primitive2DReference tryMergeBorderLinePrimitive2D( + const BorderLinePrimitive2D* pCandidateA, + const BorderLinePrimitive2D* pCandidateB) + { + assert(pCandidateA); + assert(pCandidateB); + + // start of candidate has to match end of this + if(!pCandidateA->getEnd().equal(pCandidateB->getStart())) + { + return Primitive2DReference(); + } + + // candidate A needs a length + if(pCandidateA->getStart().equal(pCandidateA->getEnd())) + { + return Primitive2DReference(); + } + + // candidate B needs a length + if(pCandidateB->getStart().equal(pCandidateB->getEnd())) + { + return Primitive2DReference(); + } + + // StrokeAttribute has to be equal + if(!(pCandidateA->getStrokeAttribute() == pCandidateB->getStrokeAttribute())) + { + return Primitive2DReference(); + } + + // direction has to be equal -> cross product == 0.0 + const basegfx::B2DVector aVT(pCandidateA->getEnd() - pCandidateA->getStart()); + const basegfx::B2DVector aVC(pCandidateB->getEnd() - pCandidateB->getStart()); + if(!rtl::math::approxEqual(0.0, aVC.cross(aVT))) + { + return Primitive2DReference(); + } + + // number BorderLines has to be equal + const size_t count(pCandidateA->getBorderLines().size()); + if(count != pCandidateB->getBorderLines().size()) + { + return Primitive2DReference(); + } + + for(size_t a(0); a < count; a++) + { + const BorderLine& rBT(pCandidateA->getBorderLines()[a]); + const BorderLine& rBC(pCandidateB->getBorderLines()[a]); + + // LineAttribute has to be the same + if(!(rBC.getLineAttribute() == rBT.getLineAttribute())) + { + return Primitive2DReference(); + } + + // isGap has to be the same + if(rBC.isGap() != rBT.isGap()) + { + return Primitive2DReference(); + } + + if(rBT.isGap()) + { + // when gap, width has to be equal + if(!rtl::math::approxEqual(rBT.getLineAttribute().getWidth(), rBC.getLineAttribute().getWidth())) + { + return Primitive2DReference(); + } + } + else + { + // when not gap, the line extends have at least reach to the center ( > 0.0), + // else there is an extend usage. When > 0.0 they just overlap, no problem + if(rBT.getEndLeft() >= 0.0 + && rBT.getEndRight() >= 0.0 + && rBC.getStartLeft() >= 0.0 + && rBC.getStartRight() >= 0.0) + { + // okay + } + else + { + return Primitive2DReference(); + } + } + } + + // all conditions met, create merged primitive + std::vector< BorderLine > aMergedBorderLines; + + for(size_t a(0); a < count; a++) + { + const BorderLine& rBT(pCandidateA->getBorderLines()[a]); + const BorderLine& rBC(pCandidateB->getBorderLines()[a]); + + if(rBT.isGap()) + { + aMergedBorderLines.push_back(rBT); + } + else + { + aMergedBorderLines.push_back( + BorderLine( + rBT.getLineAttribute(), + rBT.getStartLeft(), rBT.getStartRight(), + rBC.getEndLeft(), rBC.getEndRight())); + } + } + + return Primitive2DReference( + new BorderLinePrimitive2D( + pCandidateA->getStart(), + pCandidateB->getEnd(), + aMergedBorderLines, + pCandidateA->getStrokeAttribute())); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/controlprimitive2d.cxx b/drawinglayer/source/primitive2d/controlprimitive2d.cxx new file mode 100644 index 000000000..c3b030dcf --- /dev/null +++ b/drawinglayer/source/primitive2d/controlprimitive2d.cxx @@ -0,0 +1,362 @@ +/* -*- 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/controlprimitive2d.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/awt/XControl.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <vcl/virdev.hxx> +#include <vcl/svapp.hxx> +#include <com/sun/star/awt/PosSize.hpp> +#include <vcl/bitmapex.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <tools/diagnose_ex.h> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <svtools/optionsdrawinglayer.hxx> +#include <toolkit/awt/vclxwindow.hxx> +#include <vcl/window.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ + void ControlPrimitive2D::createXControl() + { + if(!(!mxXControl.is() && getControlModel().is())) + return; + + uno::Reference< beans::XPropertySet > xSet(getControlModel(), uno::UNO_QUERY); + + if(!xSet.is()) + return; + + uno::Any aValue(xSet->getPropertyValue("DefaultControl")); + OUString aUnoControlTypeName; + + if(!(aValue >>= aUnoControlTypeName)) + return; + + if(aUnoControlTypeName.isEmpty()) + return; + + uno::Reference< uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + uno::Reference< awt::XControl > xXControl( + xContext->getServiceManager()->createInstanceWithContext(aUnoControlTypeName, xContext), uno::UNO_QUERY); + + if(xXControl.is()) + { + xXControl->setModel(getControlModel()); + + // remember XControl + mxXControl = xXControl; + } + } + + Primitive2DReference ControlPrimitive2D::createBitmapDecomposition(const geometry::ViewInformation2D& rViewInformation) const + { + Primitive2DReference xRetval; + const uno::Reference< awt::XControl >& rXControl(getXControl()); + + if(rXControl.is()) + { + uno::Reference< awt::XWindow > xControlWindow(rXControl, uno::UNO_QUERY); + + if(xControlWindow.is()) + { + // get decomposition to get size + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + getTransform().decompose(aScale, aTranslate, fRotate, fShearX); + + // get absolute discrete size (no mirror or rotate here) + aScale = basegfx::absolute(aScale); + basegfx::B2DVector aDiscreteSize(rViewInformation.getObjectToViewTransformation() * aScale); + + // limit to a maximum square size, e.g. 300x150 pixels (45000) + const SvtOptionsDrawinglayer aDrawinglayerOpt; + const double fDiscreteMax(aDrawinglayerOpt.GetQuadraticFormControlRenderLimit()); + const double fDiscreteQuadratic(aDiscreteSize.getX() * aDiscreteSize.getY()); + const bool bScaleUsed(fDiscreteQuadratic > fDiscreteMax); + double fFactor(1.0); + + if(bScaleUsed) + { + // get factor and adapt to scaled size + fFactor = sqrt(fDiscreteMax / fDiscreteQuadratic); + aDiscreteSize *= fFactor; + } + + // go to integer + const sal_Int32 nSizeX(basegfx::fround(aDiscreteSize.getX())); + const sal_Int32 nSizeY(basegfx::fround(aDiscreteSize.getY())); + + if(nSizeX > 0 && nSizeY > 0) + { + // prepare VirtualDevice + ScopedVclPtrInstance< VirtualDevice > aVirtualDevice(*Application::GetDefaultDevice()); + const Size aSizePixel(nSizeX, nSizeY); + aVirtualDevice->SetOutputSizePixel(aSizePixel); + + // set size at control + xControlWindow->setPosSize(0, 0, nSizeX, nSizeY, awt::PosSize::POSSIZE); + + // get graphics and view + uno::Reference< awt::XGraphics > xGraphics(aVirtualDevice->CreateUnoGraphics()); + uno::Reference< awt::XView > xControlView(rXControl, uno::UNO_QUERY); + + if(xGraphics.is() && xControlView.is()) + { + // link graphics and view + xControlView->setGraphics(xGraphics); + + { // #i93162# For painting the control setting a Zoom (using setZoom() at the xControlView) + // is needed to define the font size. Normally this is done in + // ViewObjectContactOfUnoControl::createPrimitive2DSequence by using positionControlForPaint(). + // For some reason the difference between MapUnit::MapTwipS and MapUnit::Map100thMM still plays + // a role there so that for Draw/Impress/Calc (the MapUnit::Map100thMM users) i need to set a zoom + // here, too. The factor includes the needed scale, but is calculated by pure comparisons. It + // is somehow related to the twips/100thmm relationship. + bool bUserIs100thmm(false); + const uno::Reference< awt::XControl > xControl(xControlView, uno::UNO_QUERY); + + if(xControl.is()) + { + uno::Reference< awt::XWindowPeer > xWindowPeer(xControl->getPeer()); + + if(xWindowPeer.is()) + { + VCLXWindow* pVCLXWindow = comphelper::getUnoTunnelImplementation<VCLXWindow>(xWindowPeer); + + if(pVCLXWindow) + { + VclPtr<vcl::Window> pWindow = pVCLXWindow->GetWindow(); + + if(pWindow) + { + pWindow = pWindow->GetParent(); + + if(pWindow && MapUnit::Map100thMM == pWindow->GetMapMode().GetMapUnit()) + { + bUserIs100thmm = true; + } + } + } + } + } + + if(bUserIs100thmm) + { + // calc screen zoom for text display. fFactor is already added indirectly in aDiscreteSize + basegfx::B2DVector aScreenZoom( + basegfx::fTools::equalZero(aScale.getX()) ? 1.0 : aDiscreteSize.getX() / aScale.getX(), + basegfx::fTools::equalZero(aScale.getY()) ? 1.0 : aDiscreteSize.getY() / aScale.getY()); + static const double fZoomScale(28.0); // do not ask for this constant factor, but it gets the zoom right + aScreenZoom *= fZoomScale; + + // set zoom at control view for text scaling + xControlView->setZoom(static_cast<float>(aScreenZoom.getX()), static_cast<float>(aScreenZoom.getY())); + } + } + + try + { + // try to paint it to VirtualDevice + xControlView->draw(0, 0); + + // get bitmap + const BitmapEx aContent(aVirtualDevice->GetBitmapEx(Point(), aSizePixel)); + + // to avoid scaling, use the Bitmap pixel size as primitive size + const Size aBitmapSize(aContent.GetSizePixel()); + basegfx::B2DVector aBitmapSizeLogic( + rViewInformation.getInverseObjectToViewTransformation() * + basegfx::B2DVector(aBitmapSize.getWidth() - 1, aBitmapSize.getHeight() - 1)); + + if(bScaleUsed) + { + // if scaled adapt to scaled size + aBitmapSizeLogic /= fFactor; + } + + // short form for scale and translate transformation + const basegfx::B2DHomMatrix aBitmapTransform(basegfx::utils::createScaleTranslateB2DHomMatrix( + aBitmapSizeLogic.getX(), aBitmapSizeLogic.getY(), aTranslate.getX(), aTranslate.getY())); + + // create primitive + xRetval = new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(aContent), + aBitmapTransform); + } + catch( const uno::Exception& ) + { + DBG_UNHANDLED_EXCEPTION("drawinglayer"); + } + } + } + } + } + + return xRetval; + } + + Primitive2DReference ControlPrimitive2D::createPlaceholderDecomposition() const + { + // create a gray placeholder hairline polygon in object size + basegfx::B2DRange aObjectRange(0.0, 0.0, 1.0, 1.0); + aObjectRange.transform(getTransform()); + const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aObjectRange)); + const basegfx::BColor aGrayTone(0xc0 / 255.0, 0xc0 / 255.0, 0xc0 / 255.0); + + // The replacement object may also get a text like 'empty group' here later + Primitive2DReference xRetval(new PolygonHairlinePrimitive2D(aOutline, aGrayTone)); + + return xRetval; + } + + void ControlPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + // try to create a bitmap decomposition. If that fails for some reason, + // at least create a replacement decomposition. + Primitive2DReference xReference(createBitmapDecomposition(rViewInformation)); + + if(!xReference.is()) + { + xReference = createPlaceholderDecomposition(); + } + + rContainer.push_back(xReference); + } + + ControlPrimitive2D::ControlPrimitive2D( + const basegfx::B2DHomMatrix& rTransform, + const uno::Reference< awt::XControlModel >& rxControlModel) + : BufferedDecompositionPrimitive2D(), + maTransform(rTransform), + mxControlModel(rxControlModel), + mxXControl(), + maLastViewScaling() + { + } + + ControlPrimitive2D::ControlPrimitive2D( + const basegfx::B2DHomMatrix& rTransform, + const uno::Reference< awt::XControlModel >& rxControlModel, + const uno::Reference< awt::XControl >& rxXControl) + : BufferedDecompositionPrimitive2D(), + maTransform(rTransform), + mxControlModel(rxControlModel), + mxXControl(rxXControl), + maLastViewScaling() + { + } + + const uno::Reference< awt::XControl >& ControlPrimitive2D::getXControl() const + { + if(!mxXControl.is()) + { + const_cast< ControlPrimitive2D* >(this)->createXControl(); + } + + return mxXControl; + } + + bool ControlPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + // use base class compare operator + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const ControlPrimitive2D& rCompare = static_cast<const ControlPrimitive2D&>(rPrimitive); + + if(getTransform() == rCompare.getTransform()) + { + // check if ControlModel references both are/are not + bool bRetval(getControlModel().is() == rCompare.getControlModel().is()); + + if(bRetval && getControlModel().is()) + { + // both exist, check for equality + bRetval = (getControlModel() == rCompare.getControlModel()); + } + + if(bRetval) + { + // check if XControl references both are/are not + bRetval = (getXControl().is() == rCompare.getXControl().is()); + } + + if(bRetval && getXControl().is()) + { + // both exist, check for equality + bRetval = (getXControl() == rCompare.getXControl()); + } + + return bRetval; + } + } + + return false; + } + + basegfx::B2DRange ControlPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // simply derivate from unit range + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(getTransform()); + return aRetval; + } + + void ControlPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + // this primitive is view-dependent related to the scaling. If scaling has changed, + // destroy existing decomposition. To detect change, use size of unit size in view coordinates + ::osl::MutexGuard aGuard( m_aMutex ); + const basegfx::B2DVector aNewScaling(rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(1.0, 1.0)); + + if(!getBuffered2DDecomposition().empty()) + { + if(!maLastViewScaling.equal(aNewScaling)) + { + // conditions of last local decomposition have changed, delete + const_cast< ControlPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + } + + if(getBuffered2DDecomposition().empty()) + { + // remember ViewTransformation + const_cast< ControlPrimitive2D* >(this)->maLastViewScaling = aNewScaling; + } + + // use parent implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + // provide unique ID + ImplPrimitive2DIDBlock(ControlPrimitive2D, PRIMITIVE2D_ID_CONTROLPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/cropprimitive2d.cxx b/drawinglayer/source/primitive2d/cropprimitive2d.cxx new file mode 100644 index 000000000..2d780ac26 --- /dev/null +++ b/drawinglayer/source/primitive2d/cropprimitive2d.cxx @@ -0,0 +1,152 @@ +/* -*- 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 <primitive2d/cropprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + CropPrimitive2D::CropPrimitive2D( + const Primitive2DContainer& rChildren, + const basegfx::B2DHomMatrix& rTransformation, + double fCropLeft, + double fCropTop, + double fCropRight, + double fCropBottom) + : GroupPrimitive2D(rChildren), + maTransformation(rTransformation), + mfCropLeft(fCropLeft), + mfCropTop(fCropTop), + mfCropRight(fCropRight), + mfCropBottom(fCropBottom) + { + } + + bool CropPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const CropPrimitive2D& rCompare = static_cast< const CropPrimitive2D& >(rPrimitive); + + return (getTransformation() == rCompare.getTransformation() + && getCropLeft() == rCompare.getCropLeft() + && getCropTop() == rCompare.getCropTop() + && getCropRight() == rCompare.getCropRight() + && getCropBottom() == rCompare.getCropBottom()); + } + + return false; + } + + void CropPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(getChildren().empty()) + return; + + // get original object scale in unit coordinates (no mirroring) + const basegfx::B2DVector aObjectScale(basegfx::absolute(getTransformation() * basegfx::B2DVector(1.0, 1.0))); + + // we handle cropping, so when no width or no height, content will be empty, + // so only do something when we have a width and a height + if(aObjectScale.equalZero()) + return; + + // calculate crop distances in unit coordinates. They are already combined with CropScaleFactor, thus + // are relative only to object scale + const double fBackScaleX(basegfx::fTools::equalZero(aObjectScale.getX()) ? 1.0 : 1.0 / fabs(aObjectScale.getX())); + const double fBackScaleY(basegfx::fTools::equalZero(aObjectScale.getY()) ? 1.0 : 1.0 / fabs(aObjectScale.getY())); + const double fLeft(getCropLeft() * fBackScaleX); + const double fTop(getCropTop() * fBackScaleY); + const double fRight(getCropRight() * fBackScaleX); + const double fBottom(getCropBottom() * fBackScaleY); + + // calc new unit range for comparisons; the original range is the unit range + const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0); + const basegfx::B2DRange aNewRange( + -fLeft, + -fTop, + 1.0 + fRight, + 1.0 + fBottom); + + // if we have no overlap the crop has removed everything, so we do only + // have to create content if this is not the case + if(!aNewRange.overlaps(aUnitRange)) + return; + + // create new transform; first take out old transform to get + // to unit coordinates by inverting. Inverting should be flawless + // since we already checked that object size is not zero in X or Y + basegfx::B2DHomMatrix aNewTransform(getTransformation()); + + aNewTransform.invert(); + + // apply crop enlargement in unit coordinates + aNewTransform = basegfx::utils::createScaleTranslateB2DHomMatrix( + aNewRange.getRange(), + aNewRange.getMinimum()) * aNewTransform; + + // apply original transformation. Since we have manipulated the crop + // in unit coordinates we do not need to care about mirroring or + // a corrected point for a possible shear or rotation, this all comes for + // free + aNewTransform = getTransformation() * aNewTransform; + + // prepare TransformPrimitive2D with xPrimitive + const Primitive2DReference xTransformPrimitive( + new TransformPrimitive2D( + aNewTransform, + getChildren())); + + if(aUnitRange.isInside(aNewRange)) + { + // the new range is completely inside the old range (unit range), + // so no masking is needed + rVisitor.append(xTransformPrimitive); + } + else + { + // mask with original object's bounds + basegfx::B2DPolyPolygon aMaskPolyPolygon(basegfx::utils::createUnitPolygon()); + aMaskPolyPolygon.transform(getTransformation()); + + // create maskPrimitive with aMaskPolyPolygon and aMaskContentVector + const Primitive2DReference xMask( + new MaskPrimitive2D( + aMaskPolyPolygon, + Primitive2DContainer { xTransformPrimitive })); + + rVisitor.append(xMask); + } + } + + // provide unique ID + ImplPrimitive2DIDBlock(CropPrimitive2D, PRIMITIVE2D_ID_CROPPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/discretebitmapprimitive2d.cxx b/drawinglayer/source/primitive2d/discretebitmapprimitive2d.cxx new file mode 100644 index 000000000..17d6f2301 --- /dev/null +++ b/drawinglayer/source/primitive2d/discretebitmapprimitive2d.cxx @@ -0,0 +1,100 @@ +/* -*- 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/discretebitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <toolkit/helper/vclunohelper.hxx> + + +namespace drawinglayer::primitive2d +{ + void DiscreteBitmapPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // use getViewTransformation() and getObjectTransformation() from + // ObjectAndViewTransformationDependentPrimitive2D to create a BitmapPrimitive2D + // with the correct mapping + + if(getBitmapEx().IsEmpty()) + return; + + // get discrete size + const Size& rSizePixel = getBitmapEx().GetSizePixel(); + const basegfx::B2DVector aDiscreteSize(rSizePixel.Width(), rSizePixel.Height()); + + // get inverse ViewTransformation + basegfx::B2DHomMatrix aInverseViewTransformation(getViewTransformation()); + aInverseViewTransformation.invert(); + + // get size and position in world coordinates + const basegfx::B2DVector aWorldSize(aInverseViewTransformation * aDiscreteSize); + const basegfx::B2DPoint aWorldTopLeft(getObjectTransformation() * getTopLeft()); + + // build object matrix in world coordinates so that the top-left + // position remains, but possible transformations (e.g. rotations) + // in the ObjectToView stack remain and get correctly applied + basegfx::B2DHomMatrix aObjectTransform; + + aObjectTransform.set(0, 0, aWorldSize.getX()); + aObjectTransform.set(1, 1, aWorldSize.getY()); + aObjectTransform.set(0, 2, aWorldTopLeft.getX()); + aObjectTransform.set(1, 2, aWorldTopLeft.getY()); + + // get inverse ObjectTransformation + basegfx::B2DHomMatrix aInverseObjectTransformation(getObjectTransformation()); + aInverseObjectTransformation.invert(); + + // transform to object coordinate system + aObjectTransform = aInverseObjectTransformation * aObjectTransform; + + // create BitmapPrimitive2D with now object-local coordinate data + rContainer.push_back( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getBitmapEx()), + aObjectTransform)); + } + + DiscreteBitmapPrimitive2D::DiscreteBitmapPrimitive2D( + const BitmapEx& rBitmapEx, + const basegfx::B2DPoint& rTopLeft) + : ObjectAndViewTransformationDependentPrimitive2D(), + maBitmapEx(rBitmapEx), + maTopLeft(rTopLeft) + { + } + + bool DiscreteBitmapPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(ObjectAndViewTransformationDependentPrimitive2D::operator==(rPrimitive)) + { + const DiscreteBitmapPrimitive2D& rCompare = static_cast<const DiscreteBitmapPrimitive2D&>(rPrimitive); + + return (getBitmapEx() == rCompare.getBitmapEx() + && getTopLeft() == rCompare.getTopLeft()); + } + + return false; + } + + // provide unique ID + ImplPrimitive2DIDBlock(DiscreteBitmapPrimitive2D, PRIMITIVE2D_ID_DISCRETEBITMAPPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/discreteshadowprimitive2d.cxx b/drawinglayer/source/primitive2d/discreteshadowprimitive2d.cxx new file mode 100644 index 000000000..3100db147 --- /dev/null +++ b/drawinglayer/source/primitive2d/discreteshadowprimitive2d.cxx @@ -0,0 +1,316 @@ +/* -*- 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/discreteshadowprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <toolkit/helper/vclunohelper.hxx> + + +namespace drawinglayer::primitive2d +{ + DiscreteShadow::DiscreteShadow(const BitmapEx& rBitmapEx) + : maBitmapEx(rBitmapEx), + maTopLeft(), + maTop(), + maTopRight(), + maRight(), + maBottomRight(), + maBottom(), + maBottomLeft(), + maLeft() + { + const Size& rBitmapSize = getBitmapEx().GetSizePixel(); + + if(rBitmapSize.Width() != rBitmapSize.Height() || rBitmapSize.Width() < 7) + { + OSL_ENSURE(false, "DiscreteShadowPrimitive2D: wrong bitmap format (!)"); + maBitmapEx = BitmapEx(); + } + } + + const BitmapEx& DiscreteShadow::getTopLeft() const + { + if(maTopLeft.IsEmpty()) + { + const sal_Int32 nQuarter((getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const_cast< DiscreteShadow* >(this)->maTopLeft = getBitmapEx(); + const_cast< DiscreteShadow* >(this)->maTopLeft.Crop( + ::tools::Rectangle(Point(0, 0), Size((nQuarter * 2) + 1, (nQuarter * 2) + 1))); + } + + return maTopLeft; + } + + const BitmapEx& DiscreteShadow::getTop() const + { + if(maTop.IsEmpty()) + { + const sal_Int32 nQuarter((getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const_cast< DiscreteShadow* >(this)->maTop = getBitmapEx(); + const_cast< DiscreteShadow* >(this)->maTop.Crop( + ::tools::Rectangle(Point((nQuarter * 2) + 1, 0), Size(1, nQuarter))); + } + + return maTop; + } + + const BitmapEx& DiscreteShadow::getTopRight() const + { + if(maTopRight.IsEmpty()) + { + const sal_Int32 nQuarter((getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const_cast< DiscreteShadow* >(this)->maTopRight = getBitmapEx(); + const_cast< DiscreteShadow* >(this)->maTopRight.Crop( + ::tools::Rectangle(Point((nQuarter * 2) + 2, 0), Size((nQuarter * 2) + 1, (nQuarter * 2) + 1))); + } + + return maTopRight; + } + + const BitmapEx& DiscreteShadow::getRight() const + { + if(maRight.IsEmpty()) + { + const sal_Int32 nQuarter((getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const_cast< DiscreteShadow* >(this)->maRight = getBitmapEx(); + const_cast< DiscreteShadow* >(this)->maRight.Crop( + ::tools::Rectangle(Point((nQuarter * 3) + 3, (nQuarter * 2) + 1), Size(nQuarter, 1))); + } + + return maRight; + } + + const BitmapEx& DiscreteShadow::getBottomRight() const + { + if(maBottomRight.IsEmpty()) + { + const sal_Int32 nQuarter((getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const_cast< DiscreteShadow* >(this)->maBottomRight = getBitmapEx(); + const_cast< DiscreteShadow* >(this)->maBottomRight.Crop( + ::tools::Rectangle(Point((nQuarter * 2) + 2, (nQuarter * 2) + 2), Size((nQuarter * 2) + 1, (nQuarter * 2) + 1))); + } + + return maBottomRight; + } + + const BitmapEx& DiscreteShadow::getBottom() const + { + if(maBottom.IsEmpty()) + { + const sal_Int32 nQuarter((getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const_cast< DiscreteShadow* >(this)->maBottom = getBitmapEx(); + const_cast< DiscreteShadow* >(this)->maBottom.Crop( + ::tools::Rectangle(Point((nQuarter * 2) + 1, (nQuarter * 3) + 3), Size(1, nQuarter))); + } + + return maBottom; + } + + const BitmapEx& DiscreteShadow::getBottomLeft() const + { + if(maBottomLeft.IsEmpty()) + { + const sal_Int32 nQuarter((getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const_cast< DiscreteShadow* >(this)->maBottomLeft = getBitmapEx(); + const_cast< DiscreteShadow* >(this)->maBottomLeft.Crop( + ::tools::Rectangle(Point(0, (nQuarter * 2) + 2), Size((nQuarter * 2) + 1, (nQuarter * 2) + 1))); + } + + return maBottomLeft; + } + + const BitmapEx& DiscreteShadow::getLeft() const + { + if(maLeft.IsEmpty()) + { + const sal_Int32 nQuarter((getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const_cast< DiscreteShadow* >(this)->maLeft = getBitmapEx(); + const_cast< DiscreteShadow* >(this)->maLeft.Crop( + ::tools::Rectangle(Point(0, (nQuarter * 2) + 1), Size(nQuarter, 1))); + } + + return maLeft; + } + +} // end of namespace + + +namespace drawinglayer::primitive2d +{ + void DiscreteShadowPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + Primitive2DContainer xRetval; + + if(getDiscreteShadow().getBitmapEx().IsEmpty()) + return; + + const sal_Int32 nQuarter((getDiscreteShadow().getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const basegfx::B2DVector aScale(getTransform() * basegfx::B2DVector(1.0, 1.0)); + const double fSingleX(getDiscreteUnit() / aScale.getX()); + const double fSingleY(getDiscreteUnit() / aScale.getY()); + const double fBorderX(fSingleX * nQuarter); + const double fBorderY(fSingleY * nQuarter); + const double fBigLenX((fBorderX * 2.0) + fSingleX); + const double fBigLenY((fBorderY * 2.0) + fSingleY); + + xRetval.resize(8); + + // TopLeft + xRetval[0] = Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getTopLeft()), + basegfx::utils::createScaleTranslateB2DHomMatrix( + fBigLenX, + fBigLenY, + -fBorderX, + -fBorderY))); + + // Top + xRetval[1] = Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getTop()), + basegfx::utils::createScaleTranslateB2DHomMatrix( + 1.0 - (2.0 * (fBorderX + fSingleX)) + fSingleX, + fBorderY, + fBorderX + fSingleX, + -fBorderY))); + + // TopRight + xRetval[2] = Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getTopRight()), + basegfx::utils::createScaleTranslateB2DHomMatrix( + fBigLenX, + fBigLenY, + 1.0 - fBorderX, + -fBorderY))); + + // Right + xRetval[3] = Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getRight()), + basegfx::utils::createScaleTranslateB2DHomMatrix( + fBorderX, + 1.0 - (2.0 * (fBorderY + fSingleY)) + fSingleY, + 1.0 + fSingleX, + fBorderY + fSingleY))); + + // BottomRight + xRetval[4] = Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getBottomRight()), + basegfx::utils::createScaleTranslateB2DHomMatrix( + fBigLenX, + fBigLenY, + 1.0 - (fBorderX + fSingleX) + fSingleX, + 1.0 - (fBorderY + fSingleY) + fSingleY))); + + // Bottom + xRetval[5] = Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getBottom()), + basegfx::utils::createScaleTranslateB2DHomMatrix( + 1.0 - (2.0 * (fBorderX + fSingleX)) + fSingleX, + fBorderY, + fBorderX + fSingleX, + 1.0 + fSingleY))); + + // BottomLeft + xRetval[6] = Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getBottomLeft()), + basegfx::utils::createScaleTranslateB2DHomMatrix( + fBigLenX, + fBigLenY, + -fBorderX, + 1.0 - fBorderY))); + + // Left + xRetval[7] = Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getDiscreteShadow().getLeft()), + basegfx::utils::createScaleTranslateB2DHomMatrix( + fBorderX, + 1.0 - (2.0 * (fBorderY + fSingleY)) + fSingleY, + -fBorderX, + fBorderY + fSingleY))); + + // put all in object transformation to get to target positions + rContainer.push_back( + new TransformPrimitive2D( + getTransform(), + xRetval)); + } + + DiscreteShadowPrimitive2D::DiscreteShadowPrimitive2D( + const basegfx::B2DHomMatrix& rTransform, + const DiscreteShadow& rDiscreteShadow) + : DiscreteMetricDependentPrimitive2D(), + maTransform(rTransform), + maDiscreteShadow(rDiscreteShadow) + { + } + + bool DiscreteShadowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive)) + { + const DiscreteShadowPrimitive2D& rCompare = static_cast<const DiscreteShadowPrimitive2D&>(rPrimitive); + + return (getTransform() == rCompare.getTransform() + && getDiscreteShadow() == rCompare.getDiscreteShadow()); + } + + return false; + } + + basegfx::B2DRange DiscreteShadowPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + if(getDiscreteShadow().getBitmapEx().IsEmpty()) + { + // no graphics without valid bitmap definition + return basegfx::B2DRange(); + } + else + { + // prepare normal objectrange + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(getTransform()); + + // extract discrete shadow size and grow + const basegfx::B2DVector aScale(rViewInformation.getViewTransformation() * basegfx::B2DVector(1.0, 1.0)); + const sal_Int32 nQuarter((getDiscreteShadow().getBitmapEx().GetSizePixel().Width() - 3) >> 2); + const double fGrowX((1.0 / aScale.getX()) * nQuarter); + const double fGrowY((1.0 / aScale.getY()) * nQuarter); + aRetval.grow(std::max(fGrowX, fGrowY)); + + return aRetval; + } + } + + // provide unique ID + ImplPrimitive2DIDBlock(DiscreteShadowPrimitive2D, PRIMITIVE2D_ID_DISCRETESHADOWPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/embedded3dprimitive2d.cxx b/drawinglayer/source/primitive2d/embedded3dprimitive2d.cxx new file mode 100644 index 000000000..e38afeb4d --- /dev/null +++ b/drawinglayer/source/primitive2d/embedded3dprimitive2d.cxx @@ -0,0 +1,148 @@ +/* -*- 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/embedded3dprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/color/bcolor.hxx> +#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/geometry/viewinformation3d.hxx> +#include <processor3d/shadow3dextractor.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + bool Embedded3DPrimitive2D::impGetShadow3D() const + { + osl::MutexGuard aGuard( m_aMutex ); + + // create on demand + if(!mbShadow3DChecked && !getChildren3D().empty()) + { + // create shadow extraction processor + processor3d::Shadow3DExtractingProcessor aShadowProcessor( + getViewInformation3D(), + getObjectTransformation(), + getLightNormal(), + getShadowSlant(), + getScene3DRange()); + + // process local primitives + aShadowProcessor.process(getChildren3D()); + + // fetch result and set checked flag + const_cast< Embedded3DPrimitive2D* >(this)->maShadowPrimitives = aShadowProcessor.getPrimitive2DSequence(); + const_cast< Embedded3DPrimitive2D* >(this)->mbShadow3DChecked = true; + } + + // return if there are shadow primitives + return !maShadowPrimitives.empty(); + } + + void Embedded3DPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + // use info to create a yellow 2d rectangle, similar to empty 3d scenes and/or groups + const basegfx::B2DRange aLocal2DRange(getB2DRange(rViewInformation)); + const basegfx::B2DPolygon aOutline(basegfx::utils::createPolygonFromRect(aLocal2DRange)); + const basegfx::BColor aYellow(1.0, 1.0, 0.0); + rContainer.push_back(new PolygonHairlinePrimitive2D(aOutline, aYellow)); + } + + Embedded3DPrimitive2D::Embedded3DPrimitive2D( + const primitive3d::Primitive3DContainer& rxChildren3D, + const basegfx::B2DHomMatrix& rObjectTransformation, + const geometry::ViewInformation3D& rViewInformation3D, + const basegfx::B3DVector& rLightNormal, + double fShadowSlant, + const basegfx::B3DRange& rScene3DRange) + : BufferedDecompositionPrimitive2D(), + mxChildren3D(rxChildren3D), + maObjectTransformation(rObjectTransformation), + maViewInformation3D(rViewInformation3D), + maLightNormal(rLightNormal), + mfShadowSlant(fShadowSlant), + maScene3DRange(rScene3DRange), + maShadowPrimitives(), + maB2DRange(), + mbShadow3DChecked(false) + { + maLightNormal.normalize(); + } + + bool Embedded3DPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const Embedded3DPrimitive2D& rCompare = static_cast< const Embedded3DPrimitive2D& >(rPrimitive); + + return (getChildren3D() == rCompare.getChildren3D() + && getObjectTransformation() == rCompare.getObjectTransformation() + && getViewInformation3D() == rCompare.getViewInformation3D() + && getLightNormal() == rCompare.getLightNormal() + && getShadowSlant() == rCompare.getShadowSlant() + && getScene3DRange() == rCompare.getScene3DRange()); + } + + return false; + } + + basegfx::B2DRange Embedded3DPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + if(maB2DRange.isEmpty()) + { + // use the 3d transformation stack to create a projection of the 3D range + basegfx::B3DRange a3DRange(getChildren3D().getB3DRange(getViewInformation3D())); + a3DRange.transform(getViewInformation3D().getObjectToView()); + + // create 2d range from projected 3d and transform with scene's object transformation + basegfx::B2DRange aNewRange; + aNewRange.expand(basegfx::B2DPoint(a3DRange.getMinX(), a3DRange.getMinY())); + aNewRange.expand(basegfx::B2DPoint(a3DRange.getMaxX(), a3DRange.getMaxY())); + aNewRange.transform(getObjectTransformation()); + + // check for 3D shadows and their 2D projections. If those exist, they need to be + // taken into account + if(impGetShadow3D()) + { + const basegfx::B2DRange aShadow2DRange(maShadowPrimitives.getB2DRange(rViewInformation)); + + if(!aShadow2DRange.isEmpty()) + { + aNewRange.expand(aShadow2DRange); + } + } + + // assign to buffered value + const_cast< Embedded3DPrimitive2D* >(this)->maB2DRange = aNewRange; + } + + return maB2DRange; + } + + // provide unique ID + ImplPrimitive2DIDBlock(Embedded3DPrimitive2D, PRIMITIVE2D_ID_EMBEDDED3DPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/epsprimitive2d.cxx b/drawinglayer/source/primitive2d/epsprimitive2d.cxx new file mode 100644 index 000000000..b519547c0 --- /dev/null +++ b/drawinglayer/source/primitive2d/epsprimitive2d.cxx @@ -0,0 +1,82 @@ +/* -*- 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/epsprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/metafileprimitive2d.hxx> + +namespace drawinglayer::primitive2d +{ + void EpsPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + const GDIMetaFile& rSubstituteContent = getMetaFile(); + + if( rSubstituteContent.GetActionSize() ) + { + // the default decomposition will use the Metafile replacement visualisation. + // To really use the Eps data, a renderer has to know and interpret this primitive + // directly. + + rContainer.push_back( + new MetafilePrimitive2D( + getEpsTransform(), + rSubstituteContent)); + } + } + + EpsPrimitive2D::EpsPrimitive2D( + const basegfx::B2DHomMatrix& rEpsTransform, + const GfxLink& rGfxLink, + const GDIMetaFile& rMetaFile) + : BufferedDecompositionPrimitive2D(), + maEpsTransform(rEpsTransform), + maGfxLink(rGfxLink), + maMetaFile(rMetaFile) + { + } + + bool EpsPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const EpsPrimitive2D& rCompare = static_cast<const EpsPrimitive2D&>(rPrimitive); + + return (getEpsTransform() == rCompare.getEpsTransform() + && getGfxLink() == rCompare.getGfxLink() + && getMetaFile() == rCompare.getMetaFile()); + } + + return false; + } + + basegfx::B2DRange EpsPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // use own implementation to quickly answer the getB2DRange question. + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(getEpsTransform()); + + return aRetval; + } + + // provide unique ID + ImplPrimitive2DIDBlock(EpsPrimitive2D, PRIMITIVE2D_ID_EPSPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/fillgradientprimitive2d.cxx b/drawinglayer/source/primitive2d/fillgradientprimitive2d.cxx new file mode 100644 index 000000000..fba740e83 --- /dev/null +++ b/drawinglayer/source/primitive2d/fillgradientprimitive2d.cxx @@ -0,0 +1,333 @@ +/* -*- 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/fillgradientprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <texture/texture.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void FillGradientPrimitive2D::generateMatricesAndColors( + std::vector< drawinglayer::texture::B2DHomMatrixAndBColor >& rEntries, + basegfx::BColor& rOuterColor) const + { + rEntries.clear(); + + // make sure steps is not too high/low + const basegfx::BColor aStart(getFillGradient().getStartColor()); + const basegfx::BColor aEnd(getFillGradient().getEndColor()); + const sal_uInt32 nMaxSteps(sal_uInt32((aStart.getMaximumDistance(aEnd) * 127.5) + 0.5)); + sal_uInt32 nSteps(getFillGradient().getSteps()); + + if(nSteps == 0) + { + nSteps = nMaxSteps; + } + + if(nSteps < 2) + { + nSteps = 2; + } + + if(nSteps > nMaxSteps) + { + nSteps = nMaxSteps; + } + + nSteps = std::max(sal_uInt32(1), nSteps); + + switch(getFillGradient().getStyle()) + { + case attribute::GradientStyle::Linear: + { + texture::GeoTexSvxGradientLinear aGradient( + getDefinitionRange(), + getOutputRange(), + aStart, + aEnd, + nSteps, + getFillGradient().getBorder(), + getFillGradient().getAngle()); + aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + break; + } + case attribute::GradientStyle::Axial: + { + texture::GeoTexSvxGradientAxial aGradient( + getDefinitionRange(), + getOutputRange(), + aStart, + aEnd, + nSteps, + getFillGradient().getBorder(), + getFillGradient().getAngle()); + aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + break; + } + case attribute::GradientStyle::Radial: + { + texture::GeoTexSvxGradientRadial aGradient( + getDefinitionRange(), + aStart, + aEnd, + nSteps, + getFillGradient().getBorder(), + getFillGradient().getOffsetX(), + getFillGradient().getOffsetY()); + aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + break; + } + case attribute::GradientStyle::Elliptical: + { + texture::GeoTexSvxGradientElliptical aGradient( + getDefinitionRange(), + aStart, + aEnd, + nSteps, + getFillGradient().getBorder(), + getFillGradient().getOffsetX(), + getFillGradient().getOffsetY(), + getFillGradient().getAngle()); + aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + break; + } + case attribute::GradientStyle::Square: + { + texture::GeoTexSvxGradientSquare aGradient( + getDefinitionRange(), + aStart, + aEnd, + nSteps, + getFillGradient().getBorder(), + getFillGradient().getOffsetX(), + getFillGradient().getOffsetY(), + getFillGradient().getAngle()); + aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + break; + } + case attribute::GradientStyle::Rect: + { + texture::GeoTexSvxGradientRect aGradient( + getDefinitionRange(), + aStart, + aEnd, + nSteps, + getFillGradient().getBorder(), + getFillGradient().getOffsetX(), + getFillGradient().getOffsetY(), + getFillGradient().getAngle()); + aGradient.appendTransformationsAndColors(rEntries, rOuterColor); + break; + } + } + } + + void FillGradientPrimitive2D::createOverlappingFill( + Primitive2DContainer& rContainer, + const std::vector< drawinglayer::texture::B2DHomMatrixAndBColor >& rEntries, + const basegfx::BColor& rOuterColor, + const basegfx::B2DPolygon& rUnitPolygon) const + { + // create solid fill with outmost color + rContainer.push_back( + new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect(getOutputRange())), + rOuterColor)); + + // create solid fill steps + for(size_t a(0); a < rEntries.size(); a++) + { + // create part polygon + basegfx::B2DPolygon aNewPoly(rUnitPolygon); + + aNewPoly.transform(rEntries[a].maB2DHomMatrix); + + // create solid fill + rContainer.push_back( + new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aNewPoly), + rEntries[a].maBColor)); + } + } + + void FillGradientPrimitive2D::createNonOverlappingFill( + Primitive2DContainer& rContainer, + const std::vector< drawinglayer::texture::B2DHomMatrixAndBColor >& rEntries, + const basegfx::BColor& rOuterColor, + const basegfx::B2DPolygon& rUnitPolygon) const + { + // get outmost visible range from object + basegfx::B2DRange aOutmostRange(getOutputRange()); + basegfx::B2DPolyPolygon aCombinedPolyPoly; + + if(!rEntries.empty()) + { + // extend aOutmostRange with first polygon + basegfx::B2DPolygon aFirstPoly(rUnitPolygon); + + aFirstPoly.transform(rEntries[0].maB2DHomMatrix); + aCombinedPolyPoly.append(aFirstPoly); + aOutmostRange.expand(aFirstPoly.getB2DRange()); + } + + // add outmost range to combined polypolygon (in 1st place), create first primitive + aCombinedPolyPoly.insert(0, basegfx::utils::createPolygonFromRect(aOutmostRange)); + rContainer.push_back( + new PolyPolygonColorPrimitive2D( + aCombinedPolyPoly, + rOuterColor)); + + if(rEntries.empty()) + return; + + // reuse first polygon, it's the second one + aCombinedPolyPoly.remove(0); + + for(size_t a(0); a < rEntries.size() - 1; a++) + { + // create next inner polygon, combined with last one + basegfx::B2DPolygon aNextPoly(rUnitPolygon); + + aNextPoly.transform(rEntries[a + 1].maB2DHomMatrix); + aCombinedPolyPoly.append(aNextPoly); + + // create primitive with correct color + rContainer.push_back( + new PolyPolygonColorPrimitive2D( + aCombinedPolyPoly, + rEntries[a].maBColor)); + + // reuse inner polygon, it's the 2nd one + aCombinedPolyPoly.remove(0); + } + + // add last inner polygon with last color + rContainer.push_back( + new PolyPolygonColorPrimitive2D( + aCombinedPolyPoly, + rEntries[rEntries.size() - 1].maBColor)); + } + + void FillGradientPrimitive2D::createFill(Primitive2DContainer& rContainer, bool bOverlapping) const + { + // prepare shape of the Unit Polygon + basegfx::B2DPolygon aUnitPolygon; + + switch(getFillGradient().getStyle()) + { + case attribute::GradientStyle::Radial: + case attribute::GradientStyle::Elliptical: + { + aUnitPolygon = basegfx::utils::createPolygonFromCircle(basegfx::B2DPoint(0.0, 0.0), 1.0); + break; + } + default: // GradientStyle::Linear, attribute::GradientStyle::Axial, attribute::GradientStyle::Square, attribute::GradientStyle::Rect + { + aUnitPolygon = basegfx::utils::createPolygonFromRect(basegfx::B2DRange(-1.0, -1.0, 1.0, 1.0)); + break; + } + } + + // get the transform matrices and colors (where colors + // will have one more entry that matrices) + std::vector< drawinglayer::texture::B2DHomMatrixAndBColor > aEntries; + basegfx::BColor aOuterColor; + + generateMatricesAndColors(aEntries, aOuterColor); + + if(bOverlapping) + { + createOverlappingFill(rContainer, aEntries, aOuterColor, aUnitPolygon); + } + else + { + createNonOverlappingFill(rContainer, aEntries, aOuterColor, aUnitPolygon); + } + } + + void FillGradientPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // default creates overlapping fill which works with AntiAliasing and without. + // The non-overlapping version does not create single filled polygons, but + // PolyPolygons where each one describes a 'ring' for the gradient such + // that the rings will not overlap. This is useful for the old XOR-paint + // 'trick' of VCL which is recorded in Metafiles; so this version may be + // used from the MetafilePrimitive2D in its decomposition. + + if(!getFillGradient().isDefault()) + { + createFill(rContainer, /*bOverlapping*/true); + } + } + + FillGradientPrimitive2D::FillGradientPrimitive2D( + const basegfx::B2DRange& rOutputRange, + const attribute::FillGradientAttribute& rFillGradient) + : BufferedDecompositionPrimitive2D(), + maOutputRange(rOutputRange), + maDefinitionRange(rOutputRange), + maFillGradient(rFillGradient) + { + } + + FillGradientPrimitive2D::FillGradientPrimitive2D( + const basegfx::B2DRange& rOutputRange, + const basegfx::B2DRange& rDefinitionRange, + const attribute::FillGradientAttribute& rFillGradient) + : BufferedDecompositionPrimitive2D(), + maOutputRange(rOutputRange), + maDefinitionRange(rDefinitionRange), + maFillGradient(rFillGradient) + { + } + + bool FillGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const FillGradientPrimitive2D& rCompare = static_cast<const FillGradientPrimitive2D&>(rPrimitive); + + return (getOutputRange() == rCompare.getOutputRange() + && getDefinitionRange() == rCompare.getDefinitionRange() + && getFillGradient() == rCompare.getFillGradient()); + } + + return false; + } + + basegfx::B2DRange FillGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // return the geometrically visible area + return getOutputRange(); + } + + // provide unique ID + ImplPrimitive2DIDBlock(FillGradientPrimitive2D, PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/fillgraphicprimitive2d.cxx b/drawinglayer/source/primitive2d/fillgraphicprimitive2d.cxx new file mode 100644 index 000000000..1bfc47940 --- /dev/null +++ b/drawinglayer/source/primitive2d/fillgraphicprimitive2d.cxx @@ -0,0 +1,128 @@ +/* -*- 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/fillgraphicprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <texture/texture.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <primitive2d/graphicprimitivehelper2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void FillGraphicPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + const attribute::FillGraphicAttribute& rAttribute = getFillGraphic(); + + if(rAttribute.isDefault()) + return; + + const Graphic& rGraphic = rAttribute.getGraphic(); + + if(!(GraphicType::Bitmap == rGraphic.GetType() || GraphicType::GdiMetafile == rGraphic.GetType())) + return; + + const Size aSize(rGraphic.GetPrefSize()); + + if(!(aSize.Width() && aSize.Height())) + return; + + // we have a graphic (bitmap or metafile) with some size + if(rAttribute.getTiling()) + { + // get object range and create tiling matrices + std::vector< basegfx::B2DHomMatrix > aMatrices; + texture::GeoTexSvxTiled aTiling( + rAttribute.getGraphicRange(), + rAttribute.getOffsetX(), + rAttribute.getOffsetY()); + + // get matrices and realloc retval + aTiling.appendTransformations(aMatrices); + + // prepare content primitive + Primitive2DContainer xSeq; + create2DDecompositionOfGraphic(xSeq, + rGraphic, + basegfx::B2DHomMatrix()); + + for(size_t a(0); a < aMatrices.size(); a++) + { + rContainer.push_back(new TransformPrimitive2D( + getTransformation() * aMatrices[a], + xSeq)); + } + } + else + { + // add graphic without tiling + const basegfx::B2DHomMatrix aObjectTransform( + getTransformation() * basegfx::utils::createScaleTranslateB2DHomMatrix( + rAttribute.getGraphicRange().getRange(), + rAttribute.getGraphicRange().getMinimum())); + + create2DDecompositionOfGraphic(rContainer, + rGraphic, + aObjectTransform); + } + } + + FillGraphicPrimitive2D::FillGraphicPrimitive2D( + const basegfx::B2DHomMatrix& rTransformation, + const attribute::FillGraphicAttribute& rFillGraphic) + : BufferedDecompositionPrimitive2D(), + maTransformation(rTransformation), + maFillGraphic(rFillGraphic) + { + } + + bool FillGraphicPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const FillGraphicPrimitive2D& rCompare = static_cast< const FillGraphicPrimitive2D& >(rPrimitive); + + return (getTransformation() == rCompare.getTransformation() + && getFillGraphic() == rCompare.getFillGraphic()); + } + + return false; + } + + basegfx::B2DRange FillGraphicPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // return range of it + basegfx::B2DPolygon aPolygon(basegfx::utils::createUnitPolygon()); + aPolygon.transform(getTransformation()); + + return basegfx::utils::getRange(aPolygon); + } + + // provide unique ID + ImplPrimitive2DIDBlock(FillGraphicPrimitive2D, PRIMITIVE2D_ID_FILLGRAPHICPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/fillhatchprimitive2d.cxx b/drawinglayer/source/primitive2d/fillhatchprimitive2d.cxx new file mode 100644 index 000000000..9c4ef03bf --- /dev/null +++ b/drawinglayer/source/primitive2d/fillhatchprimitive2d.cxx @@ -0,0 +1,198 @@ +/* -*- 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/fillhatchprimitive2d.hxx> +#include <texture/texture.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void FillHatchPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(getFillHatch().isDefault()) + return; + + // create hatch + const basegfx::BColor aHatchColor(getFillHatch().getColor()); + const double fAngle(getFillHatch().getAngle()); + std::vector< basegfx::B2DHomMatrix > aMatrices; + double fDistance(getFillHatch().getDistance()); + const bool bAdaptDistance(0 != getFillHatch().getMinimalDiscreteDistance()); + + // #i120230# evtl. adapt distance + if(bAdaptDistance) + { + const double fDiscreteDistance(getFillHatch().getDistance() / getDiscreteUnit()); + + if(fDiscreteDistance < static_cast<double>(getFillHatch().getMinimalDiscreteDistance())) + { + fDistance = static_cast<double>(getFillHatch().getMinimalDiscreteDistance()) * getDiscreteUnit(); + } + } + + // get hatch transformations + switch(getFillHatch().getStyle()) + { + case attribute::HatchStyle::Triple: + { + // rotated 45 degrees + texture::GeoTexSvxHatch aHatch( + getDefinitionRange(), + getOutputRange(), + fDistance, + fAngle - F_PI4); + + aHatch.appendTransformations(aMatrices); + + [[fallthrough]]; + } + case attribute::HatchStyle::Double: + { + // rotated 90 degrees + texture::GeoTexSvxHatch aHatch( + getDefinitionRange(), + getOutputRange(), + fDistance, + fAngle - F_PI2); + + aHatch.appendTransformations(aMatrices); + + [[fallthrough]]; + } + case attribute::HatchStyle::Single: + { + // angle as given + texture::GeoTexSvxHatch aHatch( + getDefinitionRange(), + getOutputRange(), + fDistance, + fAngle); + + aHatch.appendTransformations(aMatrices); + } + } + + // prepare return value + const bool bFillBackground(getFillHatch().isFillBackground()); + + // evtl. create filled background + if(bFillBackground) + { + // create primitive for background + rContainer.push_back( + new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect(getOutputRange())), getBColor())); + } + + // create primitives + const basegfx::B2DPoint aStart(0.0, 0.0); + const basegfx::B2DPoint aEnd(1.0, 0.0); + + for(size_t a(0); a < aMatrices.size(); a++) + { + const basegfx::B2DHomMatrix& rMatrix = aMatrices[a]; + basegfx::B2DPolygon aNewLine; + + aNewLine.append(rMatrix * aStart); + aNewLine.append(rMatrix * aEnd); + + // create hairline + rContainer.push_back(new PolygonHairlinePrimitive2D(aNewLine, aHatchColor)); + } + } + + FillHatchPrimitive2D::FillHatchPrimitive2D( + const basegfx::B2DRange& rOutputRange, + const basegfx::BColor& rBColor, + const attribute::FillHatchAttribute& rFillHatch) + : DiscreteMetricDependentPrimitive2D(), + maOutputRange(rOutputRange), + maDefinitionRange(rOutputRange), + maFillHatch(rFillHatch), + maBColor(rBColor) + { + } + + FillHatchPrimitive2D::FillHatchPrimitive2D( + const basegfx::B2DRange& rOutputRange, + const basegfx::B2DRange& rDefinitionRange, + const basegfx::BColor& rBColor, + const attribute::FillHatchAttribute& rFillHatch) + : DiscreteMetricDependentPrimitive2D(), + maOutputRange(rOutputRange), + maDefinitionRange(rDefinitionRange), + maFillHatch(rFillHatch), + maBColor(rBColor) + { + } + + bool FillHatchPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive)) + { + const FillHatchPrimitive2D& rCompare = static_cast<const FillHatchPrimitive2D&>(rPrimitive); + + return (getOutputRange() == rCompare.getOutputRange() + && getDefinitionRange() == rCompare.getDefinitionRange() + && getFillHatch() == rCompare.getFillHatch() + && getBColor() == rCompare.getBColor()); + } + + return false; + } + + basegfx::B2DRange FillHatchPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // return the geometrically visible area + return getOutputRange(); + } + + void FillHatchPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + bool bAdaptDistance(0 != getFillHatch().getMinimalDiscreteDistance()); + + if(bAdaptDistance) + { + // behave view-dependent + DiscreteMetricDependentPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + else + { + // behave view-independent + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + } + + // provide unique ID + ImplPrimitive2DIDBlock(FillHatchPrimitive2D, PRIMITIVE2D_ID_FILLHATCHPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/glowprimitive2d.cxx b/drawinglayer/source/primitive2d/glowprimitive2d.cxx new file mode 100644 index 000000000..fb6a599ab --- /dev/null +++ b/drawinglayer/source/primitive2d/glowprimitive2d.cxx @@ -0,0 +1,68 @@ +/* -*- 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/glowprimitive2d.hxx> +#include <basegfx/color/bcolormodifier.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + +#include <sal/log.hxx> +#include <memory> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ +GlowPrimitive2D::GlowPrimitive2D(const Color& rGlowColor, double fRadius, + const Primitive2DContainer& rChildren) + : GroupPrimitive2D(rChildren) + , maGlowColor(rGlowColor) + , mfGlowRadius(fRadius) +{ +} + +bool GlowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BasePrimitive2D::operator==(rPrimitive)) + { + const GlowPrimitive2D& rCompare = static_cast<const GlowPrimitive2D&>(rPrimitive); + + return (getGlowRadius() == rCompare.getGlowRadius() + && getGlowColor() == rCompare.getGlowColor()); + } + + return false; +} + +basegfx::B2DRange +GlowPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const +{ + basegfx::B2DRange aRetval(GroupPrimitive2D::getB2DRange(rViewInformation)); + // We need additional space for the glow from all sides + aRetval.grow(getGlowRadius()); + return aRetval; +} + +// provide unique ID +ImplPrimitive2DIDBlock(GlowPrimitive2D, PRIMITIVE2D_ID_GLOWPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/graphicprimitive2d.cxx b/drawinglayer/source/primitive2d/graphicprimitive2d.cxx new file mode 100644 index 000000000..f86b1585b --- /dev/null +++ b/drawinglayer/source/primitive2d/graphicprimitive2d.cxx @@ -0,0 +1,218 @@ +/* -*- 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 <algorithm> + +#include <drawinglayer/primitive2d/graphicprimitive2d.hxx> +#include <primitive2d/cropprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <primitive2d/graphicprimitivehelper2d.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + +namespace drawinglayer::primitive2d +{ +void GraphicPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, + const geometry::ViewInformation2D&) const +{ + if (255 == getGraphicAttr().GetTransparency()) + { + // content is invisible, done + return; + } + + // do not apply mirroring from GraphicAttr to the Metafile by calling + // GetTransformedGraphic, this will try to mirror the Metafile using Scale() + // at the Metafile. This again calls Scale at the single MetaFile actions, + // but this implementation never worked. I reworked that implementations, + // but for security reasons i will try not to use it. + basegfx::B2DHomMatrix aTransform(getTransform()); + + if (getGraphicAttr().IsMirrored()) + { + // content needs mirroring + const bool bHMirr(getGraphicAttr().GetMirrorFlags() & BmpMirrorFlags::Horizontal); + const bool bVMirr(getGraphicAttr().GetMirrorFlags() & BmpMirrorFlags::Vertical); + + // mirror by applying negative scale to the unit primitive and + // applying the object transformation on it. + aTransform + = basegfx::utils::createScaleB2DHomMatrix(bHMirr ? -1.0 : 1.0, bVMirr ? -1.0 : 1.0); + aTransform.translate(bHMirr ? 1.0 : 0.0, bVMirr ? 1.0 : 0.0); + aTransform = getTransform() * aTransform; + } + + // Get transformed graphic. Suppress rotation and cropping, only filtering is needed + // here (and may be replaced later on). Cropping is handled below as mask primitive (if set). + // Also need to suppress mirroring, it is part of the transformation now (see above). + // Also move transparency handling to embedding to a UnifiedTransparencePrimitive2D; do + // that by remembering original transparency and applying that later if needed + GraphicAttr aSuppressGraphicAttr(getGraphicAttr()); + + aSuppressGraphicAttr.SetCrop(0, 0, 0, 0); + aSuppressGraphicAttr.SetRotation(0); + aSuppressGraphicAttr.SetMirrorFlags(BmpMirrorFlags::NONE); + aSuppressGraphicAttr.SetTransparency(0); + + const GraphicObject& rGraphicObject = getGraphicObject(); + Graphic aTransformedGraphic(rGraphicObject.GetGraphic()); + const bool isBitmap(GraphicType::Bitmap == aTransformedGraphic.GetType() + && !aTransformedGraphic.getVectorGraphicData()); + const bool isAdjusted(getGraphicAttr().IsAdjusted()); + const bool isDrawMode(GraphicDrawMode::Standard != getGraphicAttr().GetDrawMode()); + + if (isBitmap && (isAdjusted || isDrawMode)) + { + // the pure primitive solution with the color modifiers works well, too, but when + // it is a bitmap graphic the old modification currently is faster; so use it here + // instead of creating all as in create2DColorModifierEmbeddingsAsNeeded (see below). + // Still, crop, rotation, mirroring and transparency is handled by primitives already + // (see above). + // This could even be done when vector graphic, but we explicitly want to have the + // pure primitive solution for this; this will allow vector graphics to stay vector + // graphics, independent from the color filtering stuff. This will enhance e.g. + // SVG and print quality while reducing data size at the same time. + // The other way around the old modifications when only used on already bitmap objects + // will not lose any quality. + aTransformedGraphic = rGraphicObject.GetTransformedGraphic(&aSuppressGraphicAttr); + + // reset GraphicAttr after use to not apply double + aSuppressGraphicAttr = GraphicAttr(); + } + + // create sub-content; helper takes care of correct handling of + // bitmap, svg or metafile content + Primitive2DContainer aRetval; + create2DDecompositionOfGraphic(aRetval, aTransformedGraphic, aTransform); + + if (aRetval.empty()) + { + // content is invisible, done + return; + } + + if (isAdjusted || isDrawMode) + { + // embed to needed ModifiedColorPrimitive2D's if necessary. Do this for + // adjustments and draw mode specials + aRetval = create2DColorModifierEmbeddingsAsNeeded( + aRetval, aSuppressGraphicAttr.GetDrawMode(), + std::clamp(aSuppressGraphicAttr.GetLuminance() * 0.01, -1.0, 1.0), + std::clamp(aSuppressGraphicAttr.GetContrast() * 0.01, -1.0, 1.0), + std::clamp(aSuppressGraphicAttr.GetChannelR() * 0.01, -1.0, 1.0), + std::clamp(aSuppressGraphicAttr.GetChannelG() * 0.01, -1.0, 1.0), + std::clamp(aSuppressGraphicAttr.GetChannelB() * 0.01, -1.0, 1.0), + std::clamp(aSuppressGraphicAttr.GetGamma(), 0.0, 10.0), + aSuppressGraphicAttr.IsInvert()); + + if (aRetval.empty()) + { + // content is invisible, done + return; + } + } + + if (getGraphicAttr().IsTransparent()) + { + // check for transparency + const double fTransparency( + std::clamp(getGraphicAttr().GetTransparency() * (1.0 / 255.0), 0.0, 1.0)); + + if (!basegfx::fTools::equalZero(fTransparency)) + { + const Primitive2DReference aUnifiedTransparence( + new UnifiedTransparencePrimitive2D(aRetval, fTransparency)); + + aRetval = Primitive2DContainer{ aUnifiedTransparence }; + } + } + + if (getGraphicAttr().IsCropped()) + { + // check for cropping + // calculate scalings between real image size and logic object size. This + // is necessary since the crop values are relative to original bitmap size + const basegfx::B2DVector aObjectScale(aTransform * basegfx::B2DVector(1.0, 1.0)); + const basegfx::B2DVector aCropScaleFactor(rGraphicObject.calculateCropScaling( + aObjectScale.getX(), aObjectScale.getY(), getGraphicAttr().GetLeftCrop(), + getGraphicAttr().GetTopCrop(), getGraphicAttr().GetRightCrop(), + getGraphicAttr().GetBottomCrop())); + + // embed content in cropPrimitive + Primitive2DReference xPrimitive(new CropPrimitive2D( + aRetval, aTransform, getGraphicAttr().GetLeftCrop() * aCropScaleFactor.getX(), + getGraphicAttr().GetTopCrop() * aCropScaleFactor.getY(), + getGraphicAttr().GetRightCrop() * aCropScaleFactor.getX(), + getGraphicAttr().GetBottomCrop() * aCropScaleFactor.getY())); + + aRetval = Primitive2DContainer{ xPrimitive }; + } + + rContainer.insert(rContainer.end(), aRetval.begin(), aRetval.end()); +} + +GraphicPrimitive2D::GraphicPrimitive2D(const basegfx::B2DHomMatrix& rTransform, + const GraphicObject& rGraphicObject, + const GraphicAttr& rGraphicAttr) + : BufferedDecompositionPrimitive2D() + , maTransform(rTransform) + , maGraphicObject(rGraphicObject) + , maGraphicAttr(rGraphicAttr) +{ +} + +GraphicPrimitive2D::GraphicPrimitive2D(const basegfx::B2DHomMatrix& rTransform, + const GraphicObject& rGraphicObject) + : BufferedDecompositionPrimitive2D() + , maTransform(rTransform) + , maGraphicObject(rGraphicObject) + , maGraphicAttr() +{ +} + +bool GraphicPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const GraphicPrimitive2D& rCompare = static_cast<const GraphicPrimitive2D&>(rPrimitive); + + return (getTransform() == rCompare.getTransform() + && getGraphicObject() == rCompare.getGraphicObject() + && getGraphicAttr() == rCompare.getGraphicAttr()); + } + + return false; +} + +basegfx::B2DRange +GraphicPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const +{ + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(getTransform()); + return aRetval; +} + +// provide unique ID +ImplPrimitive2DIDBlock(GraphicPrimitive2D, PRIMITIVE2D_ID_GRAPHICPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/graphicprimitivehelper2d.cxx b/drawinglayer/source/primitive2d/graphicprimitivehelper2d.cxx new file mode 100644 index 000000000..9c441dc0d --- /dev/null +++ b/drawinglayer/source/primitive2d/graphicprimitivehelper2d.cxx @@ -0,0 +1,699 @@ +/* -*- 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 <algorithm> + +#include <primitive2d/graphicprimitivehelper2d.hxx> +#include <drawinglayer/animation/animationtiming.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/animatedprimitive2d.hxx> +#include <drawinglayer/primitive2d/metafileprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/numeric/ftools.hxx> + +// helper class for animated graphics + +#include <vcl/animate/Animation.hxx> +#include <vcl/graph.hxx> +#include <vcl/virdev.hxx> +#include <vcl/svapp.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/skia/SkiaHelper.hxx> + +namespace drawinglayer::primitive2d +{ + namespace { + + class AnimatedGraphicPrimitive2D : public AnimatedSwitchPrimitive2D + { + private: + /// the geometric definition + basegfx::B2DHomMatrix maTransform; + + /** the Graphic with all its content possibilities, here only + animated is allowed and gets checked by isValidData(). + an instance of Graphic is used here since it's ref-counted + and thus a safe copy for now + */ + const Graphic maGraphic; + + /// local animation processing data, excerpt from maGraphic + ::Animation maAnimation; + + /// the on-demand created VirtualDevices for frame creation + ScopedVclPtrInstance< VirtualDevice > maVirtualDevice; + ScopedVclPtrInstance< VirtualDevice > maVirtualDeviceMask; + + // index of the next frame that would be regularly prepared + sal_uInt32 mnNextFrameToPrepare; + + /// buffering of 1st frame (always active) + Primitive2DReference maBufferedFirstFrame; + + /// buffering of all frames + Primitive2DContainer maBufferedPrimitives; + bool mbBufferingAllowed; + + /// set if the animation is huge so that just always the next frame + /// is used instead of using timing + bool mbHugeSize; + + /// helper methods + bool isValidData() const + { + return (GraphicType::Bitmap == maGraphic.GetType() + && maGraphic.IsAnimated() + && maAnimation.Count()); + } + + void ensureVirtualDeviceSizeAndState() + { + if (!isValidData()) + return; + + const Size aCurrent(maVirtualDevice->GetOutputSizePixel()); + const Size aTarget(maAnimation.GetDisplaySizePixel()); + + if (aCurrent != aTarget) + { + maVirtualDevice->EnableMapMode(false); + maVirtualDeviceMask->EnableMapMode(false); + maVirtualDevice->SetOutputSizePixel(aTarget); + maVirtualDeviceMask->SetOutputSizePixel(aTarget); + } + + maVirtualDevice->Erase(); + maVirtualDeviceMask->Erase(); + const ::tools::Rectangle aRect(Point(0, 0), aTarget); + maVirtualDeviceMask->SetFillColor(COL_BLACK); + maVirtualDeviceMask->SetLineColor(); + maVirtualDeviceMask->DrawRect(aRect); + } + + sal_uInt32 generateStepTime(sal_uInt32 nIndex) const + { + const AnimationBitmap& rAnimationBitmap = maAnimation.Get(sal_uInt16(nIndex)); + sal_uInt32 nWaitTime(rAnimationBitmap.mnWait * 10); + + // Take care of special value for MultiPage TIFFs. ATM these shall just + // show their first page. Later we will offer some switching when object + // is selected. + if (ANIMATION_TIMEOUT_ON_CLICK == rAnimationBitmap.mnWait) + { + // ATM the huge value would block the timer, so + // use a long time to show first page (whole day) + nWaitTime = 100 * 60 * 60 * 24; + } + + // Bad trap: There are animated gifs with no set WaitTime (!). + // In that case use a default value. + if (0 == nWaitTime) + { + nWaitTime = 100; + } + + return nWaitTime; + } + + void createAndSetAnimationTiming() + { + if (!isValidData()) + return; + + animation::AnimationEntryLoop aAnimationLoop(maAnimation.GetLoopCount() ? maAnimation.GetLoopCount() : 0xffff); + const sal_uInt32 nCount(maAnimation.Count()); + + for (sal_uInt32 a(0); a < nCount; a++) + { + const sal_uInt32 aStepTime(generateStepTime(a)); + const animation::AnimationEntryFixed aTime(static_cast<double>(aStepTime), static_cast<double>(a) / static_cast<double>(nCount)); + + aAnimationLoop.append(aTime); + } + + animation::AnimationEntryList aAnimationEntryList; + aAnimationEntryList.append(aAnimationLoop); + + setAnimationEntry(aAnimationEntryList); + } + + Primitive2DReference createFromBuffer() const + { + // create BitmapEx by extracting from VirtualDevices + const Bitmap aMainBitmap(maVirtualDevice->GetBitmap(Point(), maVirtualDevice->GetOutputSizePixel())); + bool useAlphaMask = false; +#if defined(MACOSX) || defined(IOS) + useAlphaMask = true; +#else + // GetBitmap()-> AlphaMask is optimized with SkiaSalBitmap::InterpretAs8Bit(), 1bpp mask is not. + if( SkiaHelper::isVCLSkiaEnabled()) + useAlphaMask = true; +#endif + BitmapEx bitmap; + if( useAlphaMask ) + { + const AlphaMask aMaskBitmap(maVirtualDeviceMask->GetBitmap(Point(), maVirtualDeviceMask->GetOutputSizePixel())); + bitmap = BitmapEx(aMainBitmap, aMaskBitmap); + } + else + { + const Bitmap aMaskBitmap(maVirtualDeviceMask->GetBitmap(Point(), maVirtualDeviceMask->GetOutputSizePixel())); + bitmap = BitmapEx(aMainBitmap, aMaskBitmap); + } + + return Primitive2DReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(bitmap), + getTransform())); + } + + void checkSafeToBuffer(sal_uInt32 nIndex) + { + if (mbBufferingAllowed) + { + // all frames buffered + if (!maBufferedPrimitives.empty() && nIndex < maBufferedPrimitives.size()) + { + if (!maBufferedPrimitives[nIndex].is()) + { + maBufferedPrimitives[nIndex] = createFromBuffer(); + + // check if buffering is complete + bool bBufferingComplete(true); + + for (auto const & a: maBufferedPrimitives) + { + if (!a.is()) + { + bBufferingComplete = false; + break; + } + } + + if (bBufferingComplete) + { + maVirtualDevice.disposeAndClear(); + maVirtualDeviceMask.disposeAndClear(); + } + } + } + } + else + { + // always buffer first frame + if (0 == nIndex && !maBufferedFirstFrame.is()) + { + maBufferedFirstFrame = createFromBuffer(); + } + } + } + + void createFrame(sal_uInt32 nTarget) + { + // mnNextFrameToPrepare is the target frame to create next (which implies that + // mnNextFrameToPrepare-1 *is* currently in the VirtualDevice when + // 0 != mnNextFrameToPrepare. nTarget is the target frame. + if (!isValidData()) + return; + + if (mnNextFrameToPrepare > nTarget) + { + // we are ahead request, reset mechanism to start at frame zero + ensureVirtualDeviceSizeAndState(); + mnNextFrameToPrepare = 0; + } + + while (mnNextFrameToPrepare <= nTarget) + { + // prepare step + const AnimationBitmap& rAnimationBitmap = maAnimation.Get(sal_uInt16(mnNextFrameToPrepare)); + + switch (rAnimationBitmap.meDisposal) + { + case Disposal::Not: + { + maVirtualDevice->DrawBitmapEx(rAnimationBitmap.maPositionPixel, rAnimationBitmap.maBitmapEx); + Bitmap aMask = rAnimationBitmap.maBitmapEx.GetMask(); + + if (aMask.IsEmpty()) + { + const Point aEmpty; + const ::tools::Rectangle aRect(aEmpty, maVirtualDeviceMask->GetOutputSizePixel()); + const Wallpaper aWallpaper(COL_BLACK); + maVirtualDeviceMask->DrawWallpaper(aRect, aWallpaper); + } + else + { + BitmapEx aExpandVisibilityMask(aMask, aMask); + maVirtualDeviceMask->DrawBitmapEx(rAnimationBitmap.maPositionPixel, aExpandVisibilityMask); + } + + break; + } + case Disposal::Back: + { + // #i70772# react on no mask, for primitives, too. + const Bitmap & rMask(rAnimationBitmap.maBitmapEx.GetMask()); + const Bitmap & rContent(rAnimationBitmap.maBitmapEx.GetBitmap()); + + maVirtualDeviceMask->Erase(); + maVirtualDevice->DrawBitmap(rAnimationBitmap.maPositionPixel, rContent); + + if (rMask.IsEmpty()) + { + const ::tools::Rectangle aRect(rAnimationBitmap.maPositionPixel, rContent.GetSizePixel()); + maVirtualDeviceMask->SetFillColor(COL_BLACK); + maVirtualDeviceMask->SetLineColor(); + maVirtualDeviceMask->DrawRect(aRect); + } + else + { + BitmapEx aExpandVisibilityMask(rMask, rMask); + maVirtualDeviceMask->DrawBitmapEx(rAnimationBitmap.maPositionPixel, aExpandVisibilityMask); + } + + break; + } + case Disposal::Previous: + { + maVirtualDevice->DrawBitmapEx(rAnimationBitmap.maPositionPixel, rAnimationBitmap.maBitmapEx); + BitmapEx aExpandVisibilityMask(rAnimationBitmap.maBitmapEx.GetMask(), rAnimationBitmap.maBitmapEx.GetMask()); + maVirtualDeviceMask->DrawBitmapEx(rAnimationBitmap.maPositionPixel, aExpandVisibilityMask); + break; + } + } + + // to not waste created data, check adding to buffers + checkSafeToBuffer(mnNextFrameToPrepare); + + mnNextFrameToPrepare++; + } + } + + Primitive2DReference tryTogetFromBuffer(sal_uInt32 nIndex) const + { + if (mbBufferingAllowed) + { + // all frames buffered, check if available + if (!maBufferedPrimitives.empty() && nIndex < maBufferedPrimitives.size()) + { + if (maBufferedPrimitives[nIndex].is()) + { + return maBufferedPrimitives[nIndex]; + } + } + } + else + { + // always buffer first frame, it's sometimes requested out-of-order + if (0 == nIndex && maBufferedFirstFrame.is()) + { + return maBufferedFirstFrame; + } + } + + return Primitive2DReference(); + } + + public: + /// constructor + AnimatedGraphicPrimitive2D( + const Graphic& rGraphic, + const basegfx::B2DHomMatrix& rTransform); + + /// data read access + const basegfx::B2DHomMatrix& getTransform() const { return maTransform; } + + /// compare operator + virtual bool operator==(const BasePrimitive2D& rPrimitive) const override; + + /// override to deliver the correct expected frame dependent of timing + virtual void get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const override; + }; + + } + + AnimatedGraphicPrimitive2D::AnimatedGraphicPrimitive2D( + const Graphic& rGraphic, + const basegfx::B2DHomMatrix& rTransform) + : AnimatedSwitchPrimitive2D( + animation::AnimationEntryList(), + Primitive2DContainer(), + false), + maTransform(rTransform), + maGraphic(rGraphic), + maAnimation(rGraphic.GetAnimation()), + maVirtualDevice(*Application::GetDefaultDevice()), + maVirtualDeviceMask(*Application::GetDefaultDevice(), DeviceFormat::BITMASK), + mnNextFrameToPrepare(SAL_MAX_UINT32), + maBufferedFirstFrame(), + maBufferedPrimitives(), + mbBufferingAllowed(false), + mbHugeSize(false) + { + // initialize AnimationTiming, needed to detect which frame is requested + // in get2DDecomposition + createAndSetAnimationTiming(); + + // check if we allow buffering + if (isValidData()) + { + // allow buffering up to a size of: + // - 64 frames + // - sizes of 256x256 pixels + // This may be offered in option values if needed + static const sal_uInt64 nAllowedSize(64 * 256 * 256); + static const sal_uInt64 nHugeSize(10000000); + const Size aTarget(maAnimation.GetDisplaySizePixel()); + const sal_uInt64 nUsedSize(static_cast<sal_uInt64>(maAnimation.Count()) * aTarget.Width() * aTarget.Height()); + + if (nUsedSize < nAllowedSize) + { + mbBufferingAllowed = true; + } + + if (nUsedSize > nHugeSize) + { + mbHugeSize = true; + } + } + + // prepare buffer space + if (mbBufferingAllowed && isValidData()) + { + maBufferedPrimitives = Primitive2DContainer(maAnimation.Count()); + } + } + + bool AnimatedGraphicPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + // do not use 'GroupPrimitive2D::operator==' here, that would compare + // the children. Also do not use 'BasePrimitive2D::operator==', that would + // check the ID-Type. Since we are a simple derivation without own ID, + // use the dynamic_cast RTTI directly + const AnimatedGraphicPrimitive2D* pCompare = dynamic_cast<const AnimatedGraphicPrimitive2D*>(&rPrimitive); + + // use operator== of Graphic - if that is equal, the basic definition is equal + return (nullptr != pCompare + && getTransform() == pCompare->getTransform() + && maGraphic == pCompare->maGraphic); + } + + void AnimatedGraphicPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + if (!isValidData()) + return; + + Primitive2DReference aRetval; + const double fState(getAnimationEntry().getStateAtTime(rViewInformation.getViewTime())); + const sal_uInt32 nLen(maAnimation.Count()); + sal_uInt32 nIndex(basegfx::fround(fState * static_cast<double>(nLen))); + + // nIndex is the requested frame - it is in range [0..nLen[ + // create frame representation in VirtualDevices + if (nIndex >= nLen) + { + nIndex = nLen - 1; + } + + // check buffering shortcuts, may already be created + aRetval = tryTogetFromBuffer(nIndex); + + if (aRetval.is()) + { + rVisitor.append(aRetval); + return; + } + + // if huge size (and not the buffered 1st frame) simply + // create next frame + if (mbHugeSize && 0 != nIndex && mnNextFrameToPrepare <= nIndex) + { + nIndex = mnNextFrameToPrepare % nLen; + } + + // frame not (yet) buffered or no buffering allowed, create it + const_cast<AnimatedGraphicPrimitive2D*>(this)->createFrame(nIndex); + + // try to get from buffer again, may have been added from createFrame + aRetval = tryTogetFromBuffer(nIndex); + + if (aRetval.is()) + { + rVisitor.append(aRetval); + return; + } + + // did not work (not buffered and not 1st frame), create from buffer + aRetval = createFromBuffer(); + + rVisitor.append(aRetval); + } + +} // end of namespace + +namespace drawinglayer::primitive2d +{ + void create2DDecompositionOfGraphic( + Primitive2DContainer& rContainer, + const Graphic& rGraphic, + const basegfx::B2DHomMatrix& rTransform) + { + Primitive2DContainer aRetval; + + switch(rGraphic.GetType()) + { + case GraphicType::Bitmap : + { + if(rGraphic.IsAnimated()) + { + // prepare specialized AnimatedGraphicPrimitive2D + aRetval.resize(1); + aRetval[0] = new AnimatedGraphicPrimitive2D( + rGraphic, + rTransform); + } + else if(rGraphic.getVectorGraphicData()) + { + // embedded Vector Graphic Data fill, create embed transform + const basegfx::B2DRange& rSvgRange(rGraphic.getVectorGraphicData()->getRange()); + + if(basegfx::fTools::more(rSvgRange.getWidth(), 0.0) && basegfx::fTools::more(rSvgRange.getHeight(), 0.0)) + { + // translate back to origin, scale to unit coordinates + basegfx::B2DHomMatrix aEmbedVectorGraphic( + basegfx::utils::createTranslateB2DHomMatrix( + -rSvgRange.getMinX(), + -rSvgRange.getMinY())); + + aEmbedVectorGraphic.scale( + 1.0 / rSvgRange.getWidth(), + 1.0 / rSvgRange.getHeight()); + + // apply created object transformation + aEmbedVectorGraphic = rTransform * aEmbedVectorGraphic; + + // add Vector Graphic Data primitives embedded + aRetval.resize(1); + aRetval[0] = new TransformPrimitive2D( + aEmbedVectorGraphic, + rGraphic.getVectorGraphicData()->getPrimitive2DSequence()); + } + } + else + { + aRetval.resize(1); + aRetval[0] = new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(rGraphic.GetBitmapEx()), + rTransform); + } + + break; + } + + case GraphicType::GdiMetafile : + { + // create MetafilePrimitive2D + const GDIMetaFile& rMetafile = rGraphic.GetGDIMetaFile(); + + aRetval.resize(1); + aRetval[0] = new MetafilePrimitive2D( + rTransform, + rMetafile); + + // #i100357# find out if clipping is needed for this primitive. Unfortunately, + // there exist Metafiles who's content is bigger than the proposed PrefSize set + // at them. This is an error, but we need to work around this + const Size aMetaFilePrefSize(rMetafile.GetPrefSize()); + const Size aMetaFileRealSize( + rMetafile.GetBoundRect( + *Application::GetDefaultDevice()).GetSize()); + + if(aMetaFileRealSize.getWidth() > aMetaFilePrefSize.getWidth() + || aMetaFileRealSize.getHeight() > aMetaFilePrefSize.getHeight()) + { + // clipping needed. Embed to MaskPrimitive2D. Create children and mask polygon + basegfx::B2DPolygon aMaskPolygon(basegfx::utils::createUnitPolygon()); + aMaskPolygon.transform(rTransform); + + Primitive2DReference mask = new MaskPrimitive2D( + basegfx::B2DPolyPolygon(aMaskPolygon), + aRetval); + aRetval[0] = mask; + } + break; + } + + default: + { + // nothing to create + break; + } + } + + rContainer.insert(rContainer.end(), aRetval.begin(), aRetval.end()); + } + + Primitive2DContainer create2DColorModifierEmbeddingsAsNeeded( + const Primitive2DContainer& rChildren, + GraphicDrawMode aGraphicDrawMode, + double fLuminance, + double fContrast, + double fRed, + double fGreen, + double fBlue, + double fGamma, + bool bInvert) + { + Primitive2DContainer aRetval; + + if(rChildren.empty()) + { + // no child content, done + return aRetval; + } + + // set child content as retval; that is what will be used as child content in all + // embeddings from here + aRetval = rChildren; + + if(GraphicDrawMode::Watermark == aGraphicDrawMode) + { + // this is solved by applying fixed values additionally to luminance + // and contrast, do it here and reset DrawMode to GraphicDrawMode::Standard + // original in svtools uses: + // #define WATERMARK_LUM_OFFSET 50 + // #define WATERMARK_CON_OFFSET -70 + fLuminance = std::clamp(fLuminance + 0.5, -1.0, 1.0); + fContrast = std::clamp(fContrast - 0.7, -1.0, 1.0); + aGraphicDrawMode = GraphicDrawMode::Standard; + } + + // DrawMode (GraphicDrawMode::Watermark already handled) + switch(aGraphicDrawMode) + { + case GraphicDrawMode::Greys: + { + // convert to grey + const Primitive2DReference aPrimitiveGrey( + new ModifiedColorPrimitive2D( + aRetval, + std::make_shared<basegfx::BColorModifier_gray>())); + + aRetval = Primitive2DContainer { aPrimitiveGrey }; + break; + } + case GraphicDrawMode::Mono: + { + // convert to mono (black/white with threshold 0.5) + const Primitive2DReference aPrimitiveBlackAndWhite( + new ModifiedColorPrimitive2D( + aRetval, + std::make_shared<basegfx::BColorModifier_black_and_white>(0.5))); + + aRetval = Primitive2DContainer { aPrimitiveBlackAndWhite }; + break; + } + default: // case GraphicDrawMode::Standard: + { + assert( + aGraphicDrawMode != GraphicDrawMode::Watermark + && "OOps, GraphicDrawMode::Watermark should already be handled (see above)"); + // nothing to do + break; + } + } + + // mnContPercent, mnLumPercent, mnRPercent, mnGPercent, mnBPercent + // handled in a single call + if(!basegfx::fTools::equalZero(fLuminance) + || !basegfx::fTools::equalZero(fContrast) + || !basegfx::fTools::equalZero(fRed) + || !basegfx::fTools::equalZero(fGreen) + || !basegfx::fTools::equalZero(fBlue)) + { + const Primitive2DReference aPrimitiveRGBLuminannceContrast( + new ModifiedColorPrimitive2D( + aRetval, + std::make_shared<basegfx::BColorModifier_RGBLuminanceContrast>( + fRed, + fGreen, + fBlue, + fLuminance, + fContrast))); + + aRetval = Primitive2DContainer { aPrimitiveRGBLuminannceContrast }; + } + + // gamma (boolean) + if(!basegfx::fTools::equal(fGamma, 1.0)) + { + const Primitive2DReference aPrimitiveGamma( + new ModifiedColorPrimitive2D( + aRetval, + std::make_shared<basegfx::BColorModifier_gamma>( + fGamma))); + + aRetval = Primitive2DContainer { aPrimitiveGamma }; + } + + // invert (boolean) + if(bInvert) + { + const Primitive2DReference aPrimitiveInvert( + new ModifiedColorPrimitive2D( + aRetval, + std::make_shared<basegfx::BColorModifier_invert>())); + + aRetval = Primitive2DContainer { aPrimitiveInvert }; + } + + return aRetval; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/gridprimitive2d.cxx b/drawinglayer/source/primitive2d/gridprimitive2d.cxx new file mode 100644 index 000000000..e1d084167 --- /dev/null +++ b/drawinglayer/source/primitive2d/gridprimitive2d.cxx @@ -0,0 +1,338 @@ +/* -*- 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/gridprimitive2d.hxx> +#include <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/markerarrayprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void GridPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + if(!(!rViewInformation.getViewport().isEmpty() && getWidth() > 0.0 && getHeight() > 0.0)) + return; + + // decompose grid matrix to get logic size + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + getTransform().decompose(aScale, aTranslate, fRotate, fShearX); + + // create grid matrix which transforms from scaled logic to view + basegfx::B2DHomMatrix aRST(basegfx::utils::createShearXRotateTranslateB2DHomMatrix( + fShearX, fRotate, aTranslate.getX(), aTranslate.getY())); + aRST *= rViewInformation.getObjectToViewTransformation(); + + // get step widths + double fStepX(getWidth()); + double fStepY(getHeight()); + const double fMinimalStep(10.0); + + // guarantee a step width of 10.0 + if(basegfx::fTools::less(fStepX, fMinimalStep)) + { + fStepX = fMinimalStep; + } + + if(basegfx::fTools::less(fStepY, fMinimalStep)) + { + fStepY = fMinimalStep; + } + + // get relative distances in view coordinates + double fViewStepX((rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(fStepX, 0.0)).getLength()); + double fViewStepY((rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(0.0, fStepY)).getLength()); + double fSmallStepX(1.0), fViewSmallStepX(1.0), fSmallStepY(1.0), fViewSmallStepY(1.0); + sal_uInt32 nSmallStepsX(0), nSmallStepsY(0); + + // setup subdivisions + if(getSubdivisionsX()) + { + fSmallStepX = fStepX / getSubdivisionsX(); + fViewSmallStepX = fViewStepX / getSubdivisionsX(); + } + + if(getSubdivisionsY()) + { + fSmallStepY = fStepY / getSubdivisionsY(); + fViewSmallStepY = fViewStepY / getSubdivisionsY(); + } + + // correct step width + while(fViewStepX < getSmallestViewDistance()) + { + fViewStepX *= 2.0; + fStepX *= 2.0; + } + + while(fViewStepY < getSmallestViewDistance()) + { + fViewStepY *= 2.0; + fStepY *= 2.0; + } + + // correct small step width + if(getSubdivisionsX()) + { + while(fViewSmallStepX < getSmallestSubdivisionViewDistance()) + { + fViewSmallStepX *= 2.0; + fSmallStepX *= 2.0; + } + + nSmallStepsX = static_cast<sal_uInt32>(fStepX / fSmallStepX); + } + + if(getSubdivisionsY()) + { + while(fViewSmallStepY < getSmallestSubdivisionViewDistance()) + { + fViewSmallStepY *= 2.0; + fSmallStepY *= 2.0; + } + + nSmallStepsY = static_cast<sal_uInt32>(fStepY / fSmallStepY); + } + + // calculate extended viewport in which grid points may lie at all + basegfx::B2DRange aExtendedViewport; + + if(rViewInformation.getDiscreteViewport().isEmpty()) + { + // not set, use logic size to travel over all potential grid points + aExtendedViewport = basegfx::B2DRange(0.0, 0.0, aScale.getX(), aScale.getY()); + } + else + { + // transform unit range to discrete view + aExtendedViewport = basegfx::B2DRange(0.0, 0.0, 1.0, 1.0); + basegfx::B2DHomMatrix aTrans(rViewInformation.getObjectToViewTransformation() * getTransform()); + aExtendedViewport.transform(aTrans); + + // intersect with visible part + aExtendedViewport.intersect(rViewInformation.getDiscreteViewport()); + + if(!aExtendedViewport.isEmpty()) + { + // convert back and apply scale + aTrans.invert(); + aTrans.scale(aScale.getX(), aScale.getY()); + aExtendedViewport.transform(aTrans); + + // crop start/end in X/Y to multiples of logical step width + const double fHalfCrossSize((rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(3.0, 0.0)).getLength()); + const double fMinX(floor((aExtendedViewport.getMinX() - fHalfCrossSize) / fStepX) * fStepX); + const double fMaxX(ceil((aExtendedViewport.getMaxX() + fHalfCrossSize) / fStepX) * fStepX); + const double fMinY(floor((aExtendedViewport.getMinY() - fHalfCrossSize) / fStepY) * fStepY); + const double fMaxY(ceil((aExtendedViewport.getMaxY() + fHalfCrossSize) / fStepY) * fStepY); + + // put to aExtendedViewport and crop on object logic size + aExtendedViewport = basegfx::B2DRange( + std::max(fMinX, 0.0), + std::max(fMinY, 0.0), + std::min(fMaxX, aScale.getX()), + std::min(fMaxY, aScale.getY())); + } + } + + if(aExtendedViewport.isEmpty()) + return; + + // prepare point vectors for point and cross markers + std::vector< basegfx::B2DPoint > aPositionsPoint; + std::vector< basegfx::B2DPoint > aPositionsCross; + + for(double fX(aExtendedViewport.getMinX()); fX < aExtendedViewport.getMaxX(); fX += fStepX) + { + const bool bXZero(basegfx::fTools::equalZero(fX)); + + for(double fY(aExtendedViewport.getMinY()); fY < aExtendedViewport.getMaxY(); fY += fStepY) + { + const bool bYZero(basegfx::fTools::equalZero(fY)); + + if(!bXZero && !bYZero) + { + // get discrete position and test against 3x3 area surrounding it + // since it's a cross + const double fHalfCrossSize(3.0 * 0.5); + const basegfx::B2DPoint aViewPos(aRST * basegfx::B2DPoint(fX, fY)); + const basegfx::B2DRange aDiscreteRangeCross( + aViewPos.getX() - fHalfCrossSize, aViewPos.getY() - fHalfCrossSize, + aViewPos.getX() + fHalfCrossSize, aViewPos.getY() + fHalfCrossSize); + + if(rViewInformation.getDiscreteViewport().overlaps(aDiscreteRangeCross)) + { + const basegfx::B2DPoint aLogicPos(rViewInformation.getInverseObjectToViewTransformation() * aViewPos); + aPositionsCross.push_back(aLogicPos); + } + } + + if(getSubdivisionsX() && !bYZero) + { + double fF(fX + fSmallStepX); + + for(sal_uInt32 a(1); a < nSmallStepsX && fF < aExtendedViewport.getMaxX(); a++, fF += fSmallStepX) + { + const basegfx::B2DPoint aViewPos(aRST * basegfx::B2DPoint(fF, fY)); + + if(rViewInformation.getDiscreteViewport().isInside(aViewPos)) + { + const basegfx::B2DPoint aLogicPos(rViewInformation.getInverseObjectToViewTransformation() * aViewPos); + aPositionsPoint.push_back(aLogicPos); + } + } + } + + if(getSubdivisionsY() && !bXZero) + { + double fF(fY + fSmallStepY); + + for(sal_uInt32 a(1); a < nSmallStepsY && fF < aExtendedViewport.getMaxY(); a++, fF += fSmallStepY) + { + const basegfx::B2DPoint aViewPos(aRST * basegfx::B2DPoint(fX, fF)); + + if(rViewInformation.getDiscreteViewport().isInside(aViewPos)) + { + const basegfx::B2DPoint aLogicPos(rViewInformation.getInverseObjectToViewTransformation() * aViewPos); + aPositionsPoint.push_back(aLogicPos); + } + } + } + } + } + + // prepare return value + const sal_uInt32 nCountPoint(aPositionsPoint.size()); + const sal_uInt32 nCountCross(aPositionsCross.size()); + + // add PointArrayPrimitive2D if point markers were added + if(nCountPoint) + { + rContainer.push_back(new PointArrayPrimitive2D(aPositionsPoint, getBColor())); + } + + // add MarkerArrayPrimitive2D if cross markers were added + if(!nCountCross) + return; + + if(!getSubdivisionsX() && !getSubdivisionsY()) + { + // no subdivisions, so fall back to points at grid positions, no need to + // visualize a difference between divisions and sub-divisions + rContainer.push_back(new PointArrayPrimitive2D(aPositionsCross, getBColor())); + } + else + { + rContainer.push_back(new MarkerArrayPrimitive2D(aPositionsCross, getCrossMarker())); + } + } + + GridPrimitive2D::GridPrimitive2D( + const basegfx::B2DHomMatrix& rTransform, + double fWidth, + double fHeight, + double fSmallestViewDistance, + double fSmallestSubdivisionViewDistance, + sal_uInt32 nSubdivisionsX, + sal_uInt32 nSubdivisionsY, + const basegfx::BColor& rBColor, + const BitmapEx& rCrossMarker) + : BufferedDecompositionPrimitive2D(), + maTransform(rTransform), + mfWidth(fWidth), + mfHeight(fHeight), + mfSmallestViewDistance(fSmallestViewDistance), + mfSmallestSubdivisionViewDistance(fSmallestSubdivisionViewDistance), + mnSubdivisionsX(nSubdivisionsX), + mnSubdivisionsY(nSubdivisionsY), + maBColor(rBColor), + maCrossMarker(rCrossMarker), + maLastObjectToViewTransformation(), + maLastViewport() + { + } + + bool GridPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const GridPrimitive2D& rCompare = static_cast<const GridPrimitive2D&>(rPrimitive); + + return (getTransform() == rCompare.getTransform() + && getWidth() == rCompare.getWidth() + && getHeight() == rCompare.getHeight() + && getSmallestViewDistance() == rCompare.getSmallestViewDistance() + && getSmallestSubdivisionViewDistance() == rCompare.getSmallestSubdivisionViewDistance() + && getSubdivisionsX() == rCompare.getSubdivisionsX() + && getSubdivisionsY() == rCompare.getSubdivisionsY() + && getBColor() == rCompare.getBColor() + && getCrossMarker() == rCompare.getCrossMarker()); + } + + return false; + } + + basegfx::B2DRange GridPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + // get object's range + basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0); + aUnitRange.transform(getTransform()); + + // intersect with visible part + aUnitRange.intersect(rViewInformation.getViewport()); + + return aUnitRange; + } + + void GridPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if(!getBuffered2DDecomposition().empty()) + { + if(maLastViewport != rViewInformation.getViewport() || maLastObjectToViewTransformation != rViewInformation.getObjectToViewTransformation()) + { + // conditions of last local decomposition have changed, delete + const_cast< GridPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + } + + if(getBuffered2DDecomposition().empty()) + { + // remember ViewRange and ViewTransformation + const_cast< GridPrimitive2D* >(this)->maLastObjectToViewTransformation = rViewInformation.getObjectToViewTransformation(); + const_cast< GridPrimitive2D* >(this)->maLastViewport = rViewInformation.getViewport(); + } + + // use parent implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + // provide unique ID + ImplPrimitive2DIDBlock(GridPrimitive2D, PRIMITIVE2D_ID_GRIDPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/groupprimitive2d.cxx b/drawinglayer/source/primitive2d/groupprimitive2d.cxx new file mode 100644 index 000000000..63f21f842 --- /dev/null +++ b/drawinglayer/source/primitive2d/groupprimitive2d.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 <drawinglayer/primitive2d/groupprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + GroupPrimitive2D::GroupPrimitive2D( const Primitive2DContainer& rChildren ) + : BasePrimitive2D(), + maChildren(rChildren) + { + } + + /** The compare opertator uses the Sequence::==operator, so only checking if + the references are equal. All non-equal references are interpreted as + non-equal. + */ + bool GroupPrimitive2D::operator==( const BasePrimitive2D& rPrimitive ) const + { + if(BasePrimitive2D::operator==(rPrimitive)) + { + const GroupPrimitive2D& rCompare = static_cast< const GroupPrimitive2D& >(rPrimitive); + + return getChildren() == rCompare.getChildren(); + } + + return false; + } + + /// default: just return children, so all renderers not supporting group will use its content + void GroupPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + getChildren(rVisitor); + } + + sal_Int64 SAL_CALL GroupPrimitive2D::estimateUsage() + { + size_t nRet(0); + for (auto& it : getChildren()) + { + uno::Reference<util::XAccounting> const xAcc(it, uno::UNO_QUERY); + if (xAcc.is()) + { + nRet += xAcc->estimateUsage(); + } + } + return nRet; + } + + // provide unique ID + ImplPrimitive2DIDBlock(GroupPrimitive2D, PRIMITIVE2D_ID_GROUPPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/helplineprimitive2d.cxx b/drawinglayer/source/primitive2d/helplineprimitive2d.cxx new file mode 100644 index 000000000..86c3b88ca --- /dev/null +++ b/drawinglayer/source/primitive2d/helplineprimitive2d.cxx @@ -0,0 +1,191 @@ +/* -*- 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/helplineprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void HelplinePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + if(rViewInformation.getViewport().isEmpty() || getDirection().equalZero()) + return; + + // position to view coordinates, DashLen and DashLen in logic + const basegfx::B2DPoint aViewPosition(rViewInformation.getObjectToViewTransformation() * getPosition()); + + switch(getStyle()) + { + default : // HelplineStyle2D::Point + { + const double fViewFixValue(15.0); + basegfx::B2DVector aNormalizedDirection(getDirection()); + aNormalizedDirection.normalize(); + aNormalizedDirection *= fViewFixValue; + const basegfx::B2DPoint aStartA(aViewPosition - aNormalizedDirection); + const basegfx::B2DPoint aEndA(aViewPosition + aNormalizedDirection); + basegfx::B2DPolygon aLineA; + aLineA.append(aStartA); + aLineA.append(aEndA); + aLineA.transform(rViewInformation.getInverseObjectToViewTransformation()); + PolygonMarkerPrimitive2D* pNewA = new PolygonMarkerPrimitive2D(aLineA, getRGBColA(), getRGBColB(), getDiscreteDashLength()); + rContainer.push_back(pNewA); + + const basegfx::B2DVector aPerpendicularNormalizedDirection(basegfx::getPerpendicular(aNormalizedDirection)); + const basegfx::B2DPoint aStartB(aViewPosition - aPerpendicularNormalizedDirection); + const basegfx::B2DPoint aEndB(aViewPosition + aPerpendicularNormalizedDirection); + basegfx::B2DPolygon aLineB; + aLineB.append(aStartB); + aLineB.append(aEndB); + aLineB.transform(rViewInformation.getInverseObjectToViewTransformation()); + PolygonMarkerPrimitive2D* pNewB = new PolygonMarkerPrimitive2D(aLineB, getRGBColA(), getRGBColB(), getDiscreteDashLength()); + rContainer.push_back(pNewB); + + break; + } + case HelplineStyle2D::Line : + { + basegfx::B2DPolygon aLine; + + if(basegfx::areParallel(getDirection(), basegfx::B2DVector(1.0, 0.0))) + { + // parallel to X-Axis, get cuts with Y-Axes + const double fCutA((rViewInformation.getDiscreteViewport().getMinX() - aViewPosition.getX()) / getDirection().getX()); + const double fCutB((rViewInformation.getDiscreteViewport().getMaxX() - aViewPosition.getX()) / getDirection().getX()); + const basegfx::B2DPoint aPosA(aViewPosition + (fCutA * getDirection())); + const basegfx::B2DPoint aPosB(aViewPosition + (fCutB * getDirection())); + const bool bBothLeft(aPosA.getX() < rViewInformation.getDiscreteViewport().getMinX() && aPosB.getX() < rViewInformation.getDiscreteViewport().getMinX()); + const bool bBothRight(aPosA.getX() > rViewInformation.getDiscreteViewport().getMaxX() && aPosB.getX() < rViewInformation.getDiscreteViewport().getMaxX()); + + if(!bBothLeft && !bBothRight) + { + aLine.append(aPosA); + aLine.append(aPosB); + } + } + else + { + // get cuts with X-Axes + const double fCutA((rViewInformation.getDiscreteViewport().getMinY() - aViewPosition.getY()) / getDirection().getY()); + const double fCutB((rViewInformation.getDiscreteViewport().getMaxY() - aViewPosition.getY()) / getDirection().getY()); + const basegfx::B2DPoint aPosA(aViewPosition + (fCutA * getDirection())); + const basegfx::B2DPoint aPosB(aViewPosition + (fCutB * getDirection())); + const bool bBothAbove(aPosA.getY() < rViewInformation.getDiscreteViewport().getMinY() && aPosB.getY() < rViewInformation.getDiscreteViewport().getMinY()); + const bool bBothBelow(aPosA.getY() > rViewInformation.getDiscreteViewport().getMaxY() && aPosB.getY() < rViewInformation.getDiscreteViewport().getMaxY()); + + if(!bBothAbove && !bBothBelow) + { + aLine.append(aPosA); + aLine.append(aPosB); + } + } + + if(aLine.count()) + { + // clip against visible area + const basegfx::B2DPolyPolygon aResult(basegfx::utils::clipPolygonOnRange(aLine, rViewInformation.getDiscreteViewport(), true, true)); + + for(sal_uInt32 a(0); a < aResult.count(); a++) + { + basegfx::B2DPolygon aPart(aResult.getB2DPolygon(a)); + aPart.transform(rViewInformation.getInverseObjectToViewTransformation()); + PolygonMarkerPrimitive2D* pNew = new PolygonMarkerPrimitive2D(aPart, getRGBColA(), getRGBColB(), getDiscreteDashLength()); + rContainer.push_back(pNew); + } + } + + break; + } + } + } + + HelplinePrimitive2D::HelplinePrimitive2D( + const basegfx::B2DPoint& rPosition, + const basegfx::B2DVector& rDirection, + HelplineStyle2D eStyle, + const basegfx::BColor& rRGBColA, + const basegfx::BColor& rRGBColB, + double fDiscreteDashLength) + : BufferedDecompositionPrimitive2D(), + maPosition(rPosition), + maDirection(rDirection), + meStyle(eStyle), + maRGBColA(rRGBColA), + maRGBColB(rRGBColB), + mfDiscreteDashLength(fDiscreteDashLength), + maLastObjectToViewTransformation(), + maLastViewport() + { + } + + bool HelplinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const HelplinePrimitive2D& rCompare = static_cast<const HelplinePrimitive2D&>(rPrimitive); + + return (getPosition() == rCompare.getPosition() + && getDirection() == rCompare.getDirection() + && getStyle() == rCompare.getStyle() + && getRGBColA() == rCompare.getRGBColA() + && getRGBColB() == rCompare.getRGBColB() + && getDiscreteDashLength() == rCompare.getDiscreteDashLength()); + } + + return false; + } + + void HelplinePrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if(!getBuffered2DDecomposition().empty()) + { + if(maLastViewport != rViewInformation.getViewport() || maLastObjectToViewTransformation != rViewInformation.getObjectToViewTransformation()) + { + // conditions of last local decomposition have changed, delete + const_cast< HelplinePrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + } + + if(getBuffered2DDecomposition().empty()) + { + // remember ViewRange and ViewTransformation + const_cast< HelplinePrimitive2D* >(this)->maLastObjectToViewTransformation = rViewInformation.getObjectToViewTransformation(); + const_cast< HelplinePrimitive2D* >(this)->maLastViewport = rViewInformation.getViewport(); + } + + // use parent implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + // provide unique ID + ImplPrimitive2DIDBlock(HelplinePrimitive2D, PRIMITIVE2D_ID_HELPLINEPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/hiddengeometryprimitive2d.cxx b/drawinglayer/source/primitive2d/hiddengeometryprimitive2d.cxx new file mode 100644 index 000000000..cdce8a750 --- /dev/null +++ b/drawinglayer/source/primitive2d/hiddengeometryprimitive2d.cxx @@ -0,0 +1,49 @@ +/* -*- 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/hiddengeometryprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + HiddenGeometryPrimitive2D::HiddenGeometryPrimitive2D( + const Primitive2DContainer& rChildren) + : GroupPrimitive2D(rChildren) + { + } + + basegfx::B2DRange HiddenGeometryPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + return getChildren().getB2DRange(rViewInformation); + } + + void HiddenGeometryPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& /*rVisitor*/, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + } + + // provide unique ID + ImplPrimitive2DIDBlock(HiddenGeometryPrimitive2D, PRIMITIVE2D_ID_HIDDENGEOMETRYPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/invertprimitive2d.cxx b/drawinglayer/source/primitive2d/invertprimitive2d.cxx new file mode 100644 index 000000000..63c4be524 --- /dev/null +++ b/drawinglayer/source/primitive2d/invertprimitive2d.cxx @@ -0,0 +1,40 @@ +/* -*- 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/invertprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + InvertPrimitive2D::InvertPrimitive2D( + const Primitive2DContainer& rChildren) + : GroupPrimitive2D(rChildren) + { + } + + // provide unique ID + ImplPrimitive2DIDBlock(InvertPrimitive2D, PRIMITIVE2D_ID_INVERTPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/markerarrayprimitive2d.cxx b/drawinglayer/source/primitive2d/markerarrayprimitive2d.cxx new file mode 100644 index 000000000..d4b604e79 --- /dev/null +++ b/drawinglayer/source/primitive2d/markerarrayprimitive2d.cxx @@ -0,0 +1,135 @@ +/* -*- 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/markerarrayprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <toolkit/helper/vclunohelper.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void MarkerArrayPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + const std::vector< basegfx::B2DPoint >& rPositions = getPositions(); + const sal_uInt32 nMarkerCount(rPositions.size()); + + if(!(nMarkerCount && !getMarker().IsEmpty())) + return; + + // get pixel size + Size aBitmapSize(getMarker().GetSizePixel()); + + if(!(aBitmapSize.Width() && aBitmapSize.Height())) + return; + + // get logic half pixel size + basegfx::B2DVector aLogicHalfSize(rViewInformation.getInverseObjectToViewTransformation() * + basegfx::B2DVector(aBitmapSize.getWidth() - 1.0, aBitmapSize.getHeight() - 1.0)); + + // use half size for expand + aLogicHalfSize *= 0.5; + + for(sal_uInt32 a(0); a < nMarkerCount; a++) + { + const basegfx::B2DPoint& rPosition(rPositions[a]); + const basegfx::B2DRange aRange(rPosition - aLogicHalfSize, rPosition + aLogicHalfSize); + basegfx::B2DHomMatrix aTransform; + + aTransform.set(0, 0, aRange.getWidth()); + aTransform.set(1, 1, aRange.getHeight()); + aTransform.set(0, 2, aRange.getMinX()); + aTransform.set(1, 2, aRange.getMinY()); + + rContainer.push_back( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getMarker()), + aTransform)); + } + } + + MarkerArrayPrimitive2D::MarkerArrayPrimitive2D( + const std::vector< basegfx::B2DPoint >& rPositions, + const BitmapEx& rMarker) + : BufferedDecompositionPrimitive2D(), + maPositions(rPositions), + maMarker(rMarker) + { + } + + bool MarkerArrayPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const MarkerArrayPrimitive2D& rCompare = static_cast<const MarkerArrayPrimitive2D&>(rPrimitive); + + return (getPositions() == rCompare.getPositions() + && getMarker() == rCompare.getMarker()); + } + + return false; + } + + basegfx::B2DRange MarkerArrayPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + basegfx::B2DRange aRetval; + + if(!getPositions().empty()) + { + // get the basic range from the position vector + for (auto const& pos : getPositions()) + { + aRetval.expand(pos); + } + + if(!getMarker().IsEmpty()) + { + // get pixel size + const Size aBitmapSize(getMarker().GetSizePixel()); + + if(aBitmapSize.Width() && aBitmapSize.Height()) + { + // get logic half size + basegfx::B2DVector aLogicHalfSize(rViewInformation.getInverseObjectToViewTransformation() * + basegfx::B2DVector(aBitmapSize.getWidth(), aBitmapSize.getHeight())); + + // use half size for expand + aLogicHalfSize *= 0.5; + + // apply aLogicHalfSize + aRetval.expand(aRetval.getMinimum() - aLogicHalfSize); + aRetval.expand(aRetval.getMaximum() + aLogicHalfSize); + } + } + } + + return aRetval; + } + + // provide unique ID + ImplPrimitive2DIDBlock(MarkerArrayPrimitive2D, PRIMITIVE2D_ID_MARKERARRAYPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/maskprimitive2d.cxx b/drawinglayer/source/primitive2d/maskprimitive2d.cxx new file mode 100644 index 000000000..1e8af509c --- /dev/null +++ b/drawinglayer/source/primitive2d/maskprimitive2d.cxx @@ -0,0 +1,59 @@ +/* -*- 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/maskprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + MaskPrimitive2D::MaskPrimitive2D( + const basegfx::B2DPolyPolygon& rMask, + const Primitive2DContainer& rChildren) + : GroupPrimitive2D(rChildren), + maMask(rMask) + { + } + + bool MaskPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const MaskPrimitive2D& rCompare = static_cast< const MaskPrimitive2D& >(rPrimitive); + + return (getMask() == rCompare.getMask()); + } + + return false; + } + + basegfx::B2DRange MaskPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + return getMask().getB2DRange(); + } + + // provide unique ID + ImplPrimitive2DIDBlock(MaskPrimitive2D, PRIMITIVE2D_ID_MASKPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/mediaprimitive2d.cxx b/drawinglayer/source/primitive2d/mediaprimitive2d.cxx new file mode 100644 index 000000000..5158c6822 --- /dev/null +++ b/drawinglayer/source/primitive2d/mediaprimitive2d.cxx @@ -0,0 +1,145 @@ +/* -*- 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/mediaprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <vcl/GraphicObject.hxx> +#include <drawinglayer/primitive2d/graphicprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx> + + +namespace drawinglayer::primitive2d +{ + void MediaPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + Primitive2DContainer xRetval; + xRetval.resize(1); + + // create background object + basegfx::B2DPolygon aBackgroundPolygon(basegfx::utils::createUnitPolygon()); + aBackgroundPolygon.transform(getTransform()); + const Primitive2DReference xRefBackground( + new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aBackgroundPolygon), + getBackgroundColor())); + xRetval[0] = xRefBackground; + + if(GraphicType::Bitmap == maSnapshot.GetType() || GraphicType::GdiMetafile == maSnapshot.GetType()) + { + const GraphicObject aGraphicObject(maSnapshot); + const GraphicAttr aGraphicAttr; + xRetval.resize(2); + xRetval[1] = new GraphicPrimitive2D(getTransform(), aGraphicObject, aGraphicAttr); + } + + if(getDiscreteBorder()) + { + const basegfx::B2DVector aDiscreteInLogic(rViewInformation.getInverseObjectToViewTransformation() * + basegfx::B2DVector(static_cast<double>(getDiscreteBorder()), static_cast<double>(getDiscreteBorder()))); + const double fDiscreteSize(aDiscreteInLogic.getX() + aDiscreteInLogic.getY()); + + basegfx::B2DRange aSourceRange(0.0, 0.0, 1.0, 1.0); + aSourceRange.transform(getTransform()); + + basegfx::B2DRange aDestRange(aSourceRange); + aDestRange.grow(-0.5 * fDiscreteSize); + + if(basegfx::fTools::equalZero(aDestRange.getWidth()) || basegfx::fTools::equalZero(aDestRange.getHeight())) + { + // shrunk primitive has no content (zero size in X or Y), nothing to display. Still create + // invisible content for HitTest and BoundRect + const Primitive2DReference xHiddenLines(new HiddenGeometryPrimitive2D(xRetval)); + + xRetval = Primitive2DContainer { xHiddenLines, }; + } + else + { + // create transformation matrix from original range to shrunk range + basegfx::B2DHomMatrix aTransform; + aTransform.translate(-aSourceRange.getMinX(), -aSourceRange.getMinY()); + aTransform.scale(aDestRange.getWidth() / aSourceRange.getWidth(), aDestRange.getHeight() / aSourceRange.getHeight()); + aTransform.translate(aDestRange.getMinX(), aDestRange.getMinY()); + + // add transform primitive + const Primitive2DReference aScaled(new TransformPrimitive2D(aTransform, xRetval)); + xRetval = Primitive2DContainer { aScaled }; + } + } + + rContainer.insert(rContainer.end(), xRetval.begin(), xRetval.end()); + } + + MediaPrimitive2D::MediaPrimitive2D( + const basegfx::B2DHomMatrix& rTransform, + const OUString& rURL, + const basegfx::BColor& rBackgroundColor, + sal_uInt32 nDiscreteBorder, + const Graphic &rSnapshot) + : BufferedDecompositionPrimitive2D(), + maTransform(rTransform), + maURL(rURL), + maBackgroundColor(rBackgroundColor), + mnDiscreteBorder(nDiscreteBorder), + maSnapshot(rSnapshot) + { + } + + bool MediaPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const MediaPrimitive2D& rCompare = static_cast<const MediaPrimitive2D&>(rPrimitive); + + return (getTransform() == rCompare.getTransform() + && maURL == rCompare.maURL + && getBackgroundColor() == rCompare.getBackgroundColor() + && getDiscreteBorder() == rCompare.getDiscreteBorder()); + } + + return false; + } + + basegfx::B2DRange MediaPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(getTransform()); + + if(getDiscreteBorder()) + { + const basegfx::B2DVector aDiscreteInLogic(rViewInformation.getInverseObjectToViewTransformation() * + basegfx::B2DVector(static_cast<double>(getDiscreteBorder()), static_cast<double>(getDiscreteBorder()))); + const double fDiscreteSize(aDiscreteInLogic.getX() + aDiscreteInLogic.getY()); + + aRetval.grow(-0.5 * fDiscreteSize); + } + + return aRetval; + } + + // provide unique ID + ImplPrimitive2DIDBlock(MediaPrimitive2D, PRIMITIVE2D_ID_MEDIAPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/metafileprimitive2d.cxx b/drawinglayer/source/primitive2d/metafileprimitive2d.cxx new file mode 100644 index 000000000..362ae446f --- /dev/null +++ b/drawinglayer/source/primitive2d/metafileprimitive2d.cxx @@ -0,0 +1,133 @@ +/* -*- 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/metafileprimitive2d.hxx> +#include <wmfemfhelper.hxx> + +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <vcl/canvastools.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ + void MetafilePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + // Interpret the Metafile and get the content. There should be only one target, as in the start condition, + // but iterating will be the right thing to do when some push/pop is not closed + Primitive2DContainer xRetval(wmfemfhelper::interpretMetafile(getMetaFile(), rViewInformation)); + + if(!xRetval.empty()) + { + // get target size + const ::tools::Rectangle aMtfTarget(getMetaFile().GetPrefMapMode().GetOrigin(), getMetaFile().GetPrefSize()); + const basegfx::B2DRange aMtfRange(vcl::unotools::b2DRectangleFromRectangle(aMtfTarget)); + + // tdf#113197 get content range and check if we have an overlap with + // defined target range (aMtfRange) + if (!aMtfRange.isEmpty()) + { + const basegfx::B2DRange aContentRange(xRetval.getB2DRange(rViewInformation)); + + // also test equal since isInside gives also true for equal + if (!aMtfRange.equal(aContentRange) && !aMtfRange.isInside(aContentRange)) + { + // contentRange is partly larger than aMtfRange (stuff sticks + // outside), clipping is needed + const drawinglayer::primitive2d::Primitive2DReference xMask( + new drawinglayer::primitive2d::MaskPrimitive2D( + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect( + aMtfRange)), + xRetval)); + + xRetval = drawinglayer::primitive2d::Primitive2DContainer{ xMask }; + } + } + + // create transformation + basegfx::B2DHomMatrix aAdaptedTransform; + + aAdaptedTransform.translate(-aMtfTarget.Left(), -aMtfTarget.Top()); + aAdaptedTransform.scale( + aMtfTarget.getWidth() ? 1.0 / aMtfTarget.getWidth() : 1.0, + aMtfTarget.getHeight() ? 1.0 / aMtfTarget.getHeight() : 1.0); + aAdaptedTransform = getTransform() * aAdaptedTransform; + + // embed to target transformation + const Primitive2DReference aEmbeddedTransform( + new TransformPrimitive2D( + aAdaptedTransform, + xRetval)); + + xRetval = Primitive2DContainer { aEmbeddedTransform }; + } + + rContainer.insert(rContainer.end(), xRetval.begin(), xRetval.end()); + } + + MetafilePrimitive2D::MetafilePrimitive2D( + const basegfx::B2DHomMatrix& rMetaFileTransform, + const GDIMetaFile& rMetaFile) + : BufferedDecompositionPrimitive2D(), + maMetaFileTransform(rMetaFileTransform), + maMetaFile(rMetaFile) + { + } + + bool MetafilePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const MetafilePrimitive2D& rCompare = static_cast<const MetafilePrimitive2D&>(rPrimitive); + + return (getTransform() == rCompare.getTransform() + && getMetaFile() == rCompare.getMetaFile()); + } + + return false; + } + + basegfx::B2DRange MetafilePrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // use own implementation to quickly answer the getB2DRange question. The + // MetafilePrimitive2D assumes that all geometry is inside of the shape. If + // this is not the case (i have already seen some wrong Metafiles) it should + // be embedded to a MaskPrimitive2D + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(getTransform()); + + return aRetval; + } + + // from MetafileAccessor + void MetafilePrimitive2D::accessMetafile(GDIMetaFile& rTargetMetafile) const + { + rTargetMetafile = maMetaFile; + } + + // provide unique ID + ImplPrimitive2DIDBlock(MetafilePrimitive2D, PRIMITIVE2D_ID_METAFILEPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/modifiedcolorprimitive2d.cxx b/drawinglayer/source/primitive2d/modifiedcolorprimitive2d.cxx new file mode 100644 index 000000000..4c0e8e6cc --- /dev/null +++ b/drawinglayer/source/primitive2d/modifiedcolorprimitive2d.cxx @@ -0,0 +1,64 @@ +/* -*- 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/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + ModifiedColorPrimitive2D::ModifiedColorPrimitive2D( + const Primitive2DContainer& rChildren, + const basegfx::BColorModifierSharedPtr& rColorModifier) + : GroupPrimitive2D(rChildren), + maColorModifier(rColorModifier) + { + } + + bool ModifiedColorPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const ModifiedColorPrimitive2D& rCompare = static_cast<const ModifiedColorPrimitive2D&>(rPrimitive); + + if(getColorModifier().get() == rCompare.getColorModifier().get()) + { + return true; + } + + if(!getColorModifier() || !rCompare.getColorModifier()) + { + return false; + } + + return *getColorModifier()== *rCompare.getColorModifier(); + } + + return false; + } + + // provide unique ID + ImplPrimitive2DIDBlock(ModifiedColorPrimitive2D, PRIMITIVE2D_ID_MODIFIEDCOLORPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/objectinfoprimitive2d.cxx b/drawinglayer/source/primitive2d/objectinfoprimitive2d.cxx new file mode 100644 index 000000000..32e66c705 --- /dev/null +++ b/drawinglayer/source/primitive2d/objectinfoprimitive2d.cxx @@ -0,0 +1,57 @@ +/* -*- 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/objectinfoprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ + ObjectInfoPrimitive2D::ObjectInfoPrimitive2D( + const Primitive2DContainer& rChildren, + const OUString& rName, + const OUString& rTitle, + const OUString& rDesc) + : GroupPrimitive2D(rChildren), + maName(rName), + maTitle(rTitle), + maDesc(rDesc) + { + } + + bool ObjectInfoPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const ObjectInfoPrimitive2D& rCompare = static_cast<const ObjectInfoPrimitive2D&>(rPrimitive); + + return (getName() == rCompare.getName() + && getTitle() == rCompare.getTitle() + && getDesc() == rCompare.getDesc()); + } + + return false; + } + + // provide unique ID + ImplPrimitive2DIDBlock(ObjectInfoPrimitive2D, PRIMITIVE2D_ID_OBJECTINFOPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/pagehierarchyprimitive2d.cxx b/drawinglayer/source/primitive2d/pagehierarchyprimitive2d.cxx new file mode 100644 index 000000000..ff52e1c6e --- /dev/null +++ b/drawinglayer/source/primitive2d/pagehierarchyprimitive2d.cxx @@ -0,0 +1,37 @@ +/* -*- 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/pagehierarchyprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + +using namespace com::sun::star; + +namespace drawinglayer::primitive2d +{ + PageHierarchyPrimitive2D::PageHierarchyPrimitive2D(const Primitive2DContainer& rChildren) + : GroupPrimitive2D(rChildren) + { + } + + // provide unique ID + ImplPrimitive2DIDBlock(PageHierarchyPrimitive2D, PRIMITIVE2D_ID_PAGEHIERARCHYPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/pagepreviewprimitive2d.cxx b/drawinglayer/source/primitive2d/pagepreviewprimitive2d.cxx new file mode 100644 index 000000000..b7c67bf8f --- /dev/null +++ b/drawinglayer/source/primitive2d/pagepreviewprimitive2d.cxx @@ -0,0 +1,148 @@ +/* -*- 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/pagepreviewprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + void PagePreviewPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + Primitive2DContainer aContent(getPageContent()); + + if(!(!aContent.empty() + && basegfx::fTools::more(getContentWidth(), 0.0) + && basegfx::fTools::more(getContentHeight(), 0.0))) + return; + + // the decomposed matrix will be needed + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + getTransform().decompose(aScale, aTranslate, fRotate, fShearX); + + if(!(basegfx::fTools::more(aScale.getX(), 0.0) && basegfx::fTools::more(aScale.getY(), 0.0))) + return; + + // check if content overlaps with target size and needs to be embedded with a + // clipping primitive + const basegfx::B2DRange aRealContentRange(aContent.getB2DRange(rViewInformation)); + const basegfx::B2DRange aAllowedContentRange(0.0, 0.0, getContentWidth(), getContentHeight()); + + if(!aAllowedContentRange.isInside(aRealContentRange)) + { + const Primitive2DReference xReferenceA( + new MaskPrimitive2D( + basegfx::B2DPolyPolygon( + basegfx::utils::createPolygonFromRect(aAllowedContentRange)), aContent)); + aContent = Primitive2DContainer { xReferenceA }; + } + + // create a mapping from content to object. + basegfx::B2DHomMatrix aPageTrans; + + // #i101075# when keeping the aspect ratio is wanted, it is necessary to calculate + // an equidistant scaling in X and Y and a corresponding translation to + // center the output. Calculate needed scale factors + const double fScaleX(aScale.getX() / getContentWidth()); + const double fScaleY(aScale.getY() / getContentHeight()); + + // to keep the aspect, use the smaller scale and adapt missing size by translation + if(fScaleX < fScaleY) + { + // height needs to be adapted + const double fNeededHeight(aScale.getY() / fScaleX); + const double fSpaceToAdd(fNeededHeight - getContentHeight()); + + aPageTrans.translate(0.0, fSpaceToAdd * 0.5); + aPageTrans.scale(fScaleX, aScale.getY() / fNeededHeight); + } + else + { + // width needs to be adapted + const double fNeededWidth(aScale.getX() / fScaleY); + const double fSpaceToAdd(fNeededWidth - getContentWidth()); + + aPageTrans.translate(fSpaceToAdd * 0.5, 0.0); + aPageTrans.scale(aScale.getX() / fNeededWidth, fScaleY); + } + + // add the missing object transformation aspects + const basegfx::B2DHomMatrix aCombined(basegfx::utils::createShearXRotateTranslateB2DHomMatrix( + fShearX, fRotate, aTranslate.getX(), aTranslate.getY())); + aPageTrans = aCombined * aPageTrans; + + // embed in necessary transformation to map from SdrPage to SdrPageObject + rContainer.push_back(new TransformPrimitive2D(aPageTrans, aContent)); + } + + PagePreviewPrimitive2D::PagePreviewPrimitive2D( + const css::uno::Reference< css::drawing::XDrawPage >& rxDrawPage, + const basegfx::B2DHomMatrix& rTransform, + double fContentWidth, + double fContentHeight, + const Primitive2DContainer& rPageContent) + : BufferedDecompositionPrimitive2D(), + mxDrawPage(rxDrawPage), + maPageContent(rPageContent), + maTransform(rTransform), + mfContentWidth(fContentWidth), + mfContentHeight(fContentHeight) + { + } + + bool PagePreviewPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BasePrimitive2D::operator==(rPrimitive)) + { + const PagePreviewPrimitive2D& rCompare = static_cast< const PagePreviewPrimitive2D& >(rPrimitive); + + return (getXDrawPage() == rCompare.getXDrawPage() + && getPageContent() == rCompare.getPageContent() + && getTransform() == rCompare.getTransform() + && getContentWidth() == rCompare.getContentWidth() + && getContentHeight() == rCompare.getContentHeight()); + } + + return false; + } + + basegfx::B2DRange PagePreviewPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation`*/) const + { + // nothing is allowed to stick out of a PagePreviewPrimitive, thus we + // can quickly deliver our range here + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(getTransform()); + return aRetval; + } + + // provide unique ID + ImplPrimitive2DIDBlock(PagePreviewPrimitive2D, PRIMITIVE2D_ID_PAGEPREVIEWPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/patternfillprimitive2d.cxx b/drawinglayer/source/primitive2d/patternfillprimitive2d.cxx new file mode 100644 index 000000000..ec1f96214 --- /dev/null +++ b/drawinglayer/source/primitive2d/patternfillprimitive2d.cxx @@ -0,0 +1,330 @@ +/* -*- 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/patternfillprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <texture/texture.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +#include <converters.hxx> + +using namespace com::sun::star; + +#define MAXIMUM_SQUARE_LENGTH (186.0) +#define MINIMUM_SQUARE_LENGTH (16.0) +#define MINIMUM_TILES_LENGTH (3) + +namespace drawinglayer::primitive2d +{ + void PatternFillPrimitive2D::calculateNeededDiscreteBufferSize( + sal_uInt32& rWidth, + sal_uInt32& rHeight, + const geometry::ViewInformation2D& rViewInformation) const + { + // reset parameters + rWidth = rHeight = 0; + + // check if resolution is in the range which may be buffered + const basegfx::B2DPolyPolygon& rMaskPolygon = getMask(); + const basegfx::B2DRange aMaskRange(rMaskPolygon.getB2DRange()); + + // get discrete rounded up square size of a single tile + const basegfx::B2DHomMatrix aMaskRangeTransformation( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aMaskRange.getRange(), + aMaskRange.getMinimum())); + const basegfx::B2DHomMatrix aTransform( + rViewInformation.getObjectToViewTransformation() * aMaskRangeTransformation); + const basegfx::B2DPoint aTopLeft(aTransform * getReferenceRange().getMinimum()); + const basegfx::B2DPoint aX(aTransform * basegfx::B2DPoint(getReferenceRange().getMaxX(), getReferenceRange().getMinY())); + const basegfx::B2DPoint aY(aTransform * basegfx::B2DPoint(getReferenceRange().getMinX(), getReferenceRange().getMaxY())); + const double fW(basegfx::B2DVector(aX - aTopLeft).getLength()); + const double fH(basegfx::B2DVector(aY - aTopLeft).getLength()); + const double fSquare(fW * fH); + + if(fSquare <= 0.0) + return; + + // check if less than a maximum square pixels is used + static const sal_uInt32 fMaximumSquare(MAXIMUM_SQUARE_LENGTH * MAXIMUM_SQUARE_LENGTH); + + if(fSquare >= fMaximumSquare) + return; + + // calculate needed number of tiles and check if used more than a minimum count + const texture::GeoTexSvxTiled aTiling(getReferenceRange()); + const sal_uInt32 nTiles(aTiling.getNumberOfTiles()); + static const sal_uInt32 nMinimumTiles(MINIMUM_TILES_LENGTH * MINIMUM_TILES_LENGTH); + + if(nTiles < nMinimumTiles) + return; + + rWidth = basegfx::fround(ceil(fW)); + rHeight = basegfx::fround(ceil(fH)); + static const sal_uInt32 fMinimumSquare(MINIMUM_SQUARE_LENGTH * MINIMUM_SQUARE_LENGTH); + + if(fSquare < fMinimumSquare) + { + const double fRel(fW/fH); + rWidth = basegfx::fround(sqrt(fMinimumSquare * fRel)); + rHeight = basegfx::fround(sqrt(fMinimumSquare / fRel)); + } + } + + Primitive2DContainer PatternFillPrimitive2D::createContent(const geometry::ViewInformation2D& rViewInformation) const + { + Primitive2DContainer aContent; + + // see if buffering is wanted. If so, create buffered content in given resolution + if(0 != mnDiscreteWidth && 0 != mnDiscreteHeight) + { + const geometry::ViewInformation2D aViewInformation2D; + const primitive2d::Primitive2DReference xEmbedRef( + new primitive2d::TransformPrimitive2D( + basegfx::utils::createScaleB2DHomMatrix(mnDiscreteWidth, mnDiscreteHeight), + getChildren())); + const primitive2d::Primitive2DContainer xEmbedSeq { xEmbedRef }; + + const BitmapEx aBitmapEx( + convertToBitmapEx( + xEmbedSeq, + aViewInformation2D, + mnDiscreteWidth, + mnDiscreteHeight, + mnDiscreteWidth * mnDiscreteHeight)); + + if(!aBitmapEx.IsEmpty()) + { + const Size& rBmpPix = aBitmapEx.GetSizePixel(); + + if(rBmpPix.Width() > 0 && rBmpPix.Height() > 0) + { + const primitive2d::Primitive2DReference xEmbedRefBitmap( + new primitive2d::BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(aBitmapEx), + basegfx::B2DHomMatrix())); + aContent = primitive2d::Primitive2DContainer { xEmbedRefBitmap }; + } + } + } + + if(aContent.empty()) + { + // buffering was not tried or did fail - reset remembered buffered size + // in any case + PatternFillPrimitive2D* pThat = const_cast< PatternFillPrimitive2D* >(this); + pThat->mnDiscreteWidth = pThat->mnDiscreteHeight = 0; + + // use children as default context + aContent = getChildren(); + + // check if content needs to be clipped + const basegfx::B2DRange aUnitRange(0.0, 0.0, 1.0, 1.0); + const basegfx::B2DRange aContentRange(getChildren().getB2DRange(rViewInformation)); + + if(!aUnitRange.isInside(aContentRange)) + { + const Primitive2DReference xRef( + new MaskPrimitive2D( + basegfx::B2DPolyPolygon(basegfx::utils::createPolygonFromRect(aUnitRange)), + aContent)); + + aContent = Primitive2DContainer { xRef }; + } + } + + return aContent; + } + + void PatternFillPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + Primitive2DContainer aRetval; + + if(getChildren().empty()) + return; + + if(!(!getReferenceRange().isEmpty() && getReferenceRange().getWidth() > 0.0 && getReferenceRange().getHeight() > 0.0)) + return; + + const basegfx::B2DRange aMaskRange(getMask().getB2DRange()); + + if(!(!aMaskRange.isEmpty() && aMaskRange.getWidth() > 0.0 && aMaskRange.getHeight() > 0.0)) + return; + + // create tiling matrices + std::vector< basegfx::B2DHomMatrix > aMatrices; + texture::GeoTexSvxTiled aTiling(getReferenceRange()); + + aTiling.appendTransformations(aMatrices); + + // create content + const Primitive2DContainer aContent(createContent(rViewInformation)); + + // resize result + aRetval.resize(aMatrices.size()); + + // create one primitive for each matrix + for(size_t a(0); a < aMatrices.size(); a++) + { + aRetval[a] = new TransformPrimitive2D( + aMatrices[a], + aContent); + } + + // transform result which is in unit coordinates to mask's object coordinates + { + const basegfx::B2DHomMatrix aMaskTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + aMaskRange.getRange(), + aMaskRange.getMinimum())); + + const Primitive2DReference xRef( + new TransformPrimitive2D( + aMaskTransform, + aRetval)); + + aRetval = Primitive2DContainer { xRef }; + } + + // embed result in mask + { + rContainer.push_back( + new MaskPrimitive2D( + getMask(), + aRetval)); + } + } + + PatternFillPrimitive2D::PatternFillPrimitive2D( + const basegfx::B2DPolyPolygon& rMask, + const Primitive2DContainer& rChildren, + const basegfx::B2DRange& rReferenceRange) + : BufferedDecompositionPrimitive2D(), + maMask(rMask), + maChildren(rChildren), + maReferenceRange(rReferenceRange), + mnDiscreteWidth(0), + mnDiscreteHeight(0) + { + } + + bool PatternFillPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PatternFillPrimitive2D& rCompare = static_cast< const PatternFillPrimitive2D& >(rPrimitive); + + return (getMask() == rCompare.getMask() + && getChildren() == rCompare.getChildren() + && getReferenceRange() == rCompare.getReferenceRange()); + } + + return false; + } + + basegfx::B2DRange PatternFillPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /* rViewInformation */ ) const + { + return getMask().getB2DRange(); + } + + void PatternFillPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + // The existing buffered decomposition uses a buffer in the remembered + // size or none if sizes are zero. Get new needed sizes which depend on + // the given ViewInformation + bool bResetBuffering = false; + sal_uInt32 nW(0); + sal_uInt32 nH(0); + calculateNeededDiscreteBufferSize(nW, nH, rViewInformation); + const bool bBufferingCurrentlyUsed(0 != mnDiscreteWidth && 0 != mnDiscreteHeight); + const bool bBufferingNextUsed(0 != nW && 0 != nH); + + if(bBufferingNextUsed) + { + // buffering is now possible + if(bBufferingCurrentlyUsed) + { + if(nW > mnDiscreteWidth || nH > mnDiscreteHeight) + { + // Higher resolution is needed than used in the existing buffered + // decomposition - create new one + bResetBuffering = true; + } + else if(double(nW * nH) / double(mnDiscreteWidth * mnDiscreteHeight) <= 0.5) + { + // Size has shrunk for 50% or more - it's worth to refresh the buffering + // to spare some resources + bResetBuffering = true; + } + } + else + { + // currently no buffering used - reset evtl. unbuffered + // decomposition to start buffering + bResetBuffering = true; + } + } + else + { + // buffering is no longer possible + if(bBufferingCurrentlyUsed) + { + // reset decomposition to allow creation of unbuffered one + bResetBuffering = true; + } + } + + if(bResetBuffering) + { + PatternFillPrimitive2D* pThat = const_cast< PatternFillPrimitive2D* >(this); + pThat->mnDiscreteWidth = nW; + pThat->mnDiscreteHeight = nH; + pThat->setBuffered2DDecomposition(Primitive2DContainer()); + } + + // call parent + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + sal_Int64 SAL_CALL PatternFillPrimitive2D::estimateUsage() + { + size_t nRet(0); + for (auto& it : getChildren()) + { + uno::Reference<util::XAccounting> const xAcc(it, uno::UNO_QUERY); + if (xAcc.is()) + { + nRet += xAcc->estimateUsage(); + } + } + return nRet; + } + + // provide unique ID + ImplPrimitive2DIDBlock(PatternFillPrimitive2D, PRIMITIVE2D_ID_PATTERNFILLPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/pointarrayprimitive2d.cxx b/drawinglayer/source/primitive2d/pointarrayprimitive2d.cxx new file mode 100644 index 000000000..7e527487e --- /dev/null +++ b/drawinglayer/source/primitive2d/pointarrayprimitive2d.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 <drawinglayer/primitive2d/pointarrayprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + PointArrayPrimitive2D::PointArrayPrimitive2D( + const std::vector< basegfx::B2DPoint >& rPositions, + const basegfx::BColor& rRGBColor) + : BasePrimitive2D(), + maPositions(rPositions), + maRGBColor(rRGBColor), + maB2DRange() + { + } + + bool PointArrayPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BasePrimitive2D::operator==(rPrimitive)) + { + const PointArrayPrimitive2D& rCompare = static_cast<const PointArrayPrimitive2D&>(rPrimitive); + + return (getPositions() == rCompare.getPositions() + && getRGBColor() == rCompare.getRGBColor()); + } + + return false; + } + + basegfx::B2DRange PointArrayPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(maB2DRange.isEmpty()) + { + basegfx::B2DRange aNewRange; + + // get the basic range from the position vector + for (auto const& pos : getPositions()) + { + aNewRange.expand(pos); + } + + // assign to buffered value + const_cast< PointArrayPrimitive2D* >(this)->maB2DRange = aNewRange; + } + + return maB2DRange; + } + + // provide unique ID + ImplPrimitive2DIDBlock(PointArrayPrimitive2D, PRIMITIVE2D_ID_POINTARRAYPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/polygonprimitive2d.cxx b/drawinglayer/source/primitive2d/polygonprimitive2d.cxx new file mode 100644 index 000000000..0251910e8 --- /dev/null +++ b/drawinglayer/source/primitive2d/polygonprimitive2d.cxx @@ -0,0 +1,603 @@ +/* -*- 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/polygonprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/primitive2d/PolyPolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <basegfx/polygon/b2dlinegeometry.hxx> +#include <com/sun/star/drawing/LineCap.hpp> + +#include <converters.hxx> + +using namespace com::sun::star; +using namespace std; + + +namespace drawinglayer::primitive2d +{ + PolygonHairlinePrimitive2D::PolygonHairlinePrimitive2D( + const basegfx::B2DPolygon& rPolygon, + const basegfx::BColor& rBColor) + : BasePrimitive2D(), + maPolygon(rPolygon), + maBColor(rBColor) + { + } + + bool PolygonHairlinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BasePrimitive2D::operator==(rPrimitive)) + { + const PolygonHairlinePrimitive2D& rCompare = static_cast<const PolygonHairlinePrimitive2D&>(rPrimitive); + + return (getB2DPolygon() == rCompare.getB2DPolygon() + && getBColor() == rCompare.getBColor()); + } + + return false; + } + + basegfx::B2DRange PolygonHairlinePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + // this is a hairline, thus the line width is view-dependent. Get range of polygon + // as base size + basegfx::B2DRange aRetval(getB2DPolygon().getB2DRange()); + + if(!aRetval.isEmpty()) + { + // Calculate view-dependent hairline width + const basegfx::B2DVector aDiscreteSize(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0)); + const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5); + + if(basegfx::fTools::more(fDiscreteHalfLineWidth, 0.0)) + { + aRetval.grow(fDiscreteHalfLineWidth); + } + } + + // return range + return aRetval; + } + + // provide unique ID + ImplPrimitive2DIDBlock(PolygonHairlinePrimitive2D, PRIMITIVE2D_ID_POLYGONHAIRLINEPRIMITIVE2D) + + + void PolygonMarkerPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + // calculate logic DashLength + const basegfx::B2DVector aDashVector(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(getDiscreteDashLength(), 0.0)); + const double fLogicDashLength(aDashVector.getX()); + + if(fLogicDashLength > 0.0 && !getRGBColorA().equal(getRGBColorB())) + { + // apply dashing; get line and gap snippets + std::vector< double > aDash; + basegfx::B2DPolyPolygon aDashedPolyPolyA; + basegfx::B2DPolyPolygon aDashedPolyPolyB; + + aDash.push_back(fLogicDashLength); + aDash.push_back(fLogicDashLength); + basegfx::utils::applyLineDashing(getB2DPolygon(), aDash, &aDashedPolyPolyA, &aDashedPolyPolyB, 2.0 * fLogicDashLength); + + rContainer.push_back(new PolyPolygonHairlinePrimitive2D(aDashedPolyPolyA, getRGBColorA())); + rContainer.push_back(new PolyPolygonHairlinePrimitive2D(aDashedPolyPolyB, getRGBColorB())); + } + else + { + rContainer.push_back(new PolygonHairlinePrimitive2D(getB2DPolygon(), getRGBColorA())); + } + } + + PolygonMarkerPrimitive2D::PolygonMarkerPrimitive2D( + const basegfx::B2DPolygon& rPolygon, + const basegfx::BColor& rRGBColorA, + const basegfx::BColor& rRGBColorB, + double fDiscreteDashLength) + : BufferedDecompositionPrimitive2D(), + maPolygon(rPolygon), + maRGBColorA(rRGBColorA), + maRGBColorB(rRGBColorB), + mfDiscreteDashLength(fDiscreteDashLength), + maLastInverseObjectToViewTransformation() + { + } + + bool PolygonMarkerPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PolygonMarkerPrimitive2D& rCompare = static_cast<const PolygonMarkerPrimitive2D&>(rPrimitive); + + return (getB2DPolygon() == rCompare.getB2DPolygon() + && getRGBColorA() == rCompare.getRGBColorA() + && getRGBColorB() == rCompare.getRGBColorB() + && getDiscreteDashLength() == rCompare.getDiscreteDashLength()); + } + + return false; + } + + basegfx::B2DRange PolygonMarkerPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + // this is a hairline, thus the line width is view-dependent. Get range of polygon + // as base size + basegfx::B2DRange aRetval(getB2DPolygon().getB2DRange()); + + if(!aRetval.isEmpty()) + { + // Calculate view-dependent hairline width + const basegfx::B2DVector aDiscreteSize(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0)); + const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5); + + if(basegfx::fTools::more(fDiscreteHalfLineWidth, 0.0)) + { + aRetval.grow(fDiscreteHalfLineWidth); + } + } + + // return range + return aRetval; + } + + void PolygonMarkerPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + bool bNeedNewDecomposition(false); + + if(!getBuffered2DDecomposition().empty()) + { + if(rViewInformation.getInverseObjectToViewTransformation() != maLastInverseObjectToViewTransformation) + { + bNeedNewDecomposition = true; + } + } + + if(bNeedNewDecomposition) + { + // conditions of last local decomposition have changed, delete + const_cast< PolygonMarkerPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + + if(getBuffered2DDecomposition().empty()) + { + // remember last used InverseObjectToViewTransformation + PolygonMarkerPrimitive2D* pThat = const_cast< PolygonMarkerPrimitive2D* >(this); + pThat->maLastInverseObjectToViewTransformation = rViewInformation.getInverseObjectToViewTransformation(); + } + + // use parent implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + // provide unique ID + ImplPrimitive2DIDBlock(PolygonMarkerPrimitive2D, PRIMITIVE2D_ID_POLYGONMARKERPRIMITIVE2D) + +} // end of namespace + +namespace drawinglayer +{ + namespace primitive2d + { + void PolygonStrokePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(!getB2DPolygon().count()) + return; + + // #i102241# try to simplify before usage + const basegfx::B2DPolygon aB2DPolygon(basegfx::utils::simplifyCurveSegments(getB2DPolygon())); + basegfx::B2DPolyPolygon aHairLinePolyPolygon; + + if(getStrokeAttribute().isDefault() || 0.0 == getStrokeAttribute().getFullDotDashLen()) + { + // no line dashing, just copy + aHairLinePolyPolygon.append(aB2DPolygon); + } + else + { + // apply LineStyle + basegfx::utils::applyLineDashing( + aB2DPolygon, getStrokeAttribute().getDotDashArray(), + &aHairLinePolyPolygon, nullptr, getStrokeAttribute().getFullDotDashLen()); + } + + const sal_uInt32 nCount(aHairLinePolyPolygon.count()); + + if(!getLineAttribute().isDefault() && getLineAttribute().getWidth()) + { + // create fat line data + const double fHalfLineWidth(getLineAttribute().getWidth() / 2.0); + const basegfx::B2DLineJoin aLineJoin(getLineAttribute().getLineJoin()); + const css::drawing::LineCap aLineCap(getLineAttribute().getLineCap()); + basegfx::B2DPolyPolygon aAreaPolyPolygon; + const double fMiterMinimumAngle(getLineAttribute().getMiterMinimumAngle()); + + for(sal_uInt32 a(0); a < nCount; a++) + { + // New version of createAreaGeometry; now creates bezier polygons + aAreaPolyPolygon.append(basegfx::utils::createAreaGeometry( + aHairLinePolyPolygon.getB2DPolygon(a), + fHalfLineWidth, + aLineJoin, + aLineCap, + basegfx::deg2rad(12.5) /* default fMaxAllowedAngle*/ , + 0.4 /* default fMaxPartOfEdge*/ , + fMiterMinimumAngle)); + } + + // create primitive + for(sal_uInt32 b(0); b < aAreaPolyPolygon.count(); b++) + { + // put into single polyPolygon primitives to make clear that this is NOT meant + // to be painted as a single tools::PolyPolygon (XORed as fill rule). Alternatively, a + // melting process may be used here one day. + const basegfx::B2DPolyPolygon aNewPolyPolygon(aAreaPolyPolygon.getB2DPolygon(b)); + const basegfx::BColor aColor(getLineAttribute().getColor()); + rContainer.push_back(new PolyPolygonColorPrimitive2D(aNewPolyPolygon, aColor)); + } + } + else + { + rContainer.push_back( + new PolyPolygonHairlinePrimitive2D( + aHairLinePolyPolygon, + getLineAttribute().getColor())); + } + } + + PolygonStrokePrimitive2D::PolygonStrokePrimitive2D( + const basegfx::B2DPolygon& rPolygon, + const attribute::LineAttribute& rLineAttribute, + const attribute::StrokeAttribute& rStrokeAttribute) + : BufferedDecompositionPrimitive2D(), + maPolygon(rPolygon), + maLineAttribute(rLineAttribute), + maStrokeAttribute(rStrokeAttribute) + { + // MM01: keep these - these are no curve-decompposers but just checks + // simplify curve segments: moved here to not need to use it + // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect + maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon); + } + + PolygonStrokePrimitive2D::PolygonStrokePrimitive2D( + const basegfx::B2DPolygon& rPolygon, + const attribute::LineAttribute& rLineAttribute) + : BufferedDecompositionPrimitive2D(), + maPolygon(rPolygon), + maLineAttribute(rLineAttribute), + maStrokeAttribute() + { + // MM01: keep these - these are no curve-decompposers but just checks + // simplify curve segments: moved here to not need to use it + // at VclPixelProcessor2D::tryDrawPolygonStrokePrimitive2DDirect + maPolygon = basegfx::utils::simplifyCurveSegments(maPolygon); + } + + bool PolygonStrokePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const PolygonStrokePrimitive2D& rCompare = static_cast<const PolygonStrokePrimitive2D&>(rPrimitive); + + return (getB2DPolygon() == rCompare.getB2DPolygon() + && getLineAttribute() == rCompare.getLineAttribute() + && getStrokeAttribute() == rCompare.getStrokeAttribute()); + } + + return false; + } + + basegfx::B2DRange PolygonStrokePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + basegfx::B2DRange aRetval; + + if(getLineAttribute().getWidth()) + { + bool bUseDecomposition(false); + + if(basegfx::B2DLineJoin::Miter == getLineAttribute().getLineJoin()) + { + // if line is mitered, use parent call since mitered line + // geometry may use more space than the geometry grown by half line width + bUseDecomposition = true; + } + + if(!bUseDecomposition && css::drawing::LineCap_SQUARE == getLineAttribute().getLineCap()) + { + // when drawing::LineCap_SQUARE is used the below method to grow the polygon + // range by half line width will not work, so use decomposition. Interestingly, + // the grow method below works perfectly for LineCap_ROUND since the grow is in + // all directions and the rounded cap needs the same grow in all directions independent + // from its orientation. Unfortunately this is not the case for drawing::LineCap_SQUARE + bUseDecomposition = true; + } + + if (bUseDecomposition) + { + // get correct range by using the decomposition fallback, reasons see above cases + + // ofz#947 to optimize calculating the range, turn any slow dashes into a solid line + // when just calculating bounds + attribute::StrokeAttribute aOrigStrokeAttribute = maStrokeAttribute; + const_cast<PolygonStrokePrimitive2D*>(this)->maStrokeAttribute = attribute::StrokeAttribute(); + aRetval = BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation); + const_cast<PolygonStrokePrimitive2D*>(this)->maStrokeAttribute = aOrigStrokeAttribute; + } + else + { + // for all other B2DLINEJOIN_* get the range from the base geometry + // and expand by half the line width + aRetval = getB2DPolygon().getB2DRange(); + aRetval.grow(getLineAttribute().getWidth() * 0.5); + } + } + else + { + // this is a hairline, thus the line width is view-dependent. Get range of polygon + // as base size + aRetval = getB2DPolygon().getB2DRange(); + + if(!aRetval.isEmpty()) + { + // Calculate view-dependent hairline width + const basegfx::B2DVector aDiscreteSize(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 0.0)); + const double fDiscreteHalfLineWidth(aDiscreteSize.getLength() * 0.5); + + if(basegfx::fTools::more(fDiscreteHalfLineWidth, 0.0)) + { + aRetval.grow(fDiscreteHalfLineWidth); + } + } + } + + return aRetval; + } + + // provide unique ID + ImplPrimitive2DIDBlock(PolygonStrokePrimitive2D, PRIMITIVE2D_ID_POLYGONSTROKEPRIMITIVE2D) + + + + void PolygonWavePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(!getB2DPolygon().count()) + return; + + const bool bHasWidth(!basegfx::fTools::equalZero(getWaveWidth())); + const bool bHasHeight(!basegfx::fTools::equalZero(getWaveHeight())); + + if(bHasWidth && bHasHeight) + { + // create waveline curve + const basegfx::B2DPolygon aWaveline(basegfx::utils::createWaveline(getB2DPolygon(), getWaveWidth(), getWaveHeight())); + rContainer.push_back(new PolygonStrokePrimitive2D(aWaveline, getLineAttribute(), getStrokeAttribute())); + } + else + { + // flat waveline, decompose to simple line primitive + rContainer.push_back(new PolygonStrokePrimitive2D(getB2DPolygon(), getLineAttribute(), getStrokeAttribute())); + } + } + + PolygonWavePrimitive2D::PolygonWavePrimitive2D( + const basegfx::B2DPolygon& rPolygon, + const attribute::LineAttribute& rLineAttribute, + const attribute::StrokeAttribute& rStrokeAttribute, + double fWaveWidth, + double fWaveHeight) + : PolygonStrokePrimitive2D(rPolygon, rLineAttribute, rStrokeAttribute), + mfWaveWidth(fWaveWidth), + mfWaveHeight(fWaveHeight) + { + if(mfWaveWidth < 0.0) + { + mfWaveWidth = 0.0; + } + + if(mfWaveHeight < 0.0) + { + mfWaveHeight = 0.0; + } + } + + PolygonWavePrimitive2D::PolygonWavePrimitive2D( + const basegfx::B2DPolygon& rPolygon, + const attribute::LineAttribute& rLineAttribute, + double fWaveWidth, + double fWaveHeight) + : PolygonStrokePrimitive2D(rPolygon, rLineAttribute), + mfWaveWidth(fWaveWidth), + mfWaveHeight(fWaveHeight) + { + if(mfWaveWidth < 0.0) + { + mfWaveWidth = 0.0; + } + + if(mfWaveHeight < 0.0) + { + mfWaveHeight = 0.0; + } + } + + bool PolygonWavePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(PolygonStrokePrimitive2D::operator==(rPrimitive)) + { + const PolygonWavePrimitive2D& rCompare = static_cast<const PolygonWavePrimitive2D&>(rPrimitive); + + return (getWaveWidth() == rCompare.getWaveWidth() + && getWaveHeight() == rCompare.getWaveHeight()); + } + + return false; + } + + basegfx::B2DRange PolygonWavePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + // get range of parent + basegfx::B2DRange aRetval(PolygonStrokePrimitive2D::getB2DRange(rViewInformation)); + + // if WaveHeight, grow by it + if(basegfx::fTools::more(getWaveHeight(), 0.0)) + { + aRetval.grow(getWaveHeight()); + } + + // if line width, grow by it + if(basegfx::fTools::more(getLineAttribute().getWidth(), 0.0)) + { + aRetval.grow(getLineAttribute().getWidth() * 0.5); + } + + return aRetval; + } + + // provide unique ID + ImplPrimitive2DIDBlock(PolygonWavePrimitive2D, PRIMITIVE2D_ID_POLYGONWAVEPRIMITIVE2D) + + + void PolygonStrokeArrowPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // copy local polygon, it may be changed + basegfx::B2DPolygon aLocalPolygon(getB2DPolygon()); + aLocalPolygon.removeDoublePoints(); + basegfx::B2DPolyPolygon aArrowA; + basegfx::B2DPolyPolygon aArrowB; + + if(!aLocalPolygon.isClosed() && aLocalPolygon.count() > 1) + { + // apply arrows + const double fPolyLength(basegfx::utils::getLength(aLocalPolygon)); + double fStart(0.0); + double fEnd(0.0); + double fStartOverlap(0.0); + double fEndOverlap(0.0); + + if(!getStart().isDefault() && getStart().isActive()) + { + // create start arrow primitive and consume + aArrowA = basegfx::utils::createAreaGeometryForLineStartEnd( + aLocalPolygon, getStart().getB2DPolyPolygon(), true, getStart().getWidth(), + fPolyLength, getStart().isCentered() ? 0.5 : 0.0, &fStart); + + // create some overlapping, compromise between straight and peaked markers + // for marker width 0.3cm and marker line width 0.02cm + fStartOverlap = getStart().getWidth() / 15.0; + } + + if(!getEnd().isDefault() && getEnd().isActive()) + { + // create end arrow primitive and consume + aArrowB = basegfx::utils::createAreaGeometryForLineStartEnd( + aLocalPolygon, getEnd().getB2DPolyPolygon(), false, getEnd().getWidth(), + fPolyLength, getEnd().isCentered() ? 0.5 : 0.0, &fEnd); + + // create some overlapping + fEndOverlap = getEnd().getWidth() / 15.0; + } + + if(0.0 != fStart || 0.0 != fEnd) + { + // build new poly, consume something from old poly + aLocalPolygon = basegfx::utils::getSnippetAbsolute(aLocalPolygon, fStart-fStartOverlap, fPolyLength - fEnd + fEndOverlap, fPolyLength); + } + } + + // add shaft + rContainer.push_back(new + PolygonStrokePrimitive2D( + aLocalPolygon, getLineAttribute(), getStrokeAttribute())); + + if(aArrowA.count()) + { + rContainer.push_back( + new PolyPolygonColorPrimitive2D( + aArrowA, getLineAttribute().getColor())); + } + + if(aArrowB.count()) + { + rContainer.push_back( + new PolyPolygonColorPrimitive2D( + aArrowB, getLineAttribute().getColor())); + } + } + + PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D( + const basegfx::B2DPolygon& rPolygon, + const attribute::LineAttribute& rLineAttribute, + const attribute::StrokeAttribute& rStrokeAttribute, + const attribute::LineStartEndAttribute& rStart, + const attribute::LineStartEndAttribute& rEnd) + : PolygonStrokePrimitive2D(rPolygon, rLineAttribute, rStrokeAttribute), + maStart(rStart), + maEnd(rEnd) + { + } + + PolygonStrokeArrowPrimitive2D::PolygonStrokeArrowPrimitive2D( + const basegfx::B2DPolygon& rPolygon, + const attribute::LineAttribute& rLineAttribute, + const attribute::LineStartEndAttribute& rStart, + const attribute::LineStartEndAttribute& rEnd) + : PolygonStrokePrimitive2D(rPolygon, rLineAttribute), + maStart(rStart), + maEnd(rEnd) + { + } + + bool PolygonStrokeArrowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(PolygonStrokePrimitive2D::operator==(rPrimitive)) + { + const PolygonStrokeArrowPrimitive2D& rCompare = static_cast<const PolygonStrokeArrowPrimitive2D&>(rPrimitive); + + return (getStart() == rCompare.getStart() + && getEnd() == rCompare.getEnd()); + } + + return false; + } + + basegfx::B2DRange PolygonStrokeArrowPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + if(getStart().isActive() || getEnd().isActive()) + { + // use decomposition when line start/end is used + return BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation); + } + else + { + // get range from parent + return PolygonStrokePrimitive2D::getB2DRange(rViewInformation); + } + } + + // provide unique ID + ImplPrimitive2DIDBlock(PolygonStrokeArrowPrimitive2D, PRIMITIVE2D_ID_POLYGONSTROKEARROWPRIMITIVE2D) + + } +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/primitivetools2d.cxx b/drawinglayer/source/primitive2d/primitivetools2d.cxx new file mode 100644 index 000000000..da0a378a0 --- /dev/null +++ b/drawinglayer/source/primitive2d/primitivetools2d.cxx @@ -0,0 +1,135 @@ +/* -*- 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/primitivetools2d.hxx> +#include <basegfx/vector/b2dvector.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> + + +namespace drawinglayer::primitive2d +{ + void DiscreteMetricDependentPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // get the current DiscreteUnit, look at X and Y and use the maximum + const basegfx::B2DVector aDiscreteVector(rViewInformation.getInverseObjectToViewTransformation() * basegfx::B2DVector(1.0, 1.0)); + const double fDiscreteUnit(std::min(fabs(aDiscreteVector.getX()), fabs(aDiscreteVector.getY()))); + + if(!getBuffered2DDecomposition().empty() && !basegfx::fTools::equal(fDiscreteUnit, getDiscreteUnit())) + { + // conditions of last local decomposition have changed, delete + const_cast< DiscreteMetricDependentPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + + if(getBuffered2DDecomposition().empty()) + { + // remember new valid DiscreteUnit + const_cast< DiscreteMetricDependentPrimitive2D* >(this)->mfDiscreteUnit = fDiscreteUnit; + } + + // call base implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + + + + void ViewportDependentPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // get the current Viewport + const basegfx::B2DRange& rViewport = rViewInformation.getViewport(); + + if(!getBuffered2DDecomposition().empty() && !rViewport.equal(getViewport())) + { + // conditions of last local decomposition have changed, delete + const_cast< ViewportDependentPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + + if(getBuffered2DDecomposition().empty()) + { + // remember new valid DiscreteUnit + const_cast< ViewportDependentPrimitive2D* >(this)->maViewport = rViewport; + } + + // call base implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + void ViewTransformationDependentPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // get the current ViewTransformation + const basegfx::B2DHomMatrix& rViewTransformation = rViewInformation.getViewTransformation(); + + if(!getBuffered2DDecomposition().empty() && rViewTransformation != getViewTransformation()) + { + // conditions of last local decomposition have changed, delete + const_cast< ViewTransformationDependentPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + + if(getBuffered2DDecomposition().empty()) + { + // remember new valid ViewTransformation + const_cast< ViewTransformationDependentPrimitive2D* >(this)->maViewTransformation = rViewTransformation; + } + + // call base implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + void ObjectAndViewTransformationDependentPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // get the current ViewTransformation + const basegfx::B2DHomMatrix& rViewTransformation = rViewInformation.getViewTransformation(); + + if(!getBuffered2DDecomposition().empty() && rViewTransformation != getViewTransformation()) + { + // conditions of last local decomposition have changed, delete + const_cast< ObjectAndViewTransformationDependentPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + + // get the current ObjectTransformation + const basegfx::B2DHomMatrix& rObjectTransformation = rViewInformation.getObjectTransformation(); + + if(!getBuffered2DDecomposition().empty() && rObjectTransformation != getObjectTransformation()) + { + // conditions of last local decomposition have changed, delete + const_cast< ObjectAndViewTransformationDependentPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + + if(getBuffered2DDecomposition().empty()) + { + // remember new valid ViewTransformation, and ObjectTransformation + const_cast< ObjectAndViewTransformationDependentPrimitive2D* >(this)->maViewTransformation = rViewTransformation; + const_cast< ObjectAndViewTransformationDependentPrimitive2D* >(this)->maObjectTransformation = rObjectTransformation; + } + + // call base implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/sceneprimitive2d.cxx b/drawinglayer/source/primitive2d/sceneprimitive2d.cxx new file mode 100644 index 000000000..e58c05e40 --- /dev/null +++ b/drawinglayer/source/primitive2d/sceneprimitive2d.cxx @@ -0,0 +1,697 @@ +/* -*- 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/sceneprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <drawinglayer/attribute/sdrlightattribute3d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <processor3d/zbufferprocessor3d.hxx> +#include <processor3d/shadow3dextractor.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <svtools/optionsdrawinglayer.hxx> +#include <processor3d/geometry2dextractor.hxx> +#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <basegfx/raster/bzpixelraster.hxx> +#include <vcl/BitmapTools.hxx> +#include <comphelper/threadpool.hxx> +#include <toolkit/helper/vclunohelper.hxx> + +using namespace com::sun::star; + +namespace +{ + BitmapEx BPixelRasterToBitmapEx(const basegfx::BZPixelRaster& rRaster, sal_uInt16 mnAntiAlialize) + { + BitmapEx aRetval; + const sal_uInt32 nWidth(mnAntiAlialize ? rRaster.getWidth()/mnAntiAlialize : rRaster.getWidth()); + const sal_uInt32 nHeight(mnAntiAlialize ? rRaster.getHeight()/mnAntiAlialize : rRaster.getHeight()); + + if(nWidth && nHeight) + { + const Size aDestSize(nWidth, nHeight); + vcl::bitmap::RawBitmap aContent(aDestSize, 32); + + if(mnAntiAlialize) + { + const sal_uInt16 nDivisor(mnAntiAlialize * mnAntiAlialize); + + for(sal_uInt32 y(0); y < nHeight; y++) + { + for(sal_uInt32 x(0); x < nWidth; x++) + { + sal_uInt16 nRed(0); + sal_uInt16 nGreen(0); + sal_uInt16 nBlue(0); + sal_uInt16 nOpacity(0); + sal_uInt32 nIndex(rRaster.getIndexFromXY(x * mnAntiAlialize, y * mnAntiAlialize)); + + for(sal_uInt32 c(0); c < mnAntiAlialize; c++) + { + for(sal_uInt32 d(0); d < mnAntiAlialize; d++) + { + const basegfx::BPixel& rPixel(rRaster.getBPixel(nIndex++)); + nRed = nRed + rPixel.getRed(); + nGreen = nGreen + rPixel.getGreen(); + nBlue = nBlue + rPixel.getBlue(); + nOpacity = nOpacity + rPixel.getOpacity(); + } + + nIndex += rRaster.getWidth() - mnAntiAlialize; + } + + nOpacity = nOpacity / nDivisor; + + if(nOpacity) + { + aContent.SetPixel(y, x, Color( + 255 - static_cast<sal_uInt8>(nOpacity), + static_cast<sal_uInt8>(nRed / nDivisor), + static_cast<sal_uInt8>(nGreen / nDivisor), + static_cast<sal_uInt8>(nBlue / nDivisor) )); + } + else + aContent.SetPixel(y, x, Color(255, 0, 0, 0)); + } + } + } + else + { + sal_uInt32 nIndex(0); + + for(sal_uInt32 y(0); y < nHeight; y++) + { + for(sal_uInt32 x(0); x < nWidth; x++) + { + const basegfx::BPixel& rPixel(rRaster.getBPixel(nIndex++)); + + if(rPixel.getOpacity()) + { + aContent.SetPixel(y, x, Color(255 - rPixel.getOpacity(), rPixel.getRed(), rPixel.getGreen(), rPixel.getBlue())); + } + else + aContent.SetPixel(y, x, Color(255, 0, 0, 0)); + } + } + } + + aRetval = vcl::bitmap::CreateFromData(std::move(aContent)); + + // #i101811# set PrefMapMode and PrefSize at newly created Bitmap + aRetval.SetPrefMapMode(MapMode(MapUnit::MapPixel)); + aRetval.SetPrefSize(Size(nWidth, nHeight)); + } + + return aRetval; + } +} // end of anonymous namespace + +namespace drawinglayer::primitive2d +{ + bool ScenePrimitive2D::impGetShadow3D() const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // create on demand + if(!mbShadow3DChecked && !getChildren3D().empty()) + { + basegfx::B3DVector aLightNormal; + const double fShadowSlant(getSdrSceneAttribute().getShadowSlant()); + const basegfx::B3DRange aScene3DRange(getChildren3D().getB3DRange(getViewInformation3D())); + + if(!maSdrLightingAttribute.getLightVector().empty()) + { + // get light normal from first light and normalize + aLightNormal = maSdrLightingAttribute.getLightVector()[0].getDirection(); + aLightNormal.normalize(); + } + + // create shadow extraction processor + processor3d::Shadow3DExtractingProcessor aShadowProcessor( + getViewInformation3D(), + getObjectTransformation(), + aLightNormal, + fShadowSlant, + aScene3DRange); + + // process local primitives + aShadowProcessor.process(getChildren3D()); + + // fetch result and set checked flag + const_cast< ScenePrimitive2D* >(this)->maShadowPrimitives = aShadowProcessor.getPrimitive2DSequence(); + const_cast< ScenePrimitive2D* >(this)->mbShadow3DChecked = true; + } + + // return if there are shadow primitives + return !maShadowPrimitives.empty(); + } + + void ScenePrimitive2D::calculateDiscreteSizes( + const geometry::ViewInformation2D& rViewInformation, + basegfx::B2DRange& rDiscreteRange, + basegfx::B2DRange& rVisibleDiscreteRange, + basegfx::B2DRange& rUnitVisibleRange) const + { + // use unit range and transform to discrete coordinates + rDiscreteRange = basegfx::B2DRange(0.0, 0.0, 1.0, 1.0); + rDiscreteRange.transform(rViewInformation.getObjectToViewTransformation() * getObjectTransformation()); + + // clip it against discrete Viewport (if set) + rVisibleDiscreteRange = rDiscreteRange; + + if(!rViewInformation.getViewport().isEmpty()) + { + rVisibleDiscreteRange.intersect(rViewInformation.getDiscreteViewport()); + } + + if(rVisibleDiscreteRange.isEmpty()) + { + rUnitVisibleRange = rVisibleDiscreteRange; + } + else + { + // create UnitVisibleRange containing unit range values [0.0 .. 1.0] describing + // the relative position of rVisibleDiscreteRange inside rDiscreteRange + const double fDiscreteScaleFactorX(basegfx::fTools::equalZero(rDiscreteRange.getWidth()) ? 1.0 : 1.0 / rDiscreteRange.getWidth()); + const double fDiscreteScaleFactorY(basegfx::fTools::equalZero(rDiscreteRange.getHeight()) ? 1.0 : 1.0 / rDiscreteRange.getHeight()); + + const double fMinX(basegfx::fTools::equal(rVisibleDiscreteRange.getMinX(), rDiscreteRange.getMinX()) + ? 0.0 + : (rVisibleDiscreteRange.getMinX() - rDiscreteRange.getMinX()) * fDiscreteScaleFactorX); + const double fMinY(basegfx::fTools::equal(rVisibleDiscreteRange.getMinY(), rDiscreteRange.getMinY()) + ? 0.0 + : (rVisibleDiscreteRange.getMinY() - rDiscreteRange.getMinY()) * fDiscreteScaleFactorY); + + const double fMaxX(basegfx::fTools::equal(rVisibleDiscreteRange.getMaxX(), rDiscreteRange.getMaxX()) + ? 1.0 + : (rVisibleDiscreteRange.getMaxX() - rDiscreteRange.getMinX()) * fDiscreteScaleFactorX); + const double fMaxY(basegfx::fTools::equal(rVisibleDiscreteRange.getMaxY(), rDiscreteRange.getMaxY()) + ? 1.0 + : (rVisibleDiscreteRange.getMaxY() - rDiscreteRange.getMinY()) * fDiscreteScaleFactorY); + + rUnitVisibleRange = basegfx::B2DRange(fMinX, fMinY, fMaxX, fMaxY); + } + } + + void ScenePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + // create 2D shadows from contained 3D primitives. This creates the shadow primitives on demand and tells if + // there are some or not. Do this at start, the shadow might still be visible even when the scene is not + if(impGetShadow3D()) + { + // test visibility + const basegfx::B2DRange aShadow2DRange(maShadowPrimitives.getB2DRange(rViewInformation)); + const basegfx::B2DRange aViewRange( + rViewInformation.getViewport()); + + if(aViewRange.isEmpty() || aShadow2DRange.overlaps(aViewRange)) + { + // add extracted 2d shadows (before 3d scene creations itself) + rContainer.insert(rContainer.end(), maShadowPrimitives.begin(), maShadowPrimitives.end()); + } + } + + // get the involved ranges (see helper method calculateDiscreteSizes for details) + basegfx::B2DRange aDiscreteRange; + basegfx::B2DRange aVisibleDiscreteRange; + basegfx::B2DRange aUnitVisibleRange; + + calculateDiscreteSizes(rViewInformation, aDiscreteRange, aVisibleDiscreteRange, aUnitVisibleRange); + + if(aVisibleDiscreteRange.isEmpty()) + return; + + // test if discrete view size (pixel) maybe too big and limit it + double fViewSizeX(aVisibleDiscreteRange.getWidth()); + double fViewSizeY(aVisibleDiscreteRange.getHeight()); + const double fViewVisibleArea(fViewSizeX * fViewSizeY); + const SvtOptionsDrawinglayer aDrawinglayerOpt; + const double fMaximumVisibleArea(aDrawinglayerOpt.GetQuadratic3DRenderLimit()); + double fReduceFactor(1.0); + + if(fViewVisibleArea > fMaximumVisibleArea) + { + fReduceFactor = sqrt(fMaximumVisibleArea / fViewVisibleArea); + fViewSizeX *= fReduceFactor; + fViewSizeY *= fReduceFactor; + } + + if(rViewInformation.getReducedDisplayQuality()) + { + // when reducing the visualisation is allowed (e.g. an OverlayObject + // only needed for dragging), reduce resolution extra + // to speed up dragging interactions + const double fArea(fViewSizeX * fViewSizeY); + if (fArea != 0.0) + { + double fReducedVisualisationFactor(1.0 / (sqrt(fArea) * (1.0 / 170.0))); + + if(fReducedVisualisationFactor > 1.0) + { + fReducedVisualisationFactor = 1.0; + } + else if(fReducedVisualisationFactor < 0.20) + { + fReducedVisualisationFactor = 0.20; + } + + if(fReducedVisualisationFactor != 1.0) + { + fReduceFactor *= fReducedVisualisationFactor; + } + } + } + + // determine the oversample value + static const sal_uInt16 nDefaultOversampleValue(3); + const sal_uInt16 nOversampleValue(aDrawinglayerOpt.IsAntiAliasing() ? nDefaultOversampleValue : 0); + + geometry::ViewInformation3D aViewInformation3D(getViewInformation3D()); + { + // calculate a transformation from DiscreteRange to evtl. rotated/sheared content. + // Start with full transformation from object to discrete units + basegfx::B2DHomMatrix aObjToUnit(rViewInformation.getObjectToViewTransformation() * getObjectTransformation()); + + // bring to unit coordinates by applying inverse DiscreteRange + aObjToUnit.translate(-aDiscreteRange.getMinX(), -aDiscreteRange.getMinY()); + if (aDiscreteRange.getWidth() != 0.0 && aDiscreteRange.getHeight() != 0.0) + { + aObjToUnit.scale(1.0 / aDiscreteRange.getWidth(), 1.0 / aDiscreteRange.getHeight()); + } + + // calculate transformed user coordinate system + const basegfx::B2DPoint aStandardNull(0.0, 0.0); + const basegfx::B2DPoint aUnitRangeTopLeft(aObjToUnit * aStandardNull); + const basegfx::B2DVector aStandardXAxis(1.0, 0.0); + const basegfx::B2DVector aUnitRangeXAxis(aObjToUnit * aStandardXAxis); + const basegfx::B2DVector aStandardYAxis(0.0, 1.0); + const basegfx::B2DVector aUnitRangeYAxis(aObjToUnit * aStandardYAxis); + + if(!aUnitRangeTopLeft.equal(aStandardNull) || !aUnitRangeXAxis.equal(aStandardXAxis) || !aUnitRangeYAxis.equal(aStandardYAxis)) + { + // build transformation from unit range to user coordinate system; the unit range + // X and Y axes are the column vectors, the null point is the offset + basegfx::B2DHomMatrix aUnitRangeToUser; + + aUnitRangeToUser.set3x2( + aUnitRangeXAxis.getX(), aUnitRangeYAxis.getX(), aUnitRangeTopLeft.getX(), + aUnitRangeXAxis.getY(), aUnitRangeYAxis.getY(), aUnitRangeTopLeft.getY()); + + // decompose to allow to apply this to the 3D transformation + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + aUnitRangeToUser.decompose(aScale, aTranslate, fRotate, fShearX); + + // apply before DeviceToView and after Projection, 3D is in range [-1.0 .. 1.0] in X,Y and Z + // and not yet flipped in Y + basegfx::B3DHomMatrix aExtendedProjection(aViewInformation3D.getProjection()); + + // bring to unit coordinates, flip Y, leave Z unchanged + aExtendedProjection.scale(0.5, -0.5, 1.0); + aExtendedProjection.translate(0.5, 0.5, 0.0); + + // apply extra; Y is flipped now, go with positive shear and rotate values + aExtendedProjection.scale(aScale.getX(), aScale.getY(), 1.0); + aExtendedProjection.shearXZ(fShearX, 0.0); + aExtendedProjection.rotate(0.0, 0.0, fRotate); + aExtendedProjection.translate(aTranslate.getX(), aTranslate.getY(), 0.0); + + // back to state after projection + aExtendedProjection.translate(-0.5, -0.5, 0.0); + aExtendedProjection.scale(2.0, -2.0, 1.0); + + aViewInformation3D = geometry::ViewInformation3D( + aViewInformation3D.getObjectTransformation(), + aViewInformation3D.getOrientation(), + aExtendedProjection, + aViewInformation3D.getDeviceToView(), + aViewInformation3D.getViewTime(), + aViewInformation3D.getExtendedInformationSequence()); + } + } + + // calculate logic render size in world coordinates for usage in renderer + const basegfx::B2DHomMatrix& aInverseOToV(rViewInformation.getInverseObjectToViewTransformation()); + const double fLogicX((aInverseOToV * basegfx::B2DVector(aDiscreteRange.getWidth() * fReduceFactor, 0.0)).getLength()); + const double fLogicY((aInverseOToV * basegfx::B2DVector(0.0, aDiscreteRange.getHeight() * fReduceFactor)).getLength()); + + // generate ViewSizes + const double fFullViewSizeX((rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(fLogicX, 0.0)).getLength()); + const double fFullViewSizeY((rViewInformation.getObjectToViewTransformation() * basegfx::B2DVector(0.0, fLogicY)).getLength()); + + // generate RasterWidth and RasterHeight for visible part + const sal_Int32 nRasterWidth(basegfx::fround(fFullViewSizeX * aUnitVisibleRange.getWidth()) + 1); + const sal_Int32 nRasterHeight(basegfx::fround(fFullViewSizeY * aUnitVisibleRange.getHeight()) + 1); + + if(!(nRasterWidth && nRasterHeight)) + return; + + // create view unit buffer + basegfx::BZPixelRaster aBZPixelRaster( + nOversampleValue ? nRasterWidth * nOversampleValue : nRasterWidth, + nOversampleValue ? nRasterHeight * nOversampleValue : nRasterHeight); + + // check for parallel execution possibilities + static bool bMultithreadAllowed = false; // loplugin:constvars:ignore + sal_Int32 nThreadCount(0); + comphelper::ThreadPool& rThreadPool(comphelper::ThreadPool::getSharedOptimalPool()); + + if(bMultithreadAllowed) + { + nThreadCount = rThreadPool.getWorkerCount(); + + if(nThreadCount > 1) + { + // at least use 10px per processor, so limit number of processors to + // target pixel size divided by 10 (which might be zero what is okay) + nThreadCount = std::min(nThreadCount, nRasterHeight / 10); + } + } + + if(nThreadCount > 1) + { + class Executor : public comphelper::ThreadTask + { + private: + std::unique_ptr<processor3d::ZBufferProcessor3D> mpZBufferProcessor3D; + const primitive3d::Primitive3DContainer& mrChildren3D; + + public: + explicit Executor( + std::shared_ptr<comphelper::ThreadTaskTag> const & rTag, + std::unique_ptr<processor3d::ZBufferProcessor3D> pZBufferProcessor3D, + const primitive3d::Primitive3DContainer& rChildren3D) + : comphelper::ThreadTask(rTag), + mpZBufferProcessor3D(std::move(pZBufferProcessor3D)), + mrChildren3D(rChildren3D) + { + } + + virtual void doWork() override + { + mpZBufferProcessor3D->process(mrChildren3D); + mpZBufferProcessor3D->finish(); + mpZBufferProcessor3D.reset(); + } + }; + + const sal_uInt32 nLinesPerThread(aBZPixelRaster.getHeight() / nThreadCount); + std::shared_ptr<comphelper::ThreadTaskTag> aTag = comphelper::ThreadPool::createThreadTaskTag(); + + for(sal_Int32 a(0); a < nThreadCount; a++) + { + std::unique_ptr<processor3d::ZBufferProcessor3D> pNewZBufferProcessor3D(new processor3d::ZBufferProcessor3D( + aViewInformation3D, + getSdrSceneAttribute(), + getSdrLightingAttribute(), + aUnitVisibleRange, + nOversampleValue, + fFullViewSizeX, + fFullViewSizeY, + aBZPixelRaster, + nLinesPerThread * a, + a + 1 == nThreadCount ? aBZPixelRaster.getHeight() : nLinesPerThread * (a + 1))); + std::unique_ptr<Executor> pExecutor(new Executor(aTag, std::move(pNewZBufferProcessor3D), getChildren3D())); + rThreadPool.pushTask(std::move(pExecutor)); + } + + rThreadPool.waitUntilDone(aTag); + } + else + { + // use default 3D primitive processor to create BitmapEx for aUnitVisiblePart and process + processor3d::ZBufferProcessor3D aZBufferProcessor3D( + aViewInformation3D, + getSdrSceneAttribute(), + getSdrLightingAttribute(), + aUnitVisibleRange, + nOversampleValue, + fFullViewSizeX, + fFullViewSizeY, + aBZPixelRaster, + 0, + aBZPixelRaster.getHeight()); + + aZBufferProcessor3D.process(getChildren3D()); + aZBufferProcessor3D.finish(); + } + + const_cast< ScenePrimitive2D* >(this)->maOldRenderedBitmap = BPixelRasterToBitmapEx(aBZPixelRaster, nOversampleValue); + const Size aBitmapSizePixel(maOldRenderedBitmap.GetSizePixel()); + + if(!(aBitmapSizePixel.getWidth() && aBitmapSizePixel.getHeight())) + return; + + // create transform for the created bitmap in discrete coordinates first. + basegfx::B2DHomMatrix aNew2DTransform; + + aNew2DTransform.set(0, 0, aVisibleDiscreteRange.getWidth()); + aNew2DTransform.set(1, 1, aVisibleDiscreteRange.getHeight()); + aNew2DTransform.set(0, 2, aVisibleDiscreteRange.getMinX()); + aNew2DTransform.set(1, 2, aVisibleDiscreteRange.getMinY()); + + // transform back to world coordinates for usage in primitive creation + aNew2DTransform *= aInverseOToV; + + // create bitmap primitive and add + rContainer.push_back( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(maOldRenderedBitmap), + aNew2DTransform)); + + // test: Allow to add an outline in the debugger when tests are needed + static bool bAddOutlineToCreated3DSceneRepresentation(false); // loplugin:constvars:ignore + + if(bAddOutlineToCreated3DSceneRepresentation) + { + basegfx::B2DPolygon aOutline(basegfx::utils::createUnitPolygon()); + aOutline.transform(aNew2DTransform); + rContainer.push_back(new PolygonHairlinePrimitive2D(aOutline, basegfx::BColor(1.0, 0.0, 0.0))); + } + } + + Primitive2DContainer ScenePrimitive2D::getGeometry2D() const + { + Primitive2DContainer aRetval; + + // create 2D projected geometry from 3D geometry + if(!getChildren3D().empty()) + { + // create 2D geometry extraction processor + processor3d::Geometry2DExtractingProcessor aGeometryProcessor( + getViewInformation3D(), + getObjectTransformation()); + + // process local primitives + aGeometryProcessor.process(getChildren3D()); + + // fetch result + aRetval = aGeometryProcessor.getPrimitive2DSequence(); + } + + return aRetval; + } + + Primitive2DContainer ScenePrimitive2D::getShadow2D() const + { + Primitive2DContainer aRetval; + + // create 2D shadows from contained 3D primitives + if(impGetShadow3D()) + { + // add extracted 2d shadows (before 3d scene creations itself) + aRetval = maShadowPrimitives; + } + + return aRetval; + } + + bool ScenePrimitive2D::tryToCheckLastVisualisationDirectHit(const basegfx::B2DPoint& rLogicHitPoint, bool& o_rResult) const + { + if(!maOldRenderedBitmap.IsEmpty() && !maOldUnitVisiblePart.isEmpty()) + { + basegfx::B2DHomMatrix aInverseSceneTransform(getObjectTransformation()); + aInverseSceneTransform.invert(); + const basegfx::B2DPoint aRelativePoint(aInverseSceneTransform * rLogicHitPoint); + + if(maOldUnitVisiblePart.isInside(aRelativePoint)) + { + // calculate coordinates relative to visualized part + double fDivisorX(maOldUnitVisiblePart.getWidth()); + double fDivisorY(maOldUnitVisiblePart.getHeight()); + + if(basegfx::fTools::equalZero(fDivisorX)) + { + fDivisorX = 1.0; + } + + if(basegfx::fTools::equalZero(fDivisorY)) + { + fDivisorY = 1.0; + } + + const double fRelativeX((aRelativePoint.getX() - maOldUnitVisiblePart.getMinX()) / fDivisorX); + const double fRelativeY((aRelativePoint.getY() - maOldUnitVisiblePart.getMinY()) / fDivisorY); + + // combine with real BitmapSizePixel to get bitmap coordinates + const Size aBitmapSizePixel(maOldRenderedBitmap.GetSizePixel()); + const sal_Int32 nX(basegfx::fround(fRelativeX * aBitmapSizePixel.Width())); + const sal_Int32 nY(basegfx::fround(fRelativeY * aBitmapSizePixel.Height())); + + // try to get a statement about transparency in that pixel + o_rResult = (0xff != maOldRenderedBitmap.GetTransparency(nX, nY)); + return true; + } + } + + return false; + } + + ScenePrimitive2D::ScenePrimitive2D( + const primitive3d::Primitive3DContainer& rxChildren3D, + const attribute::SdrSceneAttribute& rSdrSceneAttribute, + const attribute::SdrLightingAttribute& rSdrLightingAttribute, + const basegfx::B2DHomMatrix& rObjectTransformation, + const geometry::ViewInformation3D& rViewInformation3D) + : BufferedDecompositionPrimitive2D(), + mxChildren3D(rxChildren3D), + maSdrSceneAttribute(rSdrSceneAttribute), + maSdrLightingAttribute(rSdrLightingAttribute), + maObjectTransformation(rObjectTransformation), + maViewInformation3D(rViewInformation3D), + maShadowPrimitives(), + mbShadow3DChecked(false), + mfOldDiscreteSizeX(0.0), + mfOldDiscreteSizeY(0.0), + maOldUnitVisiblePart(), + maOldRenderedBitmap() + { + } + + bool ScenePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const ScenePrimitive2D& rCompare = static_cast<const ScenePrimitive2D&>(rPrimitive); + + return (getChildren3D() == rCompare.getChildren3D() + && getSdrSceneAttribute() == rCompare.getSdrSceneAttribute() + && getSdrLightingAttribute() == rCompare.getSdrLightingAttribute() + && getObjectTransformation() == rCompare.getObjectTransformation() + && getViewInformation3D() == rCompare.getViewInformation3D()); + } + + return false; + } + + basegfx::B2DRange ScenePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + // transform unit range to discrete coordinate range + basegfx::B2DRange aRetval(0.0, 0.0, 1.0, 1.0); + aRetval.transform(rViewInformation.getObjectToViewTransformation() * getObjectTransformation()); + + // force to discrete expanded bounds (it grows, so expanding works perfectly well) + aRetval.expand(basegfx::B2DTuple(floor(aRetval.getMinX()), floor(aRetval.getMinY()))); + aRetval.expand(basegfx::B2DTuple(ceil(aRetval.getMaxX()), ceil(aRetval.getMaxY()))); + + // transform back from discrete (view) to world coordinates + aRetval.transform(rViewInformation.getInverseObjectToViewTransformation()); + + // expand by evtl. existing shadow primitives + if(impGetShadow3D()) + { + const basegfx::B2DRange aShadow2DRange(maShadowPrimitives.getB2DRange(rViewInformation)); + + if(!aShadow2DRange.isEmpty()) + { + aRetval.expand(aShadow2DRange); + } + } + + return aRetval; + } + + void ScenePrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // get the involved ranges (see helper method calculateDiscreteSizes for details) + basegfx::B2DRange aDiscreteRange; + basegfx::B2DRange aUnitVisibleRange; + bool bNeedNewDecomposition(false); + bool bDiscreteSizesAreCalculated(false); + + if(!getBuffered2DDecomposition().empty()) + { + basegfx::B2DRange aVisibleDiscreteRange; + calculateDiscreteSizes(rViewInformation, aDiscreteRange, aVisibleDiscreteRange, aUnitVisibleRange); + bDiscreteSizesAreCalculated = true; + + // needs to be painted when the new part is not part of the last + // decomposition + if(!maOldUnitVisiblePart.isInside(aUnitVisibleRange)) + { + bNeedNewDecomposition = true; + } + + // display has changed and cannot be reused when resolution got bigger. It + // can be reused when resolution got smaller, though. + if(!bNeedNewDecomposition) + { + if(basegfx::fTools::more(aDiscreteRange.getWidth(), mfOldDiscreteSizeX) || + basegfx::fTools::more(aDiscreteRange.getHeight(), mfOldDiscreteSizeY)) + { + bNeedNewDecomposition = true; + } + } + } + + if(bNeedNewDecomposition) + { + // conditions of last local decomposition have changed, delete + const_cast< ScenePrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + + if(getBuffered2DDecomposition().empty()) + { + if(!bDiscreteSizesAreCalculated) + { + basegfx::B2DRange aVisibleDiscreteRange; + calculateDiscreteSizes(rViewInformation, aDiscreteRange, aVisibleDiscreteRange, aUnitVisibleRange); + } + + // remember last used NewDiscreteSize and NewUnitVisiblePart + ScenePrimitive2D* pThat = const_cast< ScenePrimitive2D* >(this); + pThat->mfOldDiscreteSizeX = aDiscreteRange.getWidth(); + pThat->mfOldDiscreteSizeY = aDiscreteRange.getHeight(); + pThat->maOldUnitVisiblePart = aUnitVisibleRange; + } + + // use parent implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + // provide unique ID + ImplPrimitive2DIDBlock(ScenePrimitive2D, PRIMITIVE2D_ID_SCENEPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/sdrdecompositiontools2d.cxx b/drawinglayer/source/primitive2d/sdrdecompositiontools2d.cxx new file mode 100644 index 000000000..5080a92a8 --- /dev/null +++ b/drawinglayer/source/primitive2d/sdrdecompositiontools2d.cxx @@ -0,0 +1,106 @@ +/* -*- 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/sdrdecompositiontools2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx> + + +namespace drawinglayer::primitive2d +{ + Primitive2DReference createHiddenGeometryPrimitives2D( + const basegfx::B2DHomMatrix& rMatrix) + { + const basegfx::B2DPolygon& aUnitOutline(basegfx::utils::createUnitPolygon()); + + return createHiddenGeometryPrimitives2D( + false/*bFilled*/, + basegfx::B2DPolyPolygon(aUnitOutline), + rMatrix); + } + + Primitive2DReference createHiddenGeometryPrimitives2D( + const basegfx::B2DPolyPolygon& rPolyPolygon) + { + return createHiddenGeometryPrimitives2D( + false/*bFilled*/, + rPolyPolygon, + basegfx::B2DHomMatrix()); + } + + Primitive2DReference createHiddenGeometryPrimitives2D( + bool bFilled, + const basegfx::B2DRange& rRange) + { + return createHiddenGeometryPrimitives2D( + bFilled, + rRange, + basegfx::B2DHomMatrix()); + } + + Primitive2DReference createHiddenGeometryPrimitives2D( + bool bFilled, + const basegfx::B2DRange& rRange, + const basegfx::B2DHomMatrix& rMatrix) + { + const basegfx::B2DPolyPolygon aOutline(basegfx::utils::createPolygonFromRect(rRange)); + + return createHiddenGeometryPrimitives2D( + bFilled, + aOutline, + rMatrix); + } + + Primitive2DReference createHiddenGeometryPrimitives2D( + bool bFilled, + const basegfx::B2DPolyPolygon& rPolyPolygon, + const basegfx::B2DHomMatrix& rMatrix) + { + // create fill or line primitive + Primitive2DReference xReference; + basegfx::B2DPolyPolygon aScaledOutline(rPolyPolygon); + aScaledOutline.transform(rMatrix); + + if(bFilled) + { + xReference = new PolyPolygonColorPrimitive2D( + aScaledOutline, + basegfx::BColor(0.0, 0.0, 0.0)); + } + else + { + const basegfx::BColor aGrayTone(0xc0 / 255.0, 0xc0 / 255.0, 0xc0 / 255.0); + + xReference = new PolyPolygonHairlinePrimitive2D( + aScaledOutline, + aGrayTone); + } + + // create HiddenGeometryPrimitive2D + return Primitive2DReference( + new HiddenGeometryPrimitive2D(Primitive2DContainer { xReference })); + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/shadowprimitive2d.cxx b/drawinglayer/source/primitive2d/shadowprimitive2d.cxx new file mode 100644 index 000000000..8cb831149 --- /dev/null +++ b/drawinglayer/source/primitive2d/shadowprimitive2d.cxx @@ -0,0 +1,86 @@ +/* -*- 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/shadowprimitive2d.hxx> +#include <basegfx/color/bcolormodifier.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + ShadowPrimitive2D::ShadowPrimitive2D( + const basegfx::B2DHomMatrix& rShadowTransform, + const basegfx::BColor& rShadowColor, + const Primitive2DContainer& rChildren) + : GroupPrimitive2D(rChildren), + maShadowTransform(rShadowTransform), + maShadowColor(rShadowColor) + { + } + + bool ShadowPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BasePrimitive2D::operator==(rPrimitive)) + { + const ShadowPrimitive2D& rCompare = static_cast< const ShadowPrimitive2D& >(rPrimitive); + + return (getShadowTransform() == rCompare.getShadowTransform() + && getShadowColor() == rCompare.getShadowColor()); + } + + return false; + } + + basegfx::B2DRange ShadowPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + basegfx::B2DRange aRetval(getChildren().getB2DRange(rViewInformation)); + aRetval.transform(getShadowTransform()); + return aRetval; + } + + void ShadowPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(getChildren().empty()) + return; + + // create a modifiedColorPrimitive containing the shadow color and the content + const basegfx::BColorModifierSharedPtr aBColorModifier = + std::make_shared<basegfx::BColorModifier_replace>( + getShadowColor()); + const Primitive2DReference xRefA( + new ModifiedColorPrimitive2D( + getChildren(), + aBColorModifier)); + const Primitive2DContainer aSequenceB { xRefA }; + + // build transformed primitiveVector with shadow offset and add to target + rVisitor.append(new TransformPrimitive2D(getShadowTransform(), aSequenceB)); + } + + // provide unique ID + ImplPrimitive2DIDBlock(ShadowPrimitive2D, PRIMITIVE2D_ID_SHADOWPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx b/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx new file mode 100644 index 000000000..4c5b1b2c6 --- /dev/null +++ b/drawinglayer/source/primitive2d/softedgeprimitive2d.cxx @@ -0,0 +1,69 @@ +/* -*- 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/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/softedgeprimitive2d.hxx> + +namespace drawinglayer::primitive2d +{ +SoftEdgePrimitive2D::SoftEdgePrimitive2D(double fRadius, const Primitive2DContainer& rChildren) + : GroupPrimitive2D(rChildren) + , mfRadius(fRadius) +{ +} + +bool SoftEdgePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const +{ + if (GroupPrimitive2D::operator==(rPrimitive)) + { + auto& rCompare = static_cast<const SoftEdgePrimitive2D&>(rPrimitive); + return getRadius() == rCompare.getRadius(); + } + + return false; +} + +void SoftEdgePrimitive2D::get2DDecomposition( + Primitive2DDecompositionVisitor& rVisitor, + const geometry::ViewInformation2D& rViewInformation) const +{ + if (getChildren().empty()) + return; + + if (!mbInMaskGeneration) + { + GroupPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + return; + } + + // create a modifiedColorPrimitive containing the *black* color and the content. Using black + // on white allows creating useful mask in VclPixelProcessor2D::processSoftEdgePrimitive2D. + basegfx::BColorModifierSharedPtr aBColorModifier + = std::make_shared<basegfx::BColorModifier_replace>(basegfx::BColor()); + + const Primitive2DReference xRef(new ModifiedColorPrimitive2D(getChildren(), aBColorModifier)); + rVisitor.append(xRef); +} + +ImplPrimitive2DIDBlock(SoftEdgePrimitive2D, PRIMITIVE2D_ID_SOFTEDGEPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/structuretagprimitive2d.cxx b/drawinglayer/source/primitive2d/structuretagprimitive2d.cxx new file mode 100644 index 000000000..c46b7df87 --- /dev/null +++ b/drawinglayer/source/primitive2d/structuretagprimitive2d.cxx @@ -0,0 +1,59 @@ +/* -*- 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/structuretagprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + StructureTagPrimitive2D::StructureTagPrimitive2D( + const vcl::PDFWriter::StructElement& rStructureElement, + bool bBackground, + bool bIsImage, + const Primitive2DContainer& rChildren) + : GroupPrimitive2D(rChildren), + maStructureElement(rStructureElement), + mbBackground(bBackground), + mbIsImage(bIsImage) + { + } + + bool StructureTagPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const StructureTagPrimitive2D& rCompare = static_cast<const StructureTagPrimitive2D&>(rPrimitive); + + return (isBackground() == rCompare.isBackground() && + isImage() == rCompare.isImage()); + } + + return false; + } + + // provide unique ID + ImplPrimitive2DIDBlock(StructureTagPrimitive2D, PRIMITIVE2D_ID_STRUCTURETAGPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/svggradientprimitive2d.cxx b/drawinglayer/source/primitive2d/svggradientprimitive2d.cxx new file mode 100644 index 000000000..e6906a638 --- /dev/null +++ b/drawinglayer/source/primitive2d/svggradientprimitive2d.cxx @@ -0,0 +1,1089 @@ +/* -*- 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/svggradientprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <sal/log.hxx> +#include <cmath> +#include <vcl/skia/SkiaHelper.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 (full quality) + sal_uInt32 nSteps(basegfx::fround(rColorA.getDistance(rColorB) * 255.0)); + + if(nSteps) + { + // calc discrete length to change color all 1.5 discrete units (pixels) + const sal_uInt32 nDistSteps(basegfx::fround(fDelta / (fDiscreteUnit * 1.5))); + + nSteps = std::min(nSteps, nDistSteps); + } + + // roughly cut when too big or too small + nSteps = std::min(nSteps, sal_uInt32(255)); + nSteps = std::max(nSteps, sal_uInt32(1)); + + return nSteps; + } +} // end of anonymous namespace + + +namespace drawinglayer::primitive2d +{ + void SvgGradientHelper::createSingleGradientEntryFill(Primitive2DContainer& rContainer) const + { + const SvgGradientEntryVector& rEntries = getGradientEntries(); + const sal_uInt32 nCount(rEntries.size()); + + if(nCount) + { + const SvgGradientEntry& rSingleEntry = rEntries[nCount - 1]; + const double fOpacity(rSingleEntry.getOpacity()); + + if(fOpacity > 0.0) + { + Primitive2DReference xRef( + new PolyPolygonColorPrimitive2D( + getPolyPolygon(), + rSingleEntry.getColor())); + + if(fOpacity < 1.0) + { + const Primitive2DContainer aContent { xRef }; + + xRef = Primitive2DReference( + new UnifiedTransparencePrimitive2D( + aContent, + 1.0 - fOpacity)); + } + + rContainer.push_back(xRef); + } + } + else + { + OSL_ENSURE(false, "Single gradient entry construction without entry (!)"); + } + } + + void SvgGradientHelper::checkPreconditions() + { + mbPreconditionsChecked = true; + const SvgGradientEntryVector& rEntries = getGradientEntries(); + + if(rEntries.empty()) + { + // no fill at all, done + return; + } + + // sort maGradientEntries by offset, small to big + std::sort(maGradientEntries.begin(), maGradientEntries.end()); + + // gradient with at least two colors + bool bAllInvisible(true); + bool bInvalidEntries(false); + + for(const SvgGradientEntry& rCandidate : rEntries) + { + if(basegfx::fTools::equalZero(rCandidate.getOpacity())) + { + // invisible + mbFullyOpaque = false; + } + else if(basegfx::fTools::equal(rCandidate.getOpacity(), 1.0)) + { + // completely opaque + bAllInvisible = false; + } + else + { + // opacity + bAllInvisible = false; + mbFullyOpaque = false; + } + + if(!basegfx::fTools::betweenOrEqualEither(rCandidate.getOffset(), 0.0, 1.0)) + { + bInvalidEntries = true; + } + } + + if(bAllInvisible) + { + // all invisible, nothing to do + return; + } + + if(bInvalidEntries) + { + // invalid entries, do nothing + SAL_WARN("drawinglayer", "SvgGradientHelper got invalid SvgGradientEntries outside [0.0 .. 1.0]"); + return; + } + + const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange()); + + if(aPolyRange.isEmpty()) + { + // no range to fill, nothing to do + return; + } + + const double fPolyWidth(aPolyRange.getWidth()); + const double fPolyHeight(aPolyRange.getHeight()); + + if(basegfx::fTools::equalZero(fPolyWidth) || basegfx::fTools::equalZero(fPolyHeight)) + { + // no width/height to fill, nothing to do + return; + } + + mbCreatesContent = true; + + if(1 == rEntries.size()) + { + // fill with single existing color + setSingleEntry(); + } + } + + const SvgGradientEntry& SvgGradientHelper::FindEntryLessOrEqual( + sal_Int32& rInt, + const double fFrac) const + { + const bool bMirror(SpreadMethod::Reflect == getSpreadMethod() && 0 != rInt % 2); + const SvgGradientEntryVector& rCurrent(bMirror ? getMirroredGradientEntries() : getGradientEntries()); + + for(SvgGradientEntryVector::const_reverse_iterator aIter(rCurrent.rbegin()); aIter != rCurrent.rend(); aIter++) + { + if(basegfx::fTools::lessOrEqual(aIter->getOffset(), fFrac)) + { + return *aIter; + } + } + + // walk over gap to the left, be prepared for missing 0.0/1.0 entries + rInt--; + const bool bMirror2(SpreadMethod::Reflect == getSpreadMethod() && 0 != rInt % 2); + const SvgGradientEntryVector& rCurrent2(bMirror2 ? getMirroredGradientEntries() : getGradientEntries()); + return rCurrent2.back(); + } + + const SvgGradientEntry& SvgGradientHelper::FindEntryMore( + sal_Int32& rInt, + const double fFrac) const + { + const bool bMirror(SpreadMethod::Reflect == getSpreadMethod() && 0 != rInt % 2); + const SvgGradientEntryVector& rCurrent(bMirror ? getMirroredGradientEntries() : getGradientEntries()); + + for(SvgGradientEntryVector::const_iterator aIter(rCurrent.begin()); aIter != rCurrent.end(); aIter++) + { + if(basegfx::fTools::more(aIter->getOffset(), fFrac)) + { + return *aIter; + } + } + + // walk over gap to the right, be prepared for missing 0.0/1.0 entries + rInt++; + const bool bMirror2(SpreadMethod::Reflect == getSpreadMethod() && 0 != rInt % 2); + const SvgGradientEntryVector& rCurrent2(bMirror2 ? getMirroredGradientEntries() : getGradientEntries()); + return rCurrent2.front(); + } + + // tdf#124424 Adapted creation of color runs to do in a single effort. Previous + // version tried to do this from [0.0 .. 1.0] and to re-use transformed versions + // in the caller if SpreadMethod was on some repeat mode, but had problems when + // e.g. like in the bugdoc from the task a negative-only fStart/fEnd run was + // requested in which case it did nothing. Even when reusing the spread might + // not have been a full one from [0.0 .. 1.0]. + // This gets complicated due to mirrored runs, but also for gradient definitions + // with missing entries for 0.0 and 1.0 in which case these have to be guessed + // to be there with same parametrisation as their nearest existing entries. These + // *could* have been added at checkPreconditions() but would then create unnecessary + // spreads on zone overlaps. + void SvgGradientHelper::createRun( + Primitive2DContainer& rTargetColor, + Primitive2DContainer& rTargetOpacity, + double fStart, + double fEnd) const + { + double fInt(0.0); + double fFrac(0.0); + double fEnd2(0.0); + + if(SpreadMethod::Pad == getSpreadMethod()) + { + if(fStart < 0.0) + { + fFrac = std::modf(fStart, &fInt); + const SvgGradientEntry& rFront(getGradientEntries().front()); + const SvgGradientEntry aTemp(1.0 + fFrac, rFront.getColor(), rFront.getOpacity()); + createAtom(rTargetColor, rTargetOpacity, aTemp, rFront, static_cast<sal_Int32>(fInt - 1), 0); + fStart = rFront.getOffset(); + } + + if(fEnd > 1.0) + { + // change fEnd early, but create geometry later (after range below) + fEnd2 = fEnd; + fEnd = getGradientEntries().back().getOffset(); + } + } + + while(fStart < fEnd) + { + fFrac = std::modf(fStart, &fInt); + + if(fFrac < 0.0) + { + fInt -= 1; + fFrac = 1.0 + fFrac; + } + + sal_Int32 nIntLeft(static_cast<sal_Int32>(fInt)); + sal_Int32 nIntRight(nIntLeft); + + const SvgGradientEntry& rLeft(FindEntryLessOrEqual(nIntLeft, fFrac)); + const SvgGradientEntry& rRight(FindEntryMore(nIntRight, fFrac)); + createAtom(rTargetColor, rTargetOpacity, rLeft, rRight, nIntLeft, nIntRight); + + const double fNextfStart(static_cast<double>(nIntRight) + rRight.getOffset()); + + if(basegfx::fTools::more(fNextfStart, fStart)) + { + fStart = fNextfStart; + } + else + { + SAL_WARN("drawinglayer", "SvgGradientHelper spread error"); + fStart += 1.0; + } + } + + if(fEnd2 > 1.0) + { + // create end run for SpreadMethod::Pad late to keep correct creation order + fFrac = std::modf(fEnd2, &fInt); + const SvgGradientEntry& rBack(getGradientEntries().back()); + const SvgGradientEntry aTemp(fFrac, rBack.getColor(), rBack.getOpacity()); + createAtom(rTargetColor, rTargetOpacity, rBack, aTemp, 0, static_cast<sal_Int32>(fInt)); + } + } + + void SvgGradientHelper::createResult( + Primitive2DContainer& rContainer, + const Primitive2DContainer& rTargetColor, + const Primitive2DContainer& rTargetOpacity, + const basegfx::B2DHomMatrix& rUnitGradientToObject, + bool bInvert) const + { + const Primitive2DContainer aTargetColorEntries(rTargetColor.maybeInvert(bInvert)); + const Primitive2DContainer aTargetOpacityEntries(rTargetOpacity.maybeInvert(bInvert)); + + if(aTargetColorEntries.empty()) + return; + + Primitive2DReference xRefContent; + + if(!aTargetOpacityEntries.empty()) + { + const Primitive2DReference xRefOpacity = new TransparencePrimitive2D( + aTargetColorEntries, + aTargetOpacityEntries); + + xRefContent = new TransformPrimitive2D( + rUnitGradientToObject, + Primitive2DContainer { xRefOpacity }); + } + else + { + xRefContent = new TransformPrimitive2D( + rUnitGradientToObject, + aTargetColorEntries); + } + + rContainer.push_back(new MaskPrimitive2D( + getPolyPolygon(), + Primitive2DContainer { xRefContent })); + } + + SvgGradientHelper::SvgGradientHelper( + const basegfx::B2DHomMatrix& rGradientTransform, + const basegfx::B2DPolyPolygon& rPolyPolygon, + const SvgGradientEntryVector& rGradientEntries, + const basegfx::B2DPoint& rStart, + bool bUseUnitCoordinates, + SpreadMethod aSpreadMethod) + : maGradientTransform(rGradientTransform), + maPolyPolygon(rPolyPolygon), + maGradientEntries(rGradientEntries), + maMirroredGradientEntries(), + maStart(rStart), + maSpreadMethod(aSpreadMethod), + mbPreconditionsChecked(false), + mbCreatesContent(false), + mbSingleEntry(false), + mbFullyOpaque(true), + mbUseUnitCoordinates(bUseUnitCoordinates) + { + } + + SvgGradientHelper::~SvgGradientHelper() + { + } + + const SvgGradientEntryVector& SvgGradientHelper::getMirroredGradientEntries() const + { + if(maMirroredGradientEntries.empty() && !getGradientEntries().empty()) + { + const_cast< SvgGradientHelper* >(this)->createMirroredGradientEntries(); + } + + return maMirroredGradientEntries; + } + + void SvgGradientHelper::createMirroredGradientEntries() + { + if(!(maMirroredGradientEntries.empty() && !getGradientEntries().empty())) + return; + + const sal_uInt32 nCount(getGradientEntries().size()); + maMirroredGradientEntries.clear(); + maMirroredGradientEntries.reserve(nCount); + + for(sal_uInt32 a(0); a < nCount; a++) + { + const SvgGradientEntry& rCandidate = getGradientEntries()[nCount - 1 - a]; + + maMirroredGradientEntries.emplace_back( + 1.0 - rCandidate.getOffset(), + rCandidate.getColor(), + rCandidate.getOpacity()); + } + } + + bool SvgGradientHelper::operator==(const SvgGradientHelper& rSvgGradientHelper) const + { + const SvgGradientHelper& rCompare = rSvgGradientHelper; + + return (getGradientTransform() == rCompare.getGradientTransform() + && getPolyPolygon() == rCompare.getPolyPolygon() + && getGradientEntries() == rCompare.getGradientEntries() + && getStart() == rCompare.getStart() + && getUseUnitCoordinates() == rCompare.getUseUnitCoordinates() + && getSpreadMethod() == rCompare.getSpreadMethod()); + } + +} // end of namespace drawinglayer::primitive2d + + +namespace drawinglayer::primitive2d +{ + void SvgLinearGradientPrimitive2D::checkPreconditions() + { + // call parent + SvgGradientHelper::checkPreconditions(); + + if(getCreatesContent()) + { + // Check Vector + const basegfx::B2DVector aVector(getEnd() - getStart()); + + if(basegfx::fTools::equalZero(aVector.getX()) && basegfx::fTools::equalZero(aVector.getY())) + { + // fill with single color using last stop color + setSingleEntry(); + } + } + } + + void SvgLinearGradientPrimitive2D::createAtom( + Primitive2DContainer& rTargetColor, + Primitive2DContainer& rTargetOpacity, + const SvgGradientEntry& rFrom, + const SvgGradientEntry& rTo, + sal_Int32 nOffsetFrom, + sal_Int32 nOffsetTo) const + { + // create gradient atom [rFrom.getOffset() .. rTo.getOffset()] with (rFrom.getOffset() > rTo.getOffset()) + if(rFrom.getOffset() == rTo.getOffset()) + { + OSL_ENSURE(false, "SvgGradient Atom creation with no step width (!)"); + } + else + { + rTargetColor.push_back( + new SvgLinearAtomPrimitive2D( + rFrom.getColor(), rFrom.getOffset() + nOffsetFrom, + rTo.getColor(), rTo.getOffset() + nOffsetTo)); + + if(!getFullyOpaque()) + { + const double fTransFrom(1.0 - rFrom.getOpacity()); + const double fTransTo(1.0 - rTo.getOpacity()); + const basegfx::BColor aColorFrom(fTransFrom, fTransFrom, fTransFrom); + const basegfx::BColor aColorTo(fTransTo, fTransTo, fTransTo); + + rTargetOpacity.push_back( + new SvgLinearAtomPrimitive2D( + aColorFrom, rFrom.getOffset() + nOffsetFrom, + aColorTo, rTo.getOffset() + nOffsetTo)); + } + } + } + + void SvgLinearGradientPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(!getPreconditionsChecked()) + { + const_cast< SvgLinearGradientPrimitive2D* >(this)->checkPreconditions(); + } + + if(getSingleEntry()) + { + // fill with last existing color + createSingleGradientEntryFill(rContainer); + } + else if(getCreatesContent()) + { + // at least two color stops in range [0.0 .. 1.0], sorted, non-null vector, not completely + // invisible, width and height to fill are not empty + const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange()); + const double fPolyWidth(aPolyRange.getWidth()); + const double fPolyHeight(aPolyRange.getHeight()); + + // create ObjectTransform based on polygon range + const basegfx::B2DHomMatrix aObjectTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + fPolyWidth, fPolyHeight, + aPolyRange.getMinX(), aPolyRange.getMinY())); + basegfx::B2DHomMatrix aUnitGradientToObject; + + if(getUseUnitCoordinates()) + { + // interpret in unit coordinate system -> object aspect ratio will scale result + // create unit transform from unit vector [0.0 .. 1.0] along the X-Axis to given + // gradient vector defined by Start,End + const basegfx::B2DVector aVector(getEnd() - getStart()); + const double fVectorLength(aVector.getLength()); + + aUnitGradientToObject.scale(fVectorLength, 1.0); + aUnitGradientToObject.rotate(atan2(aVector.getY(), aVector.getX())); + aUnitGradientToObject.translate(getStart().getX(), getStart().getY()); + + aUnitGradientToObject *= getGradientTransform(); + + // create full transform from unit gradient coordinates to object coordinates + // including the SvgGradient transformation + aUnitGradientToObject *= aObjectTransform; + } + else + { + // interpret in object coordinate system -> object aspect ratio will not scale result + const basegfx::B2DPoint aStart(aObjectTransform * getStart()); + const basegfx::B2DPoint aEnd(aObjectTransform * getEnd()); + const basegfx::B2DVector aVector(aEnd - aStart); + + aUnitGradientToObject.scale(aVector.getLength(), 1.0); + aUnitGradientToObject.rotate(atan2(aVector.getY(), aVector.getX())); + aUnitGradientToObject.translate(aStart.getX(), aStart.getY()); + + aUnitGradientToObject *= getGradientTransform(); + } + + // create inverse from it + basegfx::B2DHomMatrix aObjectToUnitGradient(aUnitGradientToObject); + aObjectToUnitGradient.invert(); + + // back-transform polygon to unit gradient coordinates and get + // UnitRage. This is the range the gradient has to cover + basegfx::B2DPolyPolygon aUnitPoly(getPolyPolygon()); + aUnitPoly.transform(aObjectToUnitGradient); + const basegfx::B2DRange aUnitRange(aUnitPoly.getB2DRange()); + + // prepare result vectors + Primitive2DContainer aTargetColor; + Primitive2DContainer aTargetOpacity; + + if(basegfx::fTools::more(aUnitRange.getWidth(), 0.0)) + { + // add a pre-multiply to aUnitGradientToObject to allow + // multiplication of the polygon(xl, 0.0, xr, 1.0) + const basegfx::B2DHomMatrix aPreMultiply( + basegfx::utils::createScaleTranslateB2DHomMatrix( + 1.0, aUnitRange.getHeight(), 0.0, aUnitRange.getMinY())); + aUnitGradientToObject = aUnitGradientToObject * aPreMultiply; + + // create full color run, including all SpreadMethod variants + createRun( + aTargetColor, + aTargetOpacity, + aUnitRange.getMinX(), + aUnitRange.getMaxX()); + } + + createResult(rContainer, aTargetColor, aTargetOpacity, aUnitGradientToObject); + } + } + + SvgLinearGradientPrimitive2D::SvgLinearGradientPrimitive2D( + const basegfx::B2DHomMatrix& rGradientTransform, + const basegfx::B2DPolyPolygon& rPolyPolygon, + const SvgGradientEntryVector& rGradientEntries, + const basegfx::B2DPoint& rStart, + const basegfx::B2DPoint& rEnd, + bool bUseUnitCoordinates, + SpreadMethod aSpreadMethod) + : BufferedDecompositionPrimitive2D(), + SvgGradientHelper(rGradientTransform, rPolyPolygon, rGradientEntries, rStart, bUseUnitCoordinates, aSpreadMethod), + maEnd(rEnd) + { + } + + SvgLinearGradientPrimitive2D::~SvgLinearGradientPrimitive2D() + { + } + + bool SvgLinearGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + const SvgGradientHelper* pSvgGradientHelper = dynamic_cast< const SvgGradientHelper* >(&rPrimitive); + + if(pSvgGradientHelper && SvgGradientHelper::operator==(*pSvgGradientHelper)) + { + const SvgLinearGradientPrimitive2D& rCompare = static_cast< const SvgLinearGradientPrimitive2D& >(rPrimitive); + + return (getEnd() == rCompare.getEnd()); + } + + return false; + } + + basegfx::B2DRange SvgLinearGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // return ObjectRange + return getPolyPolygon().getB2DRange(); + } + + // provide unique ID + ImplPrimitive2DIDBlock(SvgLinearGradientPrimitive2D, PRIMITIVE2D_ID_SVGLINEARGRADIENTPRIMITIVE2D) + +} // end of namespace drawinglayer::primitive2d + + +namespace drawinglayer::primitive2d +{ + void SvgRadialGradientPrimitive2D::checkPreconditions() + { + // call parent + SvgGradientHelper::checkPreconditions(); + + if(getCreatesContent()) + { + // Check Radius + if(basegfx::fTools::equalZero(getRadius())) + { + // fill with single color using last stop color + setSingleEntry(); + } + } + } + + void SvgRadialGradientPrimitive2D::createAtom( + Primitive2DContainer& rTargetColor, + Primitive2DContainer& rTargetOpacity, + const SvgGradientEntry& rFrom, + const SvgGradientEntry& rTo, + sal_Int32 nOffsetFrom, + sal_Int32 nOffsetTo) const + { + // create gradient atom [rFrom.getOffset() .. rTo.getOffset()] with (rFrom.getOffset() > rTo.getOffset()) + if(rFrom.getOffset() == rTo.getOffset()) + { + OSL_ENSURE(false, "SvgGradient Atom creation with no step width (!)"); + } + else + { + const double fScaleFrom(rFrom.getOffset() + nOffsetFrom); + const double fScaleTo(rTo.getOffset() + nOffsetTo); + + if(isFocalSet()) + { + const basegfx::B2DVector aTranslateFrom(maFocalVector * (maFocalLength - fScaleFrom)); + const basegfx::B2DVector aTranslateTo(maFocalVector * (maFocalLength - fScaleTo)); + + rTargetColor.push_back( + new SvgRadialAtomPrimitive2D( + rFrom.getColor(), fScaleFrom, aTranslateFrom, + rTo.getColor(), fScaleTo, aTranslateTo)); + } + else + { + rTargetColor.push_back( + new SvgRadialAtomPrimitive2D( + rFrom.getColor(), fScaleFrom, + rTo.getColor(), fScaleTo)); + } + + if(!getFullyOpaque()) + { + const double fTransFrom(1.0 - rFrom.getOpacity()); + const double fTransTo(1.0 - rTo.getOpacity()); + const basegfx::BColor aColorFrom(fTransFrom, fTransFrom, fTransFrom); + const basegfx::BColor aColorTo(fTransTo, fTransTo, fTransTo); + + if(isFocalSet()) + { + const basegfx::B2DVector aTranslateFrom(maFocalVector * (maFocalLength - fScaleFrom)); + const basegfx::B2DVector aTranslateTo(maFocalVector * (maFocalLength - fScaleTo)); + + rTargetOpacity.push_back( + new SvgRadialAtomPrimitive2D( + aColorFrom, fScaleFrom, aTranslateFrom, + aColorTo, fScaleTo, aTranslateTo)); + } + else + { + rTargetOpacity.push_back( + new SvgRadialAtomPrimitive2D( + aColorFrom, fScaleFrom, + aColorTo, fScaleTo)); + } + } + } + } + + void SvgRadialGradientPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(!getPreconditionsChecked()) + { + const_cast< SvgRadialGradientPrimitive2D* >(this)->checkPreconditions(); + } + + if(getSingleEntry()) + { + // fill with last existing color + createSingleGradientEntryFill(rContainer); + } + else if(getCreatesContent()) + { + // at least two color stops in range [0.0 .. 1.0], sorted, non-null vector, not completely + // invisible, width and height to fill are not empty + const basegfx::B2DRange aPolyRange(getPolyPolygon().getB2DRange()); + const double fPolyWidth(aPolyRange.getWidth()); + const double fPolyHeight(aPolyRange.getHeight()); + + // create ObjectTransform based on polygon range + const basegfx::B2DHomMatrix aObjectTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + fPolyWidth, fPolyHeight, + aPolyRange.getMinX(), aPolyRange.getMinY())); + basegfx::B2DHomMatrix aUnitGradientToObject; + + if(getUseUnitCoordinates()) + { + // interpret in unit coordinate system -> object aspect ratio will scale result + // create unit transform from unit vector to given linear gradient vector + aUnitGradientToObject.scale(getRadius(), getRadius()); + aUnitGradientToObject.translate(getStart().getX(), getStart().getY()); + + if(!getGradientTransform().isIdentity()) + { + aUnitGradientToObject = getGradientTransform() * aUnitGradientToObject; + } + + // create full transform from unit gradient coordinates to object coordinates + // including the SvgGradient transformation + aUnitGradientToObject = aObjectTransform * aUnitGradientToObject; + } + else + { + // interpret in object coordinate system -> object aspect ratio will not scale result + // use X-Axis with radius, it was already made relative to object width when coming from + // SVG import + const double fRadius((aObjectTransform * basegfx::B2DVector(getRadius(), 0.0)).getLength()); + const basegfx::B2DPoint aStart(aObjectTransform * getStart()); + + aUnitGradientToObject.scale(fRadius, fRadius); + aUnitGradientToObject.translate(aStart.getX(), aStart.getY()); + + aUnitGradientToObject *= getGradientTransform(); + } + + // create inverse from it + basegfx::B2DHomMatrix aObjectToUnitGradient(aUnitGradientToObject); + aObjectToUnitGradient.invert(); + + // back-transform polygon to unit gradient coordinates and get + // UnitRage. This is the range the gradient has to cover + basegfx::B2DPolyPolygon aUnitPoly(getPolyPolygon()); + aUnitPoly.transform(aObjectToUnitGradient); + const basegfx::B2DRange aUnitRange(aUnitPoly.getB2DRange()); + + // create range which the gradient has to cover to cover the whole given geometry. + // For circle, go from 0.0 to max radius in all directions (the corners) + double fMax(basegfx::B2DVector(aUnitRange.getMinimum()).getLength()); + fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMaximum()).getLength()); + fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMinX(), aUnitRange.getMaxY()).getLength()); + fMax = std::max(fMax, basegfx::B2DVector(aUnitRange.getMaxX(), aUnitRange.getMinY()).getLength()); + + // prepare result vectors + Primitive2DContainer aTargetColor; + Primitive2DContainer aTargetOpacity; + + if(0.0 < fMax) + { + // prepare maFocalVector + if(isFocalSet()) + { + const_cast< SvgRadialGradientPrimitive2D* >(this)->maFocalLength = fMax; + } + + // create full color run, including all SpreadMethod variants + createRun( + aTargetColor, + aTargetOpacity, + 0.0, + fMax); + } + + createResult(rContainer, aTargetColor, aTargetOpacity, aUnitGradientToObject, true); + } + } + + SvgRadialGradientPrimitive2D::SvgRadialGradientPrimitive2D( + const basegfx::B2DHomMatrix& rGradientTransform, + const basegfx::B2DPolyPolygon& rPolyPolygon, + const SvgGradientEntryVector& rGradientEntries, + const basegfx::B2DPoint& rStart, + double fRadius, + bool bUseUnitCoordinates, + SpreadMethod aSpreadMethod, + const basegfx::B2DPoint* pFocal) + : BufferedDecompositionPrimitive2D(), + SvgGradientHelper(rGradientTransform, rPolyPolygon, rGradientEntries, rStart, bUseUnitCoordinates, aSpreadMethod), + mfRadius(fRadius), + maFocal(rStart), + maFocalVector(0.0, 0.0), + maFocalLength(0.0), + mbFocalSet(false) + { + if(pFocal && !pFocal->equal(getStart())) + { + maFocal = *pFocal; + maFocalVector = maFocal - getStart(); + mbFocalSet = true; + } + } + + SvgRadialGradientPrimitive2D::~SvgRadialGradientPrimitive2D() + { + } + + bool SvgRadialGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + const SvgGradientHelper* pSvgGradientHelper = dynamic_cast< const SvgGradientHelper* >(&rPrimitive); + + if(pSvgGradientHelper && SvgGradientHelper::operator==(*pSvgGradientHelper)) + { + const SvgRadialGradientPrimitive2D& rCompare = static_cast< const SvgRadialGradientPrimitive2D& >(rPrimitive); + + if(getRadius() == rCompare.getRadius()) + { + if(isFocalSet() == rCompare.isFocalSet()) + { + if(isFocalSet()) + { + return getFocal() == rCompare.getFocal(); + } + else + { + return true; + } + } + } + } + + return false; + } + + basegfx::B2DRange SvgRadialGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // return ObjectRange + return getPolyPolygon().getB2DRange(); + } + + // provide unique ID + ImplPrimitive2DIDBlock(SvgRadialGradientPrimitive2D, PRIMITIVE2D_ID_SVGRADIALGRADIENTPRIMITIVE2D) + +} // end of namespace drawinglayer::primitive2d + + +// SvgLinearAtomPrimitive2D class + +namespace drawinglayer::primitive2d +{ + void SvgLinearAtomPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + const double fDelta(getOffsetB() - getOffsetA()); + + if(basegfx::fTools::equalZero(fDelta)) + return; + + // use one discrete unit for overlap (one pixel) + const double fDiscreteUnit(getDiscreteUnit()); + + // use color distance and discrete lengths to calculate step count + const sal_uInt32 nSteps(calculateStepsForSvgGradient(getColorA(), getColorB(), fDelta, fDiscreteUnit)); + + // HACK: Splitting a gradient into adjacent polygons with gradually changing color is silly. + // If antialiasing is used to draw them, the AA-ed adjacent edges won't line up perfectly + // because of the AA (see SkiaSalGraphicsImpl::mergePolyPolygonToPrevious()). + // Make the polygons a bit wider, so they the partial overlap "fixes" this. + const double fixup = SkiaHelper::isVCLSkiaEnabled() ? fDiscreteUnit / 2 : 0; + + // tdf#117949 Use a small amount of discrete overlap at the edges. Usually this + // should be exactly 0.0 and 1.0, but there were cases when this gets clipped + // against the mask polygon which got numerically problematic. + // This change is unnecessary in that respect, but avoids that numerical havoc + // by at the same time doing no real harm AFAIK + // TTTT: Remove again when clipping is fixed (!) + + // prepare polygon in needed width at start position (with discrete overlap) + const basegfx::B2DPolygon aPolygon( + basegfx::utils::createPolygonFromRect( + basegfx::B2DRange( + getOffsetA() - fDiscreteUnit, + -0.0001, // TTTT -> should be 0.0, see comment above + getOffsetA() + (fDelta / nSteps) + fDiscreteUnit + fixup, + 1.0001))); // TTTT -> should be 1.0, see comment above + + // prepare loop (inside to outside, [0.0 .. 1.0[) + double fUnitScale(0.0); + const double fUnitStep(1.0 / nSteps); + + for(sal_uInt32 a(0); a < nSteps; a++, fUnitScale += fUnitStep) + { + basegfx::B2DPolygon aNew(aPolygon); + + aNew.transform(basegfx::utils::createTranslateB2DHomMatrix(fDelta * fUnitScale, 0.0)); + rContainer.push_back(new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aNew), + basegfx::interpolate(getColorA(), getColorB(), fUnitScale))); + } + } + + SvgLinearAtomPrimitive2D::SvgLinearAtomPrimitive2D( + const basegfx::BColor& aColorA, double fOffsetA, + const basegfx::BColor& aColorB, double fOffsetB) + : DiscreteMetricDependentPrimitive2D(), + maColorA(aColorA), + maColorB(aColorB), + mfOffsetA(fOffsetA), + mfOffsetB(fOffsetB) + { + if(mfOffsetA > mfOffsetB) + { + OSL_ENSURE(false, "Wrong offset order (!)"); + std::swap(mfOffsetA, mfOffsetB); + } + } + + bool SvgLinearAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive)) + { + const SvgLinearAtomPrimitive2D& rCompare = static_cast< const SvgLinearAtomPrimitive2D& >(rPrimitive); + + return (getColorA() == rCompare.getColorA() + && getColorB() == rCompare.getColorB() + && getOffsetA() == rCompare.getOffsetA() + && getOffsetB() == rCompare.getOffsetB()); + } + + return false; + } + + // provide unique ID + ImplPrimitive2DIDBlock(SvgLinearAtomPrimitive2D, PRIMITIVE2D_ID_SVGLINEARATOMPRIMITIVE2D) + +} // end of namespace drawinglayer::primitive2d + + +// SvgRadialAtomPrimitive2D class + +namespace drawinglayer::primitive2d +{ + void SvgRadialAtomPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + const double fDeltaScale(getScaleB() - getScaleA()); + + if(basegfx::fTools::equalZero(fDeltaScale)) + return; + + // use one discrete unit for overlap (one pixel) + const double fDiscreteUnit(getDiscreteUnit()); + + // use color distance and discrete lengths to calculate step count + const sal_uInt32 nSteps(calculateStepsForSvgGradient(getColorA(), getColorB(), fDeltaScale, fDiscreteUnit)); + + // 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(getScaleB() - (fDeltaScale * fUnitScale)); + + if(isTranslateSet()) + { + const basegfx::B2DVector aTranslate( + basegfx::interpolate( + getTranslateB(), + 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(aTransform); + rContainer.push_back(new PolyPolygonColorPrimitive2D( + basegfx::B2DPolyPolygon(aNew), + basegfx::interpolate(getColorB(), getColorA(), fUnitScale))); + } + } + + SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D( + const basegfx::BColor& aColorA, double fScaleA, const basegfx::B2DVector& rTranslateA, + const basegfx::BColor& aColorB, double fScaleB, const basegfx::B2DVector& rTranslateB) + : DiscreteMetricDependentPrimitive2D(), + maColorA(aColorA), + maColorB(aColorB), + mfScaleA(fScaleA), + mfScaleB(fScaleB) + { + // check and evtl. set translations + if(!rTranslateA.equal(rTranslateB)) + { + mpTranslate.reset( new VectorPair(rTranslateA, rTranslateB) ); + } + + // scale A and B have to be positive + mfScaleA = std::max(mfScaleA, 0.0); + mfScaleB = std::max(mfScaleB, 0.0); + + // scale B has to be bigger than scale A; swap if different + if(mfScaleA > mfScaleB) + { + OSL_ENSURE(false, "Wrong offset order (!)"); + std::swap(mfScaleA, mfScaleB); + + if(mpTranslate) + { + std::swap(mpTranslate->maTranslateA, mpTranslate->maTranslateB); + } + } + } + + SvgRadialAtomPrimitive2D::SvgRadialAtomPrimitive2D( + const basegfx::BColor& aColorA, double fScaleA, + const basegfx::BColor& aColorB, double fScaleB) + : DiscreteMetricDependentPrimitive2D(), + maColorA(aColorA), + maColorB(aColorB), + mfScaleA(fScaleA), + mfScaleB(fScaleB) + { + // scale A and B have to be positive + mfScaleA = std::max(mfScaleA, 0.0); + mfScaleB = std::max(mfScaleB, 0.0); + + // scale B has to be bigger than scale A; swap if different + if(mfScaleA > mfScaleB) + { + OSL_ENSURE(false, "Wrong offset order (!)"); + std::swap(mfScaleA, mfScaleB); + } + } + + SvgRadialAtomPrimitive2D::~SvgRadialAtomPrimitive2D() + { + } + + bool SvgRadialAtomPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(DiscreteMetricDependentPrimitive2D::operator==(rPrimitive)) + { + const SvgRadialAtomPrimitive2D& rCompare = static_cast< const SvgRadialAtomPrimitive2D& >(rPrimitive); + + if(getColorA() == rCompare.getColorA() + && getColorB() == rCompare.getColorB() + && getScaleA() == rCompare.getScaleA() + && getScaleB() == rCompare.getScaleB()) + { + if(isTranslateSet() && rCompare.isTranslateSet()) + { + return (getTranslateA() == rCompare.getTranslateA() + && getTranslateB() == rCompare.getTranslateB()); + } + else if(!isTranslateSet() && !rCompare.isTranslateSet()) + { + return true; + } + } + } + + return false; + } + + // provide unique ID + ImplPrimitive2DIDBlock(SvgRadialAtomPrimitive2D, PRIMITIVE2D_ID_SVGRADIALATOMPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/textbreakuphelper.cxx b/drawinglayer/source/primitive2d/textbreakuphelper.cxx new file mode 100644 index 000000000..1139f19e8 --- /dev/null +++ b/drawinglayer/source/primitive2d/textbreakuphelper.cxx @@ -0,0 +1,279 @@ +/* -*- 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/textbreakuphelper.hxx> +#include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx> +#include <com/sun/star/i18n/BreakIterator.hpp> +#include <comphelper/processfactory.hxx> +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/i18n/CharType.hpp> + + +namespace drawinglayer::primitive2d +{ + TextBreakupHelper::TextBreakupHelper(const TextSimplePortionPrimitive2D& rSource) + : mrSource(rSource), + mxResult(), + maTextLayouter(), + maDecTrans(), + mbNoDXArray(false) + { + maDecTrans = mrSource.getTextTransform(); + mbNoDXArray = mrSource.getDXArray().empty(); + + if(mbNoDXArray) + { + // init TextLayouter when no dxarray + maTextLayouter.setFontAttribute( + mrSource.getFontAttribute(), + maDecTrans.getScale().getX(), + maDecTrans.getScale().getY(), + mrSource.getLocale()); + } + } + + TextBreakupHelper::~TextBreakupHelper() + { + } + + void TextBreakupHelper::breakupPortion(Primitive2DContainer& rTempResult, sal_Int32 nIndex, sal_Int32 nLength, bool bWordLineMode) + { + if(!(nLength && !(nIndex == mrSource.getTextPosition() && nLength == mrSource.getTextLength()))) + return; + + // prepare values for new portion + basegfx::B2DHomMatrix aNewTransform; + std::vector< double > aNewDXArray; + const bool bNewStartIsNotOldStart(nIndex > mrSource.getTextPosition()); + + if(!mbNoDXArray) + { + // prepare new DXArray for the single word + aNewDXArray = std::vector< double >( + mrSource.getDXArray().begin() + (nIndex - mrSource.getTextPosition()), + mrSource.getDXArray().begin() + ((nIndex + nLength) - mrSource.getTextPosition())); + } + + if(bNewStartIsNotOldStart) + { + // needs to be moved to a new start position + double fOffset(0.0); + + if(mbNoDXArray) + { + // evaluate using TextLayouter + fOffset = maTextLayouter.getTextWidth(mrSource.getText(), mrSource.getTextPosition(), nIndex); + } + else + { + // get from DXArray + const sal_Int32 nIndex2(nIndex - mrSource.getTextPosition()); + fOffset = mrSource.getDXArray()[nIndex2 - 1]; + } + + // need offset without FontScale for building the new transformation. The + // new transformation will be multiplied with the current text transformation + // so FontScale would be double + double fOffsetNoScale(fOffset); + const double fFontScaleX(maDecTrans.getScale().getX()); + + if(!basegfx::fTools::equal(fFontScaleX, 1.0) + && !basegfx::fTools::equalZero(fFontScaleX)) + { + fOffsetNoScale /= fFontScaleX; + } + + // apply needed offset to transformation + aNewTransform.translate(fOffsetNoScale, 0.0); + + if(!mbNoDXArray) + { + // DXArray values need to be corrected with the offset, too. Here, + // take the scaled offset since the DXArray is scaled + const sal_uInt32 nArraySize(aNewDXArray.size()); + + for(sal_uInt32 a(0); a < nArraySize; a++) + { + aNewDXArray[a] -= fOffset; + } + } + } + + // add text transformation to new transformation + // coverity[swapped_arguments : FALSE] - this is in the correct order + aNewTransform *= maDecTrans.getB2DHomMatrix(); + + // callback to allow evtl. changes + const bool bCreate(allowChange(rTempResult.size(), aNewTransform, nIndex, nLength)); + + if(!bCreate) + return; + + // check if we have a decorated primitive as source + const TextDecoratedPortionPrimitive2D* pTextDecoratedPortionPrimitive2D = + dynamic_cast< const TextDecoratedPortionPrimitive2D* >(&mrSource); + + if(pTextDecoratedPortionPrimitive2D) + { + // create a TextDecoratedPortionPrimitive2D + rTempResult.push_back( + new TextDecoratedPortionPrimitive2D( + aNewTransform, + mrSource.getText(), + nIndex, + nLength, + aNewDXArray, + mrSource.getFontAttribute(), + mrSource.getLocale(), + mrSource.getFontColor(), + mrSource.getTextFillColor(), + + pTextDecoratedPortionPrimitive2D->getOverlineColor(), + pTextDecoratedPortionPrimitive2D->getTextlineColor(), + pTextDecoratedPortionPrimitive2D->getFontOverline(), + pTextDecoratedPortionPrimitive2D->getFontUnderline(), + pTextDecoratedPortionPrimitive2D->getUnderlineAbove(), + pTextDecoratedPortionPrimitive2D->getTextStrikeout(), + + // reset WordLineMode when BreakupUnit::Word is executed; else copy original + !bWordLineMode && pTextDecoratedPortionPrimitive2D->getWordLineMode(), + + pTextDecoratedPortionPrimitive2D->getTextEmphasisMark(), + pTextDecoratedPortionPrimitive2D->getEmphasisMarkAbove(), + pTextDecoratedPortionPrimitive2D->getEmphasisMarkBelow(), + pTextDecoratedPortionPrimitive2D->getTextRelief(), + pTextDecoratedPortionPrimitive2D->getShadow())); + } + else + { + // create a SimpleTextPrimitive + rTempResult.push_back( + new TextSimplePortionPrimitive2D( + aNewTransform, + mrSource.getText(), + nIndex, + nLength, + aNewDXArray, + mrSource.getFontAttribute(), + mrSource.getLocale(), + mrSource.getFontColor())); + } + } + + bool TextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& /*rNewTransform*/, sal_uInt32 /*nIndex*/, sal_uInt32 /*nLength*/) + { + return true; + } + + void TextBreakupHelper::breakup(BreakupUnit aBreakupUnit) + { + if(!mrSource.getTextLength()) + return; + + Primitive2DContainer aTempResult; + static css::uno::Reference< css::i18n::XBreakIterator > xBreakIterator; + + if(!xBreakIterator.is()) + { + css::uno::Reference< css::uno::XComponentContext > xContext( ::comphelper::getProcessComponentContext() ); + xBreakIterator = css::i18n::BreakIterator::create(xContext); + } + + const OUString& rTxt = mrSource.getText(); + const sal_Int32 nTextLength(mrSource.getTextLength()); + const css::lang::Locale& rLocale = mrSource.getLocale(); + const sal_Int32 nTextPosition(mrSource.getTextPosition()); + sal_Int32 nCurrent(nTextPosition); + + switch(aBreakupUnit) + { + case BreakupUnit::Character: + { + sal_Int32 nDone; + sal_Int32 nNextCellBreak(xBreakIterator->nextCharacters(rTxt, nTextPosition, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 0, nDone)); + sal_Int32 a(nTextPosition); + + for(; a < nTextPosition + nTextLength; a++) + { + if(a == nNextCellBreak) + { + breakupPortion(aTempResult, nCurrent, a - nCurrent, false); + nCurrent = a; + nNextCellBreak = xBreakIterator->nextCharacters(rTxt, a, rLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone); + } + } + + breakupPortion(aTempResult, nCurrent, a - nCurrent, false); + break; + } + case BreakupUnit::Word: + { + css::i18n::Boundary nNextWordBoundary(xBreakIterator->getWordBoundary(rTxt, nTextPosition, rLocale, css::i18n::WordType::ANY_WORD, true)); + sal_Int32 a(nTextPosition); + + for(; a < nTextPosition + nTextLength; a++) + { + if(a == nNextWordBoundary.endPos) + { + if(a > nCurrent) + { + breakupPortion(aTempResult, nCurrent, a - nCurrent, true); + } + + nCurrent = a; + + // skip spaces (maybe enhanced with a bool later if needed) + { + const sal_Int32 nEndOfSpaces(xBreakIterator->endOfCharBlock(rTxt, a, rLocale, css::i18n::CharType::SPACE_SEPARATOR)); + + if(nEndOfSpaces > a) + { + nCurrent = nEndOfSpaces; + } + } + + nNextWordBoundary = xBreakIterator->getWordBoundary(rTxt, a + 1, rLocale, css::i18n::WordType::ANY_WORD, true); + } + } + + if(a > nCurrent) + { + breakupPortion(aTempResult, nCurrent, a - nCurrent, true); + } + break; + } + } + + mxResult = aTempResult; + } + + const Primitive2DContainer& TextBreakupHelper::getResult(BreakupUnit aBreakupUnit) const + { + if(mxResult.empty()) + { + const_cast< TextBreakupHelper* >(this)->breakup(aBreakupUnit); + } + + return mxResult; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx b/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx new file mode 100644 index 000000000..28e9eccda --- /dev/null +++ b/drawinglayer/source/primitive2d/textdecoratedprimitive2d.cxx @@ -0,0 +1,398 @@ +/* -*- 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/textdecoratedprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <primitive2d/texteffectprimitive2d.hxx> +#include <drawinglayer/primitive2d/shadowprimitive2d.hxx> +#include <primitive2d/textlineprimitive2d.hxx> +#include <primitive2d/textstrikeoutprimitive2d.hxx> +#include <drawinglayer/primitive2d/textbreakuphelper.hxx> + + +namespace drawinglayer::primitive2d +{ + void TextDecoratedPortionPrimitive2D::impCreateGeometryContent( + std::vector< Primitive2DReference >& rTarget, + basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose const & rDecTrans, + const OUString& rText, + sal_Int32 nTextPosition, + sal_Int32 nTextLength, + const std::vector< double >& rDXArray, + const attribute::FontAttribute& rFontAttribute) const + { + // create the SimpleTextPrimitive needed in any case + rTarget.push_back(Primitive2DReference( + new TextSimplePortionPrimitive2D( + rDecTrans.getB2DHomMatrix(), + rText, + nTextPosition, + nTextLength, + rDXArray, + rFontAttribute, + getLocale(), + getFontColor()))); + + // see if something else needs to be done + const bool bOverlineUsed(TEXT_LINE_NONE != getFontOverline()); + const bool bUnderlineUsed(TEXT_LINE_NONE != getFontUnderline()); + const bool bStrikeoutUsed(TEXT_STRIKEOUT_NONE != getTextStrikeout()); + + if(!(bUnderlineUsed || bStrikeoutUsed || bOverlineUsed)) + return; + + // common preparations + TextLayouterDevice aTextLayouter; + + // TextLayouterDevice is needed to get metrics for text decorations like + // underline/strikeout/emphasis marks from it. For setup, the font size is needed + aTextLayouter.setFontAttribute( + getFontAttribute(), + rDecTrans.getScale().getX(), + rDecTrans.getScale().getY(), + getLocale()); + + // get text width + double fTextWidth(0.0); + + if(rDXArray.empty()) + { + fTextWidth = aTextLayouter.getTextWidth(rText, nTextPosition, nTextLength); + } + else + { + fTextWidth = rDXArray.back() * rDecTrans.getScale().getX(); + const double fFontScaleX(rDecTrans.getScale().getX()); + + if(!basegfx::fTools::equal(fFontScaleX, 1.0) + && !basegfx::fTools::equalZero(fFontScaleX)) + { + // need to take FontScaling out of the DXArray + fTextWidth /= fFontScaleX; + } + } + + if(bOverlineUsed) + { + // create primitive geometry for overline + rTarget.push_back(Primitive2DReference( + new TextLinePrimitive2D( + rDecTrans.getB2DHomMatrix(), + fTextWidth, + aTextLayouter.getOverlineOffset(), + aTextLayouter.getOverlineHeight(), + getFontOverline(), + getOverlineColor()))); + } + + if(bUnderlineUsed) + { + // create primitive geometry for underline + rTarget.push_back(Primitive2DReference( + new TextLinePrimitive2D( + rDecTrans.getB2DHomMatrix(), + fTextWidth, + aTextLayouter.getUnderlineOffset(), + aTextLayouter.getUnderlineHeight(), + getFontUnderline(), + getTextlineColor()))); + } + + if(!bStrikeoutUsed) + return; + + // create primitive geometry for strikeout + if(TEXT_STRIKEOUT_SLASH == getTextStrikeout() || TEXT_STRIKEOUT_X == getTextStrikeout()) + { + // strikeout with character + const sal_Unicode aStrikeoutChar(TEXT_STRIKEOUT_SLASH == getTextStrikeout() ? '/' : 'X'); + + rTarget.push_back(Primitive2DReference( + new TextCharacterStrikeoutPrimitive2D( + rDecTrans.getB2DHomMatrix(), + fTextWidth, + getFontColor(), + aStrikeoutChar, + getFontAttribute(), + getLocale()))); + } + else + { + // strikeout with geometry + rTarget.push_back(Primitive2DReference( + new TextGeometryStrikeoutPrimitive2D( + rDecTrans.getB2DHomMatrix(), + fTextWidth, + getFontColor(), + aTextLayouter.getUnderlineHeight(), + aTextLayouter.getStrikeoutOffset(), + getTextStrikeout()))); + } + + // TODO: Handle Font Emphasis Above/Below + } + + void TextDecoratedPortionPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(getWordLineMode()) + { + // support for single word mode; split to single word primitives + // using TextBreakupHelper + const TextBreakupHelper aTextBreakupHelper(*this); + const Primitive2DContainer& aBroken(aTextBreakupHelper.getResult(BreakupUnit::Word)); + + if(!aBroken.empty()) + { + // was indeed split to several words, use as result + rContainer.insert(rContainer.end(), aBroken.begin(), aBroken.end()); + return; + } + else + { + // no split, was already a single word. Continue to + // decompose local entity + } + } + std::vector< Primitive2DReference > aNewPrimitives; + basegfx::utils::B2DHomMatrixBufferedOnDemandDecompose aDecTrans(getTextTransform()); + Primitive2DContainer aRetval; + + // create basic geometry such as SimpleTextPrimitive, Overline, Underline, + // Strikeout, etc... + // prepare new font attributes WITHOUT outline + const attribute::FontAttribute aNewFontAttribute( + getFontAttribute().getFamilyName(), + getFontAttribute().getStyleName(), + getFontAttribute().getWeight(), + getFontAttribute().getSymbol(), + getFontAttribute().getVertical(), + getFontAttribute().getItalic(), + getFontAttribute().getMonospaced(), + false, // no outline anymore, handled locally + getFontAttribute().getRTL(), + getFontAttribute().getBiDiStrong()); + + // handle as one word + impCreateGeometryContent(aNewPrimitives, aDecTrans, getText(), getTextPosition(), getTextLength(), getDXArray(), aNewFontAttribute); + + // convert to Primitive2DSequence + const sal_uInt32 nMemberCount(aNewPrimitives.size()); + + if(nMemberCount) + { + aRetval.resize(nMemberCount); + + for(sal_uInt32 a(0); a < nMemberCount; a++) + { + aRetval[a] = aNewPrimitives[a]; + } + } + + // Handle Shadow, Outline and TextRelief + if(!aRetval.empty()) + { + // outline AND shadow depend on NO TextRelief (see dialog) + const bool bHasTextRelief(TEXT_RELIEF_NONE != getTextRelief()); + const bool bHasShadow(!bHasTextRelief && getShadow()); + const bool bHasOutline(!bHasTextRelief && getFontAttribute().getOutline()); + + if(bHasShadow || bHasTextRelief || bHasOutline) + { + Primitive2DReference aShadow; + + if(bHasShadow) + { + // create shadow with current content (in aRetval). Text shadow + // is constant, relative to font size, rotated with the text and has a + // constant color. + // shadow parameter values + static const double fFactor(1.0 / 24.0); + const double fTextShadowOffset(aDecTrans.getScale().getY() * fFactor); + static basegfx::BColor aShadowColor(0.3, 0.3, 0.3); + + // prepare shadow transform matrix + const basegfx::B2DHomMatrix aShadowTransform(basegfx::utils::createTranslateB2DHomMatrix( + fTextShadowOffset, fTextShadowOffset)); + + // create shadow primitive + aShadow = new ShadowPrimitive2D( + aShadowTransform, + aShadowColor, + aRetval); + } + + if(bHasTextRelief) + { + // create emboss using an own helper primitive since this will + // be view-dependent + const basegfx::BColor aBBlack(0.0, 0.0, 0.0); + const bool bDefaultTextColor(aBBlack == getFontColor()); + TextEffectStyle2D aTextEffectStyle2D(TextEffectStyle2D::ReliefEmbossed); + + if(bDefaultTextColor) + { + if(TEXT_RELIEF_ENGRAVED == getTextRelief()) + { + aTextEffectStyle2D = TextEffectStyle2D::ReliefEngravedDefault; + } + else + { + aTextEffectStyle2D = TextEffectStyle2D::ReliefEmbossedDefault; + } + } + else + { + if(TEXT_RELIEF_ENGRAVED == getTextRelief()) + { + aTextEffectStyle2D = TextEffectStyle2D::ReliefEngraved; + } + else + { + aTextEffectStyle2D = TextEffectStyle2D::ReliefEmbossed; + } + } + + Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D( + aRetval, + aDecTrans.getTranslate(), + aDecTrans.getRotate(), + aTextEffectStyle2D)); + aRetval = Primitive2DContainer { aNewTextEffect }; + } + else if(bHasOutline) + { + // create outline using an own helper primitive since this will + // be view-dependent + Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D( + aRetval, + aDecTrans.getTranslate(), + aDecTrans.getRotate(), + TextEffectStyle2D::Outline)); + aRetval = Primitive2DContainer { aNewTextEffect }; + } + + if(aShadow.is()) + { + // put shadow in front if there is one to paint timely before + // but placed behind content + aRetval.insert(aRetval.begin(), aShadow); + } + } + } + + rContainer.insert(rContainer.end(), aRetval.begin(), aRetval.end()); + } + + TextDecoratedPortionPrimitive2D::TextDecoratedPortionPrimitive2D( + // TextSimplePortionPrimitive2D parameters + const basegfx::B2DHomMatrix& rNewTransform, + const OUString& rText, + sal_Int32 nTextPosition, + sal_Int32 nTextLength, + const std::vector< double >& rDXArray, + const attribute::FontAttribute& rFontAttribute, + const css::lang::Locale& rLocale, + const basegfx::BColor& rFontColor, + const Color& rFillColor, + + // local parameters + const basegfx::BColor& rOverlineColor, + const basegfx::BColor& rTextlineColor, + TextLine eFontOverline, + TextLine eFontUnderline, + bool bUnderlineAbove, + TextStrikeout eTextStrikeout, + bool bWordLineMode, + TextEmphasisMark eTextEmphasisMark, + bool bEmphasisMarkAbove, + bool bEmphasisMarkBelow, + TextRelief eTextRelief, + bool bShadow) + : TextSimplePortionPrimitive2D(rNewTransform, rText, nTextPosition, nTextLength, rDXArray, rFontAttribute, rLocale, rFontColor, false, 0, rFillColor), + maOverlineColor(rOverlineColor), + maTextlineColor(rTextlineColor), + meFontOverline(eFontOverline), + meFontUnderline(eFontUnderline), + meTextStrikeout(eTextStrikeout), + meTextEmphasisMark(eTextEmphasisMark), + meTextRelief(eTextRelief), + mbUnderlineAbove(bUnderlineAbove), + mbWordLineMode(bWordLineMode), + mbEmphasisMarkAbove(bEmphasisMarkAbove), + mbEmphasisMarkBelow(bEmphasisMarkBelow), + mbShadow(bShadow) + { + } + + bool TextDecoratedPortionPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(TextSimplePortionPrimitive2D::operator==(rPrimitive)) + { + const TextDecoratedPortionPrimitive2D& rCompare = static_cast<const TextDecoratedPortionPrimitive2D&>(rPrimitive); + + return (getOverlineColor() == rCompare.getOverlineColor() + && getTextlineColor() == rCompare.getTextlineColor() + && getFontOverline() == rCompare.getFontOverline() + && getFontUnderline() == rCompare.getFontUnderline() + && getTextStrikeout() == rCompare.getTextStrikeout() + && getTextEmphasisMark() == rCompare.getTextEmphasisMark() + && getTextRelief() == rCompare.getTextRelief() + && getUnderlineAbove() == rCompare.getUnderlineAbove() + && getWordLineMode() == rCompare.getWordLineMode() + && getEmphasisMarkAbove() == rCompare.getEmphasisMarkAbove() + && getEmphasisMarkBelow() == rCompare.getEmphasisMarkBelow() + && getShadow() == rCompare.getShadow()); + } + + return false; + } + + // #i96475# + // Added missing implementation. Decorations may (will) stick out of the text's + // inking area, so add them if needed + basegfx::B2DRange TextDecoratedPortionPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + // check if this needs to be a TextDecoratedPortionPrimitive2D or + // if a TextSimplePortionPrimitive2D would be sufficient + if (TEXT_LINE_NONE != getFontOverline() + || TEXT_LINE_NONE != getFontUnderline() + || TEXT_STRIKEOUT_NONE != getTextStrikeout() + || TEXT_FONT_EMPHASIS_MARK_NONE != getTextEmphasisMark() + || TEXT_RELIEF_NONE != getTextRelief() + || getShadow()) + { + // decoration is used, fallback to BufferedDecompositionPrimitive2D::getB2DRange which uses + // the own local decomposition for computation and thus creates all necessary + // geometric objects + return BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation); + } + else + { + // no relevant decoration used, fallback to TextSimplePortionPrimitive2D::getB2DRange + return TextSimplePortionPrimitive2D::getB2DRange(rViewInformation); + } + } + + // provide unique ID + ImplPrimitive2DIDBlock(TextDecoratedPortionPrimitive2D, PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/texteffectprimitive2d.cxx b/drawinglayer/source/primitive2d/texteffectprimitive2d.cxx new file mode 100644 index 000000000..b552793bf --- /dev/null +++ b/drawinglayer/source/primitive2d/texteffectprimitive2d.cxx @@ -0,0 +1,246 @@ +/* -*- 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 <primitive2d/texteffectprimitive2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + + +namespace drawinglayer::primitive2d +{ + static const double fDiscreteSize(1.1); + + void TextEffectPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& rViewInformation) const + { + // get the distance of one discrete units from target display. Use between 1.0 and sqrt(2) to + // have good results on rotated objects, too + const basegfx::B2DVector aDistance(rViewInformation.getInverseObjectToViewTransformation() * + basegfx::B2DVector(fDiscreteSize, fDiscreteSize)); + const basegfx::B2DVector aDiagonalDistance(aDistance * (1.0 / 1.44)); + + switch(getTextEffectStyle2D()) + { + case TextEffectStyle2D::ReliefEmbossed: + case TextEffectStyle2D::ReliefEngraved: + case TextEffectStyle2D::ReliefEmbossedDefault: + case TextEffectStyle2D::ReliefEngravedDefault: + { + // prepare transform of sub-group back to (0,0) and align to X-Axis + basegfx::B2DHomMatrix aBackTransform(basegfx::utils::createTranslateB2DHomMatrix( + -getRotationCenter().getX(), -getRotationCenter().getY())); + aBackTransform.rotate(-getDirection()); + + // prepare transform of sub-group back to its position and rotation + basegfx::B2DHomMatrix aForwardTransform(basegfx::utils::createRotateB2DHomMatrix(getDirection())); + aForwardTransform.translate(getRotationCenter().getX(), getRotationCenter().getY()); + + // create transformation for one discrete unit + const bool bEmbossed( + TextEffectStyle2D::ReliefEmbossed == getTextEffectStyle2D() + || TextEffectStyle2D::ReliefEmbossedDefault == getTextEffectStyle2D()); + const bool bDefaultTextColor( + TextEffectStyle2D::ReliefEmbossedDefault == getTextEffectStyle2D() + || TextEffectStyle2D::ReliefEngravedDefault == getTextEffectStyle2D()); + basegfx::B2DHomMatrix aTransform(aBackTransform); + + if(bEmbossed) + { + // to bottom-right + aTransform.translate(aDiagonalDistance.getX(), aDiagonalDistance.getY()); + } + else + { + // to top-left + aTransform.translate(-aDiagonalDistance.getX(), -aDiagonalDistance.getY()); + } + + aTransform *= aForwardTransform; + + if(bDefaultTextColor) + { + // emboss/engrave in black, original forced to white + const basegfx::BColorModifierSharedPtr aBColorModifierToGray = + std::make_shared<basegfx::BColorModifier_replace>( + basegfx::BColor(0.0)); + const Primitive2DReference xModifiedColor( + new ModifiedColorPrimitive2D( + getTextContent(), + aBColorModifierToGray)); + + rContainer.push_back( + new TransformPrimitive2D( + aTransform, + Primitive2DContainer { xModifiedColor })); + + // add original, too + const basegfx::BColorModifierSharedPtr aBColorModifierToWhite = + std::make_shared<basegfx::BColorModifier_replace>( + basegfx::BColor(1.0)); + + rContainer.push_back( + new ModifiedColorPrimitive2D( + getTextContent(), + aBColorModifierToWhite)); + } + else + { + // emboss/engrave in gray, keep original's color + const basegfx::BColorModifierSharedPtr aBColorModifierToGray = + std::make_shared<basegfx::BColorModifier_replace>( + basegfx::BColor(0.75)); // 192 + const Primitive2DReference xModifiedColor( + new ModifiedColorPrimitive2D( + getTextContent(), + aBColorModifierToGray)); + + rContainer.push_back( + new TransformPrimitive2D( + aTransform, + Primitive2DContainer { xModifiedColor })); + + // add original, too + rContainer.push_back(new GroupPrimitive2D(getTextContent())); + } + + break; + } + case TextEffectStyle2D::Outline: + { + // create transform primitives in all directions + basegfx::B2DHomMatrix aTransform; + + aTransform.set(0, 2, aDistance.getX()); + aTransform.set(1, 2, 0.0); + rContainer.push_back(new TransformPrimitive2D(aTransform, getTextContent())); + + aTransform.set(0, 2, aDiagonalDistance.getX()); + aTransform.set(1, 2, aDiagonalDistance.getY()); + rContainer.push_back(new TransformPrimitive2D(aTransform, getTextContent())); + + aTransform.set(0, 2, 0.0); + aTransform.set(1, 2, aDistance.getY()); + rContainer.push_back(new TransformPrimitive2D(aTransform, getTextContent())); + + aTransform.set(0, 2, -aDiagonalDistance.getX()); + aTransform.set(1, 2, aDiagonalDistance.getY()); + rContainer.push_back(new TransformPrimitive2D(aTransform, getTextContent())); + + aTransform.set(0, 2, -aDistance.getX()); + aTransform.set(1, 2, 0.0); + rContainer.push_back(new TransformPrimitive2D(aTransform, getTextContent())); + + aTransform.set(0, 2, -aDiagonalDistance.getX()); + aTransform.set(1, 2, -aDiagonalDistance.getY()); + rContainer.push_back(new TransformPrimitive2D(aTransform, getTextContent())); + + aTransform.set(0, 2, 0.0); + aTransform.set(1, 2, -aDistance.getY()); + rContainer.push_back(new TransformPrimitive2D(aTransform, getTextContent())); + + aTransform.set(0, 2, aDiagonalDistance.getX()); + aTransform.set(1, 2, -aDiagonalDistance.getY()); + rContainer.push_back(new TransformPrimitive2D(aTransform, getTextContent())); + + // at last, place original over it, but force to white + const basegfx::BColorModifierSharedPtr aBColorModifierToWhite = + std::make_shared<basegfx::BColorModifier_replace>( + basegfx::BColor(1.0, 1.0, 1.0)); + rContainer.push_back( + new ModifiedColorPrimitive2D( + getTextContent(), + aBColorModifierToWhite)); + + break; + } + } + } + + TextEffectPrimitive2D::TextEffectPrimitive2D( + const Primitive2DContainer& rTextContent, + const basegfx::B2DPoint& rRotationCenter, + double fDirection, + TextEffectStyle2D eTextEffectStyle2D) + : BufferedDecompositionPrimitive2D(), + maTextContent(rTextContent), + maRotationCenter(rRotationCenter), + mfDirection(fDirection), + meTextEffectStyle2D(eTextEffectStyle2D) + { + } + + bool TextEffectPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BasePrimitive2D::operator==(rPrimitive)) + { + const TextEffectPrimitive2D& rCompare = static_cast<const TextEffectPrimitive2D&>(rPrimitive); + + return (getTextContent() == rCompare.getTextContent() + && getRotationCenter() == rCompare.getRotationCenter() + && getDirection() == rCompare.getDirection() + && getTextEffectStyle2D() == rCompare.getTextEffectStyle2D()); + } + + return false; + } + + basegfx::B2DRange TextEffectPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + // get range of content and grow by used fDiscreteSize. That way it is not necessary to ask + // the whole decomposition for its ranges (which may be expensive with outline mode which + // then will ask 9 times at nearly the same content. This may even be refined here using the + // TextEffectStyle information, e.g. for TEXTEFFECTSTYLE2D_RELIEF the grow needs only to + // be in two directions + basegfx::B2DRange aRetval(getTextContent().getB2DRange(rViewInformation)); + aRetval.grow(fDiscreteSize); + + return aRetval; + } + + void TextEffectPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if(!getBuffered2DDecomposition().empty()) + { + if(maLastObjectToViewTransformation != rViewInformation.getObjectToViewTransformation()) + { + // conditions of last local decomposition have changed, delete + const_cast< TextEffectPrimitive2D* >(this)->setBuffered2DDecomposition(Primitive2DContainer()); + } + } + + if(getBuffered2DDecomposition().empty()) + { + // remember ViewRange and ViewTransformation + const_cast< TextEffectPrimitive2D* >(this)->maLastObjectToViewTransformation = rViewInformation.getObjectToViewTransformation(); + } + + // use parent implementation + BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation); + } + + // provide unique ID + ImplPrimitive2DIDBlock(TextEffectPrimitive2D, PRIMITIVE2D_ID_TEXTEFFECTPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/textenumsprimitive2d.cxx b/drawinglayer/source/primitive2d/textenumsprimitive2d.cxx new file mode 100644 index 000000000..7f13cbbbe --- /dev/null +++ b/drawinglayer/source/primitive2d/textenumsprimitive2d.cxx @@ -0,0 +1,105 @@ +/* -*- 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/textenumsprimitive2d.hxx> + + +namespace drawinglayer::primitive2d +{ + TextLine mapFontLineStyleToTextLine(FontLineStyle eLineStyle) + { + switch(eLineStyle) + { + case LINESTYLE_SINGLE: return TEXT_LINE_SINGLE; + case LINESTYLE_DOUBLE: return TEXT_LINE_DOUBLE; + case LINESTYLE_DOTTED: return TEXT_LINE_DOTTED; + case LINESTYLE_DASH: return TEXT_LINE_DASH; + case LINESTYLE_LONGDASH: return TEXT_LINE_LONGDASH; + case LINESTYLE_DASHDOT: return TEXT_LINE_DASHDOT; + case LINESTYLE_DASHDOTDOT: return TEXT_LINE_DASHDOTDOT; + case LINESTYLE_SMALLWAVE: return TEXT_LINE_SMALLWAVE; + case LINESTYLE_WAVE: return TEXT_LINE_WAVE; + case LINESTYLE_DOUBLEWAVE: return TEXT_LINE_DOUBLEWAVE; + case LINESTYLE_BOLD: return TEXT_LINE_BOLD; + case LINESTYLE_BOLDDOTTED: return TEXT_LINE_BOLDDOTTED; + case LINESTYLE_BOLDDASH: return TEXT_LINE_BOLDDASH; + case LINESTYLE_BOLDLONGDASH: return TEXT_LINE_BOLDLONGDASH; + case LINESTYLE_BOLDDASHDOT: return TEXT_LINE_BOLDDASHDOT; + case LINESTYLE_BOLDDASHDOTDOT: return TEXT_LINE_BOLDDASHDOTDOT; + case LINESTYLE_BOLDWAVE: return TEXT_LINE_BOLDWAVE; + // FontLineStyle_FORCE_EQUAL_SIZE, LINESTYLE_DONTKNOW, LINESTYLE_NONE + default: return TEXT_LINE_NONE; + } + } + + FontLineStyle mapTextLineToFontLineStyle(TextLine eLineStyle) + { + switch(eLineStyle) + { + default: /*TEXT_LINE_NONE*/ return LINESTYLE_NONE; + case TEXT_LINE_SINGLE: return LINESTYLE_SINGLE; + case TEXT_LINE_DOUBLE: return LINESTYLE_DOUBLE; + case TEXT_LINE_DOTTED: return LINESTYLE_DOTTED; + case TEXT_LINE_DASH: return LINESTYLE_DASH; + case TEXT_LINE_LONGDASH: return LINESTYLE_LONGDASH; + case TEXT_LINE_DASHDOT: return LINESTYLE_DASHDOT; + case TEXT_LINE_DASHDOTDOT: return LINESTYLE_DASHDOTDOT; + case TEXT_LINE_SMALLWAVE: return LINESTYLE_SMALLWAVE; + case TEXT_LINE_WAVE: return LINESTYLE_WAVE; + case TEXT_LINE_DOUBLEWAVE: return LINESTYLE_DOUBLEWAVE; + case TEXT_LINE_BOLD: return LINESTYLE_BOLD; + case TEXT_LINE_BOLDDOTTED: return LINESTYLE_BOLDDOTTED; + case TEXT_LINE_BOLDDASH: return LINESTYLE_BOLDDASH; + case TEXT_LINE_BOLDLONGDASH: return LINESTYLE_LONGDASH; + case TEXT_LINE_BOLDDASHDOT: return LINESTYLE_BOLDDASHDOT; + case TEXT_LINE_BOLDDASHDOTDOT:return LINESTYLE_BOLDDASHDOTDOT; + case TEXT_LINE_BOLDWAVE: return LINESTYLE_BOLDWAVE; + } + } + + TextStrikeout mapFontStrikeoutToTextStrikeout(FontStrikeout eFontStrikeout) + { + switch(eFontStrikeout) + { + case STRIKEOUT_SINGLE: return TEXT_STRIKEOUT_SINGLE; + case STRIKEOUT_DOUBLE: return TEXT_STRIKEOUT_DOUBLE; + case STRIKEOUT_BOLD: return TEXT_STRIKEOUT_BOLD; + case STRIKEOUT_SLASH: return TEXT_STRIKEOUT_SLASH; + case STRIKEOUT_X: return TEXT_STRIKEOUT_X; + // FontStrikeout_FORCE_EQUAL_SIZE, STRIKEOUT_NONE, STRIKEOUT_DONTKNOW + default: return TEXT_STRIKEOUT_NONE; + } + } + + FontStrikeout mapTextStrikeoutToFontStrikeout(TextStrikeout eTextStrikeout) + { + switch(eTextStrikeout) + { + default: /*case primitive2d::TEXT_STRIKEOUT_NONE*/ return STRIKEOUT_NONE; + case TEXT_STRIKEOUT_SINGLE: return STRIKEOUT_SINGLE; + case TEXT_STRIKEOUT_DOUBLE: return STRIKEOUT_DOUBLE; + case TEXT_STRIKEOUT_BOLD: return STRIKEOUT_BOLD; + case TEXT_STRIKEOUT_SLASH: return STRIKEOUT_SLASH; + case TEXT_STRIKEOUT_X: return STRIKEOUT_X; + } + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/texthierarchyprimitive2d.cxx b/drawinglayer/source/primitive2d/texthierarchyprimitive2d.cxx new file mode 100644 index 000000000..77f7f6597 --- /dev/null +++ b/drawinglayer/source/primitive2d/texthierarchyprimitive2d.cxx @@ -0,0 +1,133 @@ +/* -*- 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/texthierarchyprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + TextHierarchyLinePrimitive2D::TextHierarchyLinePrimitive2D(const Primitive2DContainer& rChildren) + : GroupPrimitive2D(rChildren) + { + } + + // provide unique ID + ImplPrimitive2DIDBlock(TextHierarchyLinePrimitive2D, PRIMITIVE2D_ID_TEXTHIERARCHYLINEPRIMITIVE2D) + + + TextHierarchyParagraphPrimitive2D::TextHierarchyParagraphPrimitive2D( + const Primitive2DContainer& rChildren, + sal_Int16 nOutlineLevel) + : GroupPrimitive2D(rChildren), + mnOutlineLevel(nOutlineLevel) + { + } + + bool TextHierarchyParagraphPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const TextHierarchyParagraphPrimitive2D& rCompare = static_cast<const TextHierarchyParagraphPrimitive2D&>(rPrimitive); + + return (getOutlineLevel() == rCompare.getOutlineLevel()); + } + + return false; + } + + // provide unique ID + ImplPrimitive2DIDBlock(TextHierarchyParagraphPrimitive2D, PRIMITIVE2D_ID_TEXTHIERARCHYPARAGRAPHPRIMITIVE2D) + + + + TextHierarchyBulletPrimitive2D::TextHierarchyBulletPrimitive2D(const Primitive2DContainer& rChildren) + : GroupPrimitive2D(rChildren) + { + } + + // provide unique ID + ImplPrimitive2DIDBlock(TextHierarchyBulletPrimitive2D, PRIMITIVE2D_ID_TEXTHIERARCHYBULLETPRIMITIVE2D) + + + TextHierarchyBlockPrimitive2D::TextHierarchyBlockPrimitive2D(const Primitive2DContainer& rChildren) + : GroupPrimitive2D(rChildren) + { + } + + // provide unique ID + ImplPrimitive2DIDBlock(TextHierarchyBlockPrimitive2D, PRIMITIVE2D_ID_TEXTHIERARCHYBLOCKPRIMITIVE2D) + + + TextHierarchyFieldPrimitive2D::TextHierarchyFieldPrimitive2D( + const Primitive2DContainer& rChildren, + const FieldType& rFieldType, + const std::vector< std::pair< OUString, OUString>>* pNameValue) + : GroupPrimitive2D(rChildren), + meType(rFieldType), + meNameValue() + { + if (nullptr != pNameValue) + { + meNameValue = *pNameValue; + } + } + + OUString TextHierarchyFieldPrimitive2D::getValue(const OUString& rName) const + { + for (const std::pair< OUString, OUString >& candidate : meNameValue) + { + if (candidate.first.equals(rName)) + { + return candidate.second; + } + } + + return OUString(); + } + + bool TextHierarchyFieldPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const TextHierarchyFieldPrimitive2D& rCompare = static_cast<const TextHierarchyFieldPrimitive2D&>(rPrimitive); + + return (getType() == rCompare.getType() + && meNameValue == rCompare.meNameValue); + } + + return false; + } + + // provide unique ID + ImplPrimitive2DIDBlock(TextHierarchyFieldPrimitive2D, PRIMITIVE2D_ID_TEXTHIERARCHYFIELDPRIMITIVE2D) + TextHierarchyEditPrimitive2D::TextHierarchyEditPrimitive2D(const Primitive2DContainer& rChildren) + : GroupPrimitive2D(rChildren) + { + } + + // provide unique ID + ImplPrimitive2DIDBlock(TextHierarchyEditPrimitive2D, PRIMITIVE2D_ID_TEXTHIERARCHYEDITPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/textlayoutdevice.cxx b/drawinglayer/source/primitive2d/textlayoutdevice.cxx new file mode 100644 index 000000000..e3e57b2b5 --- /dev/null +++ b/drawinglayer/source/primitive2d/textlayoutdevice.cxx @@ -0,0 +1,484 @@ +/* -*- 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 <algorithm> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <drawinglayer/attribute/fontattribute.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/unique_disposing_ptr.hxx> +#include <osl/diagnose.h> +#include <tools/gen.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/timer.hxx> +#include <vcl/virdev.hxx> +#include <vcl/font.hxx> +#include <vcl/metric.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <vcl/svapp.hxx> + + +// VDev RevDevice provider + +namespace +{ + class ImpTimedRefDev; + + //the scoped_timed_RefDev owns an ImpTimeRefDev and releases it on dtor + //or disposing of the default XComponentContext which causes the underlying + //OutputDevice to get released + + //The ImpTimerRefDev itself, if the timeout ever gets hit, will call + //reset on the scoped_timed_RefDev to release the ImpTimerRefDev early + //if it's unused for a few minutes + class scoped_timed_RefDev : public comphelper::unique_disposing_ptr<ImpTimedRefDev> + { + public: + scoped_timed_RefDev() : comphelper::unique_disposing_ptr<ImpTimedRefDev>((css::uno::Reference<css::lang::XComponent>(::comphelper::getProcessComponentContext(), css::uno::UNO_QUERY_THROW))) + { + } + }; + + class the_scoped_timed_RefDev : public rtl::Static<scoped_timed_RefDev, the_scoped_timed_RefDev> {}; + + class ImpTimedRefDev : public Timer + { + scoped_timed_RefDev& mrOwnerOfMe; + VclPtr<VirtualDevice> mpVirDev; + sal_uInt32 mnUseCount; + + public: + explicit ImpTimedRefDev(scoped_timed_RefDev& rOwnerofMe); + virtual ~ImpTimedRefDev() override; + virtual void Invoke() override; + + VirtualDevice& acquireVirtualDevice(); + void releaseVirtualDevice(); + }; + + ImpTimedRefDev::ImpTimedRefDev(scoped_timed_RefDev& rOwnerOfMe) + : Timer( "drawinglayer ImpTimedRefDev destroy mpVirDev" ), + mrOwnerOfMe(rOwnerOfMe), + mpVirDev(nullptr), + mnUseCount(0) + { + SetTimeout(3L * 60L * 1000L); // three minutes + Start(); + } + + ImpTimedRefDev::~ImpTimedRefDev() + { + OSL_ENSURE(0 == mnUseCount, "destruction of a still used ImpTimedRefDev (!)"); + const SolarMutexGuard aSolarGuard; + mpVirDev.disposeAndClear(); + } + + void ImpTimedRefDev::Invoke() + { + // for obvious reasons, do not call anything after this + mrOwnerOfMe.reset(); + } + + VirtualDevice& ImpTimedRefDev::acquireVirtualDevice() + { + if(!mpVirDev) + { + mpVirDev = VclPtr<VirtualDevice>::Create(); + mpVirDev->SetReferenceDevice( VirtualDevice::RefDevMode::MSO1 ); + } + + if(!mnUseCount) + { + Stop(); + } + + mnUseCount++; + + return *mpVirDev; + } + + void ImpTimedRefDev::releaseVirtualDevice() + { + OSL_ENSURE(mnUseCount, "mismatch call number to releaseVirtualDevice() (!)"); + mnUseCount--; + + if(!mnUseCount) + { + Start(); + } + } +} // end of anonymous namespace + + +// access to one global ImpTimedRefDev incarnation in namespace drawinglayer::primitive + +namespace drawinglayer::primitive2d +{ + // static methods here + static VirtualDevice& acquireGlobalVirtualDevice() + { + scoped_timed_RefDev& rStdRefDevice = the_scoped_timed_RefDev::get(); + + if(!rStdRefDevice) + rStdRefDevice.reset(new ImpTimedRefDev(rStdRefDevice)); + + return rStdRefDevice->acquireVirtualDevice(); + } + + static void releaseGlobalVirtualDevice() + { + scoped_timed_RefDev& rStdRefDevice = the_scoped_timed_RefDev::get(); + + OSL_ENSURE(rStdRefDevice, "releaseGlobalVirtualDevice() without prior acquireGlobalVirtualDevice() call(!)"); + rStdRefDevice->releaseVirtualDevice(); + } + + TextLayouterDevice::TextLayouterDevice() + : maSolarGuard(), + mrDevice(acquireGlobalVirtualDevice()) + { + } + + TextLayouterDevice::~TextLayouterDevice() COVERITY_NOEXCEPT_FALSE + { + releaseGlobalVirtualDevice(); + } + + void TextLayouterDevice::setFont(const vcl::Font& rFont) + { + mrDevice.SetFont( rFont ); + } + + void TextLayouterDevice::setFontAttribute( + const attribute::FontAttribute& rFontAttribute, + double fFontScaleX, + double fFontScaleY, + const css::lang::Locale& rLocale) + { + setFont(getVclFontFromFontAttribute( + rFontAttribute, + fFontScaleX, + fFontScaleY, + 0.0, + rLocale)); + } + + double TextLayouterDevice::getOverlineOffset() const + { + const ::FontMetric& rMetric = mrDevice.GetFontMetric(); + double fRet = (rMetric.GetInternalLeading() / 2.0) - rMetric.GetAscent(); + return fRet; + } + + double TextLayouterDevice::getUnderlineOffset() const + { + const ::FontMetric& rMetric = mrDevice.GetFontMetric(); + double fRet = rMetric.GetDescent() / 2.0; + return fRet; + } + + double TextLayouterDevice::getStrikeoutOffset() const + { + const ::FontMetric& rMetric = mrDevice.GetFontMetric(); + double fRet = (rMetric.GetAscent() - rMetric.GetInternalLeading()) / 3.0; + return fRet; + } + + double TextLayouterDevice::getOverlineHeight() const + { + const ::FontMetric& rMetric = mrDevice.GetFontMetric(); + double fRet = rMetric.GetInternalLeading() / 2.5; + return fRet; + } + + double TextLayouterDevice::getUnderlineHeight() const + { + const ::FontMetric& rMetric = mrDevice.GetFontMetric(); + double fRet = rMetric.GetDescent() / 4.0; + return fRet; + } + + double TextLayouterDevice::getTextHeight() const + { + return mrDevice.GetTextHeight(); + } + + double TextLayouterDevice::getTextWidth( + const OUString& rText, + sal_uInt32 nIndex, + sal_uInt32 nLength) const + { + return mrDevice.GetTextWidth(rText, nIndex, nLength); + } + + void TextLayouterDevice::getTextOutlines( + basegfx::B2DPolyPolygonVector& rB2DPolyPolyVector, + const OUString& rText, + sal_uInt32 nIndex, + sal_uInt32 nLength, + const std::vector< double >& rDXArray) const + { + const sal_uInt32 nDXArrayCount(rDXArray.size()); + sal_uInt32 nTextLength(nLength); + const sal_uInt32 nStringLength(rText.getLength()); + + if(nTextLength + nIndex > nStringLength) + { + nTextLength = nStringLength - nIndex; + } + + if(nDXArrayCount) + { + OSL_ENSURE(nDXArrayCount == nTextLength, "DXArray size does not correspond to text portion size (!)"); + std::vector< long > aIntegerDXArray(nDXArrayCount); + + for(sal_uInt32 a(0); a < nDXArrayCount; a++) + { + aIntegerDXArray[a] = basegfx::fround(rDXArray[a]); + } + + mrDevice.GetTextOutlines( + rB2DPolyPolyVector, + rText, + nIndex, + nIndex, + nLength, + 0, + aIntegerDXArray.data()); + } + else + { + mrDevice.GetTextOutlines( + rB2DPolyPolyVector, + rText, + nIndex, + nIndex, + nLength); + } + } + + basegfx::B2DRange TextLayouterDevice::getTextBoundRect( + const OUString& rText, + sal_uInt32 nIndex, + sal_uInt32 nLength) const + { + sal_uInt32 nTextLength(nLength); + const sal_uInt32 nStringLength(rText.getLength()); + + if(nTextLength + nIndex > nStringLength) + { + nTextLength = nStringLength - nIndex; + } + + if(nTextLength) + { + ::tools::Rectangle aRect; + + mrDevice.GetTextBoundRect( + aRect, + rText, + nIndex, + nIndex, + nLength); + + // #i104432#, #i102556# take empty results into account + if(!aRect.IsEmpty()) + { + return vcl::unotools::b2DRectangleFromRectangle(aRect); + } + } + + return basegfx::B2DRange(); + } + + double TextLayouterDevice::getFontAscent() const + { + const ::FontMetric& rMetric = mrDevice.GetFontMetric(); + return rMetric.GetAscent(); + } + + double TextLayouterDevice::getFontDescent() const + { + const ::FontMetric& rMetric = mrDevice.GetFontMetric(); + return rMetric.GetDescent(); + } + + void TextLayouterDevice::addTextRectActions( + const ::tools::Rectangle& rRectangle, + const OUString& rText, + DrawTextFlags nStyle, + GDIMetaFile& rGDIMetaFile) const + { + mrDevice.AddTextRectActions( + rRectangle, rText, nStyle, rGDIMetaFile); + } + + std::vector< double > TextLayouterDevice::getTextArray( + const OUString& rText, + sal_uInt32 nIndex, + sal_uInt32 nLength) const + { + std::vector< double > aRetval; + sal_uInt32 nTextLength(nLength); + const sal_uInt32 nStringLength(rText.getLength()); + + if(nTextLength + nIndex > nStringLength) + { + nTextLength = nStringLength - nIndex; + } + + if(nTextLength) + { + aRetval.reserve(nTextLength); + std::vector<long> aArray(nTextLength); + mrDevice.GetTextArray(rText, aArray.data(), nIndex, nLength); + aRetval.assign(aArray.begin(), aArray.end()); + } + + return aRetval; + } + + +// helper methods for vcl font handling + + vcl::Font getVclFontFromFontAttribute( + const attribute::FontAttribute& rFontAttribute, + double fFontScaleX, + double fFontScaleY, + double fFontRotation, + const css::lang::Locale& rLocale) + { + // detect FontScaling + const sal_uInt32 nHeight(basegfx::fround(fabs(fFontScaleY))); + const sal_uInt32 nWidth(basegfx::fround(fabs(fFontScaleX))); + const bool bFontIsScaled(nHeight != nWidth); + +#ifdef _WIN32 + // for WIN32 systems, start with creating an unscaled font. If FontScaling + // is wanted, that width needs to be adapted using FontMetric again to get a + // width of the unscaled font + vcl::Font aRetval( + rFontAttribute.getFamilyName(), + rFontAttribute.getStyleName(), + Size(0, nHeight)); +#else + // for non-WIN32 systems things are easier since these accept a Font creation + // with initially nWidth != nHeight for FontScaling. Despite that, use zero for + // FontWidth when no scaling is used to explicitly have that zero when e.g. the + // Font would be recorded in a MetaFile (The MetaFile FontAction WILL record a + // set FontWidth; import that in a WIN32 system, and trouble is there) + vcl::Font aRetval( + rFontAttribute.getFamilyName(), + rFontAttribute.getStyleName(), + Size(bFontIsScaled ? std::max<sal_uInt32>(nWidth, 1) : 0, nHeight)); +#endif + // define various other FontAttribute + aRetval.SetAlignment(ALIGN_BASELINE); + aRetval.SetCharSet(rFontAttribute.getSymbol() ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE); + aRetval.SetVertical(rFontAttribute.getVertical()); + aRetval.SetWeight(static_cast<FontWeight>(rFontAttribute.getWeight())); + aRetval.SetItalic(rFontAttribute.getItalic() ? ITALIC_NORMAL : ITALIC_NONE); + aRetval.SetOutline(rFontAttribute.getOutline()); + aRetval.SetPitch(rFontAttribute.getMonospaced() ? PITCH_FIXED : PITCH_VARIABLE); + aRetval.SetLanguage(LanguageTag::convertToLanguageType( rLocale, false)); + +#ifdef _WIN32 + // for WIN32 systems, correct the FontWidth if FontScaling is used + if(bFontIsScaled && nHeight > 0) + { + const FontMetric aUnscaledFontMetric(Application::GetDefaultDevice()->GetFontMetric(aRetval)); + + if(aUnscaledFontMetric.GetAverageFontWidth() > 0) + { + const double fScaleFactor(static_cast<double>(nWidth) / static_cast<double>(nHeight)); + const sal_uInt32 nScaledWidth(basegfx::fround(static_cast<double>(aUnscaledFontMetric.GetAverageFontWidth()) * fScaleFactor)); + aRetval.SetAverageFontWidth(nScaledWidth); + } + } +#endif + // handle FontRotation (if defined) + if(!basegfx::fTools::equalZero(fFontRotation)) + { + sal_Int16 aRotate10th(static_cast<sal_Int16>(fFontRotation * (-1800.0/F_PI))); + aRetval.SetOrientation(aRotate10th % 3600); + } + + return aRetval; + } + + attribute::FontAttribute getFontAttributeFromVclFont( + basegfx::B2DVector& o_rSize, + const vcl::Font& rFont, + bool bRTL, + bool bBiDiStrong) + { + const attribute::FontAttribute aRetval( + rFont.GetFamilyName(), + rFont.GetStyleName(), + static_cast<sal_uInt16>(rFont.GetWeight()), + RTL_TEXTENCODING_SYMBOL == rFont.GetCharSet(), + rFont.IsVertical(), + ITALIC_NONE != rFont.GetItalic(), + PITCH_FIXED == rFont.GetPitch(), + rFont.IsOutline(), + bRTL, + bBiDiStrong); + // TODO: eKerning + + // set FontHeight and init to no FontScaling + o_rSize.setY(std::max<long>(rFont.GetFontSize().getHeight(), 0)); + o_rSize.setX(o_rSize.getY()); + +#ifdef _WIN32 + // for WIN32 systems, the FontScaling at the Font is detected by + // checking that FontWidth != 0. When FontScaling is used, WIN32 + // needs to do extra stuff to detect the correct width (since it's + // zero and not equal the font height) and its relationship to + // the height + if(rFont.GetFontSize().getWidth() > 0) + { + vcl::Font aUnscaledFont(rFont); + aUnscaledFont.SetAverageFontWidth(0); + const FontMetric aUnscaledFontMetric(Application::GetDefaultDevice()->GetFontMetric(aUnscaledFont)); + + if(aUnscaledFontMetric.GetAverageFontWidth() > 0) + { + const double fScaleFactor(static_cast<double>(rFont.GetFontSize().getWidth()) / static_cast<double>(aUnscaledFontMetric.GetAverageFontWidth())); + o_rSize.setX(fScaleFactor * o_rSize.getY()); + } + } +#else + // For non-WIN32 systems the detection is the same, but the value + // is easier achieved since width == height is interpreted as no + // scaling. Ergo, Width == 0 means width == height, and width != 0 + // means the scaling is in the direct relation of width to height + if(rFont.GetFontSize().getWidth() > 0) + { + o_rSize.setX(static_cast<double>(rFont.GetFontSize().getWidth())); + } +#endif + return aRetval; + } + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/textlineprimitive2d.cxx b/drawinglayer/source/primitive2d/textlineprimitive2d.cxx new file mode 100644 index 000000000..2238fc383 --- /dev/null +++ b/drawinglayer/source/primitive2d/textlineprimitive2d.cxx @@ -0,0 +1,288 @@ +/* -*- 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 <primitive2d/textlineprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/attribute/strokeattribute.hxx> +#include <drawinglayer/attribute/lineattribute.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> + + +namespace drawinglayer::primitive2d +{ + void TextLinePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(TEXT_LINE_NONE == getTextLine()) + return; + + bool bDoubleLine(false); + bool bWaveLine(false); + bool bBoldLine(false); + const int* pDotDashArray(nullptr); + basegfx::B2DLineJoin eLineJoin(basegfx::B2DLineJoin::NONE); + double fOffset(getOffset()); + double fHeight(getHeight()); + + static const int aDottedArray[] = { 1, 1, 0}; // DOTTED LINE + static const int aDotDashArray[] = { 1, 1, 4, 1, 0}; // DASHDOT + static const int aDashDotDotArray[] = { 1, 1, 1, 1, 4, 1, 0}; // DASHDOTDOT + static const int aDashedArray[] = { 5, 2, 0}; // DASHED LINE + static const int aLongDashArray[] = { 7, 2, 0}; // LONGDASH + + // get decomposition + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + getObjectTransformation().decompose(aScale, aTranslate, fRotate, fShearX); + + switch(getTextLine()) + { + default: // case TEXT_LINE_SINGLE: + { + break; + } + case TEXT_LINE_DOUBLE: + { + bDoubleLine = true; + break; + } + case TEXT_LINE_DOTTED: + { + pDotDashArray = aDottedArray; + break; + } + case TEXT_LINE_DASH: + { + pDotDashArray = aDashedArray; + break; + } + case TEXT_LINE_LONGDASH: + { + pDotDashArray = aLongDashArray; + break; + } + case TEXT_LINE_DASHDOT: + { + pDotDashArray = aDotDashArray; + break; + } + case TEXT_LINE_DASHDOTDOT: + { + pDotDashArray = aDashDotDotArray; + break; + } + case TEXT_LINE_SMALLWAVE: + { + bWaveLine = true; + break; + } + case TEXT_LINE_WAVE: + { + bWaveLine = true; + break; + } + case TEXT_LINE_DOUBLEWAVE: + { + bDoubleLine = true; + bWaveLine = true; + break; + } + case TEXT_LINE_BOLD: + { + bBoldLine = true; + break; + } + case TEXT_LINE_BOLDDOTTED: + { + bBoldLine = true; + pDotDashArray = aDottedArray; + break; + } + case TEXT_LINE_BOLDDASH: + { + bBoldLine = true; + pDotDashArray = aDashedArray; + break; + } + case TEXT_LINE_BOLDLONGDASH: + { + bBoldLine = true; + pDotDashArray = aLongDashArray; + break; + } + case TEXT_LINE_BOLDDASHDOT: + { + bBoldLine = true; + pDotDashArray = aDotDashArray; + break; + } + case TEXT_LINE_BOLDDASHDOTDOT: + { + bBoldLine = true; + pDotDashArray = aDashDotDotArray; + break; + } + case TEXT_LINE_BOLDWAVE: + { + bWaveLine = true; + bBoldLine = true; + break; + } + } + + if(bBoldLine) + { + fHeight *= 2.0; + } + + if(bDoubleLine) + { + fOffset -= 0.50 * fHeight; + fHeight *= 0.64; + } + + if(bWaveLine) + { + eLineJoin = basegfx::B2DLineJoin::Round; + fHeight *= 0.25; + } + + // prepare Line and Stroke Attributes + const attribute::LineAttribute aLineAttribute(getLineColor(), fHeight, eLineJoin); + attribute::StrokeAttribute aStrokeAttribute; + + if(pDotDashArray) + { + std::vector< double > aDoubleArray; + + for(const int* p = pDotDashArray; *p; ++p) + { + aDoubleArray.push_back(static_cast<double>(*p) * fHeight); + } + + aStrokeAttribute = attribute::StrokeAttribute(aDoubleArray); + } + + // create base polygon and new primitive + basegfx::B2DPolygon aLine; + Primitive2DReference aNewPrimitive; + + aLine.append(basegfx::B2DPoint(0.0, fOffset)); + aLine.append(basegfx::B2DPoint(getWidth(), fOffset)); + + const basegfx::B2DHomMatrix aUnscaledTransform( + basegfx::utils::createShearXRotateTranslateB2DHomMatrix( + fShearX, fRotate, aTranslate)); + + aLine.transform(aUnscaledTransform); + + if(bWaveLine) + { + double fWaveWidth(10.6 * fHeight); + + if(TEXT_LINE_SMALLWAVE == getTextLine()) + { + fWaveWidth *= 0.7; + } + else if(TEXT_LINE_WAVE == getTextLine()) + { + // extra multiply to get the same WaveWidth as with the bold version + fWaveWidth *= 2.0; + } + + aNewPrimitive = Primitive2DReference(new PolygonWavePrimitive2D(aLine, aLineAttribute, aStrokeAttribute, fWaveWidth, fWaveWidth * 0.5)); + } + else + { + aNewPrimitive = Primitive2DReference(new PolygonStrokePrimitive2D(aLine, aLineAttribute, aStrokeAttribute)); + } + + // add primitive + rContainer.push_back(aNewPrimitive); + + if(!bDoubleLine) + return; + + // double line, create 2nd primitive with offset using TransformPrimitive based on + // already created NewPrimitive + double fLineDist(2.3 * fHeight); + + if(bWaveLine) + { + fLineDist = 6.3 * fHeight; + } + + // move base point of text to 0.0 and de-rotate + basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix( + -aTranslate.getX(), -aTranslate.getY())); + aTransform.rotate(-fRotate); + + // translate in Y by offset + aTransform.translate(0.0, fLineDist); + + // move back and rotate + aTransform.rotate(fRotate); + aTransform.translate(aTranslate.getX(), aTranslate.getY()); + + // add transform primitive + const Primitive2DContainer aContent { aNewPrimitive }; + rContainer.push_back( new TransformPrimitive2D(aTransform, aContent) ); + } + + TextLinePrimitive2D::TextLinePrimitive2D( + const basegfx::B2DHomMatrix& rObjectTransformation, + double fWidth, + double fOffset, + double fHeight, + TextLine eTextLine, + const basegfx::BColor& rLineColor) + : BufferedDecompositionPrimitive2D(), + maObjectTransformation(rObjectTransformation), + mfWidth(fWidth), + mfOffset(fOffset), + mfHeight(fHeight), + meTextLine(eTextLine), + maLineColor(rLineColor) + { + } + + bool TextLinePrimitive2D::operator==( const BasePrimitive2D& rPrimitive ) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const TextLinePrimitive2D& rCompare = static_cast<const TextLinePrimitive2D&>(rPrimitive); + + return (getObjectTransformation() == rCompare.getObjectTransformation() + && getWidth() == rCompare.getWidth() + && getOffset() == rCompare.getOffset() + && getHeight() == rCompare.getHeight() + && getTextLine() == rCompare.getTextLine() + && getLineColor() == rCompare.getLineColor()); + } + + return false; + } + + // provide unique ID + ImplPrimitive2DIDBlock(TextLinePrimitive2D, PRIMITIVE2D_ID_TEXTLINEPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/textprimitive2d.cxx b/drawinglayer/source/primitive2d/textprimitive2d.cxx new file mode 100644 index 000000000..e85d61009 --- /dev/null +++ b/drawinglayer/source/primitive2d/textprimitive2d.cxx @@ -0,0 +1,329 @@ +/* -*- 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/textprimitive2d.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <primitive2d/texteffectprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> + + +using namespace com::sun::star; + + +namespace +{ + // adapts fontScale for usage with TextLayouter. Input is rScale which is the extracted + // scale from a text transformation. A copy is modified so that it contains only positive + // scalings and XY-equal scalings to allow to get a non-X-scaled Vcl-Font for TextLayouter. + // rScale is adapted accordingly to contain the corrected scale which would need to be + // applied to e.g. outlines received from TextLayouter under usage of fontScale. This + // includes Y-Scale, X-Scale-correction and mirrorings. + basegfx::B2DVector getCorrectedScaleAndFontScale(basegfx::B2DVector& rScale) + { + // copy input value + basegfx::B2DVector aFontScale(rScale); + + // correct FontHeight settings + if(basegfx::fTools::equalZero(aFontScale.getY())) + { + // no font height; choose one and adapt scale to get back to original scaling + static const double fDefaultFontScale(100.0); + rScale.setY(1.0 / fDefaultFontScale); + aFontScale.setY(fDefaultFontScale); + } + else if(basegfx::fTools::less(aFontScale.getY(), 0.0)) + { + // negative font height; invert and adapt scale to get back to original scaling + aFontScale.setY(-aFontScale.getY()); + rScale.setY(-1.0); + } + else + { + // positive font height; adapt scale; scaling will be part of the polygons + rScale.setY(1.0); + } + + // correct FontWidth settings + if(basegfx::fTools::equal(aFontScale.getX(), aFontScale.getY())) + { + // no FontScale, adapt scale + rScale.setX(1.0); + } + else + { + // If FontScale is used, force to no FontScale to get a non-scaled VCL font. + // Adapt scaling in X accordingly. + rScale.setX(aFontScale.getX() / aFontScale.getY()); + aFontScale.setX(aFontScale.getY()); + } + + return aFontScale; + } +} // end of anonymous namespace + + +namespace drawinglayer::primitive2d +{ + void TextSimplePortionPrimitive2D::getTextOutlinesAndTransformation(basegfx::B2DPolyPolygonVector& rTarget, basegfx::B2DHomMatrix& rTransformation) const + { + if(!getTextLength()) + return; + + // decompose object transformation to single values + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + + // if decomposition returns false, create no geometry since e.g. scaling may + // be zero + if (!(getTextTransform().decompose(aScale, aTranslate, fRotate, fShearX) && aScale.getX() != 0.0)) + return; + + // handle special case: If scale is negative in (x,y) (3rd quadrant), it can + // be expressed as rotation by PI + if(basegfx::fTools::less(aScale.getX(), 0.0) && basegfx::fTools::less(aScale.getY(), 0.0)) + { + aScale = basegfx::absolute(aScale); + fRotate += F_PI; + } + + // for the TextLayouterDevice, it is necessary to have a scaling representing + // the font size. Since we want to extract polygons here, it is okay to + // work just with scaling and to ignore shear, rotation and translation, + // all that can be applied to the polygons later + const basegfx::B2DVector aFontScale(getCorrectedScaleAndFontScale(aScale)); + + // prepare textlayoutdevice + TextLayouterDevice aTextLayouter; + aTextLayouter.setFontAttribute( + getFontAttribute(), + aFontScale.getX(), + aFontScale.getY(), + getLocale()); + + // When getting outlines from stretched text (aScale.getX() != 1.0) it + // is necessary to inverse-scale the DXArray (if used) to not get the + // outlines already aligned to given, but wrong DXArray + if(!getDXArray().empty() && !basegfx::fTools::equal(aScale.getX(), 1.0)) + { + std::vector< double > aScaledDXArray = getDXArray(); + const double fDXArrayScale(1.0 / aScale.getX()); + + for(double & a : aScaledDXArray) + { + a *= fDXArrayScale; + } + + // get the text outlines + aTextLayouter.getTextOutlines( + rTarget, + getText(), + getTextPosition(), + getTextLength(), + aScaledDXArray); + } + else + { + // get the text outlines + aTextLayouter.getTextOutlines( + rTarget, + getText(), + getTextPosition(), + getTextLength(), + getDXArray()); + } + + // create primitives for the outlines + const sal_uInt32 nCount(rTarget.size()); + + if(nCount) + { + // prepare object transformation for polygons + rTransformation = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aScale, fShearX, fRotate, aTranslate); + } + } + + void TextSimplePortionPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(!getTextLength()) + return; + + Primitive2DContainer aRetval; + basegfx::B2DPolyPolygonVector aB2DPolyPolyVector; + basegfx::B2DHomMatrix aPolygonTransform; + + // get text outlines and their object transformation + getTextOutlinesAndTransformation(aB2DPolyPolyVector, aPolygonTransform); + + // create primitives for the outlines + const sal_uInt32 nCount(aB2DPolyPolyVector.size()); + + if(!nCount) + return; + + // alloc space for the primitives + aRetval.resize(nCount); + + // color-filled polypolygons + for(sal_uInt32 a(0); a < nCount; a++) + { + // prepare polypolygon + basegfx::B2DPolyPolygon& rPolyPolygon = aB2DPolyPolyVector[a]; + rPolyPolygon.transform(aPolygonTransform); + aRetval[a] = new PolyPolygonColorPrimitive2D(rPolyPolygon, getFontColor()); + } + + if(getFontAttribute().getOutline()) + { + // decompose polygon transformation to single values + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + aPolygonTransform.decompose(aScale, aTranslate, fRotate, fShearX); + + // create outline text effect with current content and replace + Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D( + aRetval, + aTranslate, + fRotate, + TextEffectStyle2D::Outline)); + + aRetval = Primitive2DContainer { aNewTextEffect }; + } + + rContainer.insert(rContainer.end(), aRetval.begin(), aRetval.end()); + } + + TextSimplePortionPrimitive2D::TextSimplePortionPrimitive2D( + const basegfx::B2DHomMatrix& rNewTransform, + const OUString& rText, + sal_Int32 nTextPosition, + sal_Int32 nTextLength, + const std::vector< double >& rDXArray, + const attribute::FontAttribute& rFontAttribute, + const css::lang::Locale& rLocale, + const basegfx::BColor& rFontColor, + bool bFilled, + long nWidthToFill, + const Color& rTextFillColor) + : BufferedDecompositionPrimitive2D(), + maTextTransform(rNewTransform), + maText(rText), + mnTextPosition(nTextPosition), + mnTextLength(nTextLength), + maDXArray(rDXArray), + maFontAttribute(rFontAttribute), + maLocale(rLocale), + maFontColor(rFontColor), + mbFilled(bFilled), + mnWidthToFill(nWidthToFill), + maTextFillColor(rTextFillColor), + maB2DRange() + { +#if OSL_DEBUG_LEVEL > 0 + const sal_Int32 aStringLength(getText().getLength()); + OSL_ENSURE(aStringLength >= getTextPosition() && aStringLength >= getTextPosition() + getTextLength(), + "TextSimplePortionPrimitive2D with text out of range (!)"); +#endif + } + + bool LocalesAreEqual(const css::lang::Locale& rA, const css::lang::Locale& rB) + { + return (rA.Language == rB.Language + && rA.Country == rB.Country + && rA.Variant == rB.Variant); + } + + bool TextSimplePortionPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const TextSimplePortionPrimitive2D& rCompare = static_cast<const TextSimplePortionPrimitive2D&>(rPrimitive); + + return (getTextTransform() == rCompare.getTextTransform() + && getText() == rCompare.getText() + && getTextPosition() == rCompare.getTextPosition() + && getTextLength() == rCompare.getTextLength() + && getDXArray() == rCompare.getDXArray() + && getFontAttribute() == rCompare.getFontAttribute() + && LocalesAreEqual(getLocale(), rCompare.getLocale()) + && getFontColor() == rCompare.getFontColor() + && mbFilled == rCompare.mbFilled + && mnWidthToFill == rCompare.mnWidthToFill + && maTextFillColor == rCompare.maTextFillColor); + } + + return false; + } + + basegfx::B2DRange TextSimplePortionPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + if(maB2DRange.isEmpty() && getTextLength()) + { + // get TextBoundRect as base size + // decompose object transformation to single values + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + + if(getTextTransform().decompose(aScale, aTranslate, fRotate, fShearX)) + { + // for the TextLayouterDevice, it is necessary to have a scaling representing + // the font size. Since we want to extract polygons here, it is okay to + // work just with scaling and to ignore shear, rotation and translation, + // all that can be applied to the polygons later + const basegfx::B2DVector aFontScale(getCorrectedScaleAndFontScale(aScale)); + + // prepare textlayoutdevice + TextLayouterDevice aTextLayouter; + aTextLayouter.setFontAttribute( + getFontAttribute(), + aFontScale.getX(), + aFontScale.getY(), + getLocale()); + + // get basic text range + basegfx::B2DRange aNewRange(aTextLayouter.getTextBoundRect(getText(), getTextPosition(), getTextLength())); + + // #i104432#, #i102556# take empty results into account + if(!aNewRange.isEmpty()) + { + // prepare object transformation for range + const basegfx::B2DHomMatrix aRangeTransformation(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( + aScale, fShearX, fRotate, aTranslate)); + + // apply range transformation to it + aNewRange.transform(aRangeTransformation); + + // assign to buffered value + const_cast< TextSimplePortionPrimitive2D* >(this)->maB2DRange = aNewRange; + } + } + } + + return maB2DRange; + } + + // provide unique ID + ImplPrimitive2DIDBlock(TextSimplePortionPrimitive2D, PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/textstrikeoutprimitive2d.cxx b/drawinglayer/source/primitive2d/textstrikeoutprimitive2d.cxx new file mode 100644 index 000000000..23b0c5ffe --- /dev/null +++ b/drawinglayer/source/primitive2d/textstrikeoutprimitive2d.cxx @@ -0,0 +1,255 @@ +/* -*- 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 <primitive2d/textstrikeoutprimitive2d.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <drawinglayer/attribute/lineattribute.hxx> +#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <rtl/ustrbuf.hxx> + + +namespace drawinglayer::primitive2d +{ + BaseTextStrikeoutPrimitive2D::BaseTextStrikeoutPrimitive2D( + const basegfx::B2DHomMatrix& rObjectTransformation, + double fWidth, + const basegfx::BColor& rFontColor) + : BufferedDecompositionPrimitive2D(), + maObjectTransformation(rObjectTransformation), + mfWidth(fWidth), + maFontColor(rFontColor) + { + } + + bool BaseTextStrikeoutPrimitive2D::operator==( const BasePrimitive2D& rPrimitive ) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const BaseTextStrikeoutPrimitive2D& rCompare = static_cast<const BaseTextStrikeoutPrimitive2D&>(rPrimitive); + + return (getObjectTransformation() == rCompare.getObjectTransformation() + && getWidth() == rCompare.getWidth() + && getFontColor() == rCompare.getFontColor()); + } + + return false; + } + + + void TextCharacterStrikeoutPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // strikeout with character + const OUString aSingleCharString(getStrikeoutChar()); + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + + // get decomposition + getObjectTransformation().decompose(aScale, aTranslate, fRotate, fShearX); + + // prepare TextLayouter + TextLayouterDevice aTextLayouter; + + aTextLayouter.setFontAttribute( + getFontAttribute(), + aScale.getX(), + aScale.getY(), + getLocale()); + + const double fStrikeCharWidth(aTextLayouter.getTextWidth(aSingleCharString, 0, 1)); + const double fStrikeCharCount(fabs(getWidth()/fStrikeCharWidth)); + const sal_uInt32 nStrikeCharCount(static_cast< sal_uInt32 >(fStrikeCharCount + 0.5)); + std::vector<double> aDXArray(nStrikeCharCount); + OUStringBuffer aStrikeoutString; + + for(sal_uInt32 a(0); a < nStrikeCharCount; a++) + { + aStrikeoutString.append(aSingleCharString); + aDXArray[a] = (a + 1) * fStrikeCharWidth; + } + + auto len = aStrikeoutString.getLength(); + rContainer.push_back( + new TextSimplePortionPrimitive2D( + getObjectTransformation(), + aStrikeoutString.makeStringAndClear(), + 0, + len, + aDXArray, + getFontAttribute(), + getLocale(), + getFontColor())); + } + + TextCharacterStrikeoutPrimitive2D::TextCharacterStrikeoutPrimitive2D( + const basegfx::B2DHomMatrix& rObjectTransformation, + double fWidth, + const basegfx::BColor& rFontColor, + sal_Unicode aStrikeoutChar, + const attribute::FontAttribute& rFontAttribute, + const css::lang::Locale& rLocale) + : BaseTextStrikeoutPrimitive2D(rObjectTransformation, fWidth, rFontColor), + maStrikeoutChar(aStrikeoutChar), + maFontAttribute(rFontAttribute), + maLocale(rLocale) + { + } + + bool TextCharacterStrikeoutPrimitive2D::operator==( const BasePrimitive2D& rPrimitive ) const + { + if(BaseTextStrikeoutPrimitive2D::operator==(rPrimitive)) + { + const TextCharacterStrikeoutPrimitive2D& rCompare = static_cast<const TextCharacterStrikeoutPrimitive2D&>(rPrimitive); + + return (getStrikeoutChar() == rCompare.getStrikeoutChar() + && getFontAttribute() == rCompare.getFontAttribute() + && LocalesAreEqual(getLocale(), rCompare.getLocale())); + } + + return false; + } + + // provide unique ID + ImplPrimitive2DIDBlock(TextCharacterStrikeoutPrimitive2D, PRIMITIVE2D_ID_TEXTCHARACTERSTRIKEOUTPRIMITIVE2D) + + + + void TextGeometryStrikeoutPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + OSL_ENSURE(TEXT_STRIKEOUT_SLASH != getTextStrikeout() && TEXT_STRIKEOUT_X != getTextStrikeout(), + "Wrong TEXT_STRIKEOUT type; a TextCharacterStrikeoutPrimitive2D should be used (!)"); + + // strikeout with geometry + double fStrikeoutHeight(getHeight()); + double fStrikeoutOffset(getOffset()); + bool bDoubleLine(false); + + // get decomposition + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + getObjectTransformation().decompose(aScale, aTranslate, fRotate, fShearX); + + // set line attribute + switch(getTextStrikeout()) + { + default : // case primitive2d::TEXT_STRIKEOUT_SINGLE: + { + break; + } + case primitive2d::TEXT_STRIKEOUT_DOUBLE: + { + bDoubleLine = true; + break; + } + case primitive2d::TEXT_STRIKEOUT_BOLD: + { + fStrikeoutHeight *= 2.0; + break; + } + } + + if(bDoubleLine) + { + fStrikeoutOffset -= 0.50 * fStrikeoutHeight; + fStrikeoutHeight *= 0.64; + } + + // create base polygon and new primitive + basegfx::B2DPolygon aStrikeoutLine; + + aStrikeoutLine.append(basegfx::B2DPoint(0.0, -fStrikeoutOffset)); + aStrikeoutLine.append(basegfx::B2DPoint(getWidth(), -fStrikeoutOffset)); + + const basegfx::B2DHomMatrix aUnscaledTransform( + basegfx::utils::createShearXRotateTranslateB2DHomMatrix( + fShearX, fRotate, aTranslate)); + + aStrikeoutLine.transform(aUnscaledTransform); + + // add primitive + const attribute::LineAttribute aLineAttribute(getFontColor(), fStrikeoutHeight, basegfx::B2DLineJoin::NONE); + Primitive2DContainer xRetval(1); + xRetval[0] = new PolygonStrokePrimitive2D(aStrikeoutLine, aLineAttribute); + + if(bDoubleLine) + { + // double line, create 2nd primitive with offset using TransformPrimitive based on + // already created NewPrimitive + const double fLineDist(2.0 * fStrikeoutHeight); + + // move base point of text to 0.0 and de-rotate + basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix( + -aTranslate.getX(), -aTranslate.getY())); + aTransform.rotate(-fRotate); + + // translate in Y by offset + aTransform.translate(0.0, -fLineDist); + + // move back and rotate + aTransform.rotate(fRotate); + aTransform.translate(aTranslate.getX(), aTranslate.getY()); + + // add transform primitive + xRetval.push_back( + new TransformPrimitive2D( + aTransform, + xRetval)); + } + + rContainer.insert(rContainer.end(), xRetval.begin(), xRetval.end()); + } + + TextGeometryStrikeoutPrimitive2D::TextGeometryStrikeoutPrimitive2D( + const basegfx::B2DHomMatrix& rObjectTransformation, + double fWidth, + const basegfx::BColor& rFontColor, + double fHeight, + double fOffset, + TextStrikeout eTextStrikeout) + : BaseTextStrikeoutPrimitive2D(rObjectTransformation, fWidth, rFontColor), + mfHeight(fHeight), + mfOffset(fOffset), + meTextStrikeout(eTextStrikeout) + { + } + + bool TextGeometryStrikeoutPrimitive2D::operator==( const BasePrimitive2D& rPrimitive ) const + { + if(BaseTextStrikeoutPrimitive2D::operator==(rPrimitive)) + { + const TextGeometryStrikeoutPrimitive2D& rCompare = static_cast<const TextGeometryStrikeoutPrimitive2D&>(rPrimitive); + + return (getHeight() == rCompare.getHeight() + && getOffset() == rCompare.getOffset() + && getTextStrikeout() == rCompare.getTextStrikeout()); + } + + return false; + } + + // provide unique ID + ImplPrimitive2DIDBlock(TextGeometryStrikeoutPrimitive2D, PRIMITIVE2D_ID_TEXTGEOMETRYSTRIKEOUTPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/transformprimitive2d.cxx b/drawinglayer/source/primitive2d/transformprimitive2d.cxx new file mode 100644 index 000000000..4ac2ada55 --- /dev/null +++ b/drawinglayer/source/primitive2d/transformprimitive2d.cxx @@ -0,0 +1,61 @@ +/* -*- 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/transformprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + TransformPrimitive2D::TransformPrimitive2D( + const basegfx::B2DHomMatrix& rTransformation, + const Primitive2DContainer& rChildren) + : GroupPrimitive2D(rChildren), + maTransformation(rTransformation) + { + } + + bool TransformPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const TransformPrimitive2D& rCompare = static_cast< const TransformPrimitive2D& >(rPrimitive); + + return (getTransformation() == rCompare.getTransformation()); + } + + return false; + } + + basegfx::B2DRange TransformPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + basegfx::B2DRange aRetval(getChildren().getB2DRange(rViewInformation)); + aRetval.transform(getTransformation()); + return aRetval; + } + + // provide unique ID + ImplPrimitive2DIDBlock(TransformPrimitive2D, PRIMITIVE2D_ID_TRANSFORMPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/transparenceprimitive2d.cxx b/drawinglayer/source/primitive2d/transparenceprimitive2d.cxx new file mode 100644 index 000000000..da157d353 --- /dev/null +++ b/drawinglayer/source/primitive2d/transparenceprimitive2d.cxx @@ -0,0 +1,54 @@ +/* -*- 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/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + TransparencePrimitive2D::TransparencePrimitive2D( + const Primitive2DContainer& rChildren, + const Primitive2DContainer& rTransparence) + : GroupPrimitive2D(rChildren), + maTransparence(rTransparence) + { + } + + bool TransparencePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const TransparencePrimitive2D& rCompare = static_cast<const TransparencePrimitive2D&>(rPrimitive); + + return (getTransparence() == rCompare.getTransparence()); + } + + return false; + } + + // provide unique ID + ImplPrimitive2DIDBlock(TransparencePrimitive2D, PRIMITIVE2D_ID_TRANSPARENCEPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/unifiedtransparenceprimitive2d.cxx b/drawinglayer/source/primitive2d/unifiedtransparenceprimitive2d.cxx new file mode 100644 index 000000000..70cfe6750 --- /dev/null +++ b/drawinglayer/source/primitive2d/unifiedtransparenceprimitive2d.cxx @@ -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 . + */ + +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/color/bcolor.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> + + +using namespace com::sun::star; + + +namespace drawinglayer::primitive2d +{ + UnifiedTransparencePrimitive2D::UnifiedTransparencePrimitive2D( + const Primitive2DContainer& rChildren, + double fTransparence) + : GroupPrimitive2D(rChildren), + mfTransparence(fTransparence) + { + } + + bool UnifiedTransparencePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(GroupPrimitive2D::operator==(rPrimitive)) + { + const UnifiedTransparencePrimitive2D& rCompare = static_cast<const UnifiedTransparencePrimitive2D&>(rPrimitive); + + return (getTransparence() == rCompare.getTransparence()); + } + + return false; + } + + basegfx::B2DRange UnifiedTransparencePrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const + { + // do not use the fallback to decomposition here since for a correct BoundRect we also + // need invisible (1.0 == getTransparence()) geometry; these would be deleted in the decomposition + return getChildren().getB2DRange( rViewInformation); + } + + void UnifiedTransparencePrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor& rVisitor, const geometry::ViewInformation2D& rViewInformation) const + { + if(0.0 == getTransparence()) + { + // no transparence used, so just use the content + getChildren(rVisitor); + } + else if(getTransparence() > 0.0 && getTransparence() < 1.0) + { + // The idea is to create a TransparencePrimitive2D with transparent content using a fill color + // corresponding to the transparence value. Problem is that in most systems, the right + // and bottom pixel array is not filled when filling polygons, thus this would not + // always produce a complete transparent bitmap. There are some solutions: + + // - Grow the used polygon range by one discrete unit in X and Y. This + // will make the decomposition view-dependent. + + // - For all filled polygon renderings, draw the polygon outline extra. This + // would lead to unwanted side effects when using concatenated polygons. + + // - At this decomposition, add a filled polygon and a hairline polygon. This + // solution stays view-independent. + + // I will take the last one here. The small overhead of two primitives will only be + // used when UnifiedTransparencePrimitive2D is not handled directly. + const basegfx::B2DRange aPolygonRange(getChildren().getB2DRange(rViewInformation)); + const basegfx::B2DPolygon aPolygon(basegfx::utils::createPolygonFromRect(aPolygonRange)); + const basegfx::BColor aGray(getTransparence(), getTransparence(), getTransparence()); + Primitive2DContainer aTransparenceContent(2); + + aTransparenceContent[0] = Primitive2DReference(new PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPolygon), aGray)); + aTransparenceContent[1] = Primitive2DReference(new PolygonHairlinePrimitive2D(aPolygon, aGray)); + + // create sub-transparence group with a gray-colored rectangular fill polygon + rVisitor.append(new TransparencePrimitive2D(getChildren(), aTransparenceContent)); + } + else + { + // completely transparent or invalid definition, add nothing + } + } + + // provide unique ID + ImplPrimitive2DIDBlock(UnifiedTransparencePrimitive2D, PRIMITIVE2D_ID_UNIFIEDTRANSPARENCEPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/wallpaperprimitive2d.cxx b/drawinglayer/source/primitive2d/wallpaperprimitive2d.cxx new file mode 100644 index 000000000..d8926824f --- /dev/null +++ b/drawinglayer/source/primitive2d/wallpaperprimitive2d.cxx @@ -0,0 +1,255 @@ +/* -*- 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 <primitive2d/wallpaperprimitive2d.hxx> +#include <drawinglayer/primitive2d/bitmapprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/primitive2d/maskprimitive2d.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <vcl/graph.hxx> +#include <toolkit/helper/vclunohelper.hxx> + + +namespace drawinglayer::primitive2d +{ + void WallpaperBitmapPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + Primitive2DReference aRetval; + + if(!getLocalObjectRange().isEmpty() && !getBitmapEx().IsEmpty()) + { + // get bitmap PIXEL size + const Size& rPixelSize = getBitmapEx().GetSizePixel(); + + if(rPixelSize.Width() > 0 && rPixelSize.Height() > 0) + { + if(WallpaperStyle::Scale == getWallpaperStyle()) + { + // shortcut for scale; use simple BitmapPrimitive2D + basegfx::B2DHomMatrix aObjectTransform; + + aObjectTransform.set(0, 0, getLocalObjectRange().getWidth()); + aObjectTransform.set(1, 1, getLocalObjectRange().getHeight()); + aObjectTransform.set(0, 2, getLocalObjectRange().getMinX()); + aObjectTransform.set(1, 2, getLocalObjectRange().getMinY()); + + Primitive2DReference xReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getBitmapEx()), + aObjectTransform)); + + aRetval = xReference; + } + else + { + // transform to logic size + basegfx::B2DHomMatrix aInverseViewTransformation(getViewTransformation()); + aInverseViewTransformation.invert(); + basegfx::B2DVector aLogicSize(rPixelSize.Width(), rPixelSize.Height()); + aLogicSize = aInverseViewTransformation * aLogicSize; + + // apply layout + basegfx::B2DPoint aTargetTopLeft(getLocalObjectRange().getMinimum()); + bool bUseTargetTopLeft(true); + bool bNeedsClipping(false); + + switch(getWallpaperStyle()) + { + default: //case WallpaperStyle::Tile :, also WallpaperStyle::NONE and WallpaperStyle::ApplicationGradient + { + bUseTargetTopLeft = false; + break; + } + case WallpaperStyle::Scale : + { + // handled by shortcut above + break; + } + case WallpaperStyle::TopLeft : + { + // nothing to do + break; + } + case WallpaperStyle::Top : + { + const basegfx::B2DPoint aCenter(getLocalObjectRange().getCenter()); + aTargetTopLeft.setX(aCenter.getX() - (aLogicSize.getX() * 0.5)); + break; + } + case WallpaperStyle::TopRight : + { + aTargetTopLeft.setX(getLocalObjectRange().getMaxX() - aLogicSize.getX()); + break; + } + case WallpaperStyle::Left : + { + const basegfx::B2DPoint aCenter(getLocalObjectRange().getCenter()); + aTargetTopLeft.setY(aCenter.getY() - (aLogicSize.getY() * 0.5)); + break; + } + case WallpaperStyle::Center : + { + const basegfx::B2DPoint aCenter(getLocalObjectRange().getCenter()); + aTargetTopLeft = aCenter - (aLogicSize * 0.5); + break; + } + case WallpaperStyle::Right : + { + const basegfx::B2DPoint aCenter(getLocalObjectRange().getCenter()); + aTargetTopLeft.setX(getLocalObjectRange().getMaxX() - aLogicSize.getX()); + aTargetTopLeft.setY(aCenter.getY() - (aLogicSize.getY() * 0.5)); + break; + } + case WallpaperStyle::BottomLeft : + { + aTargetTopLeft.setY(getLocalObjectRange().getMaxY() - aLogicSize.getY()); + break; + } + case WallpaperStyle::Bottom : + { + const basegfx::B2DPoint aCenter(getLocalObjectRange().getCenter()); + aTargetTopLeft.setX(aCenter.getX() - (aLogicSize.getX() * 0.5)); + aTargetTopLeft.setY(getLocalObjectRange().getMaxY() - aLogicSize.getY()); + break; + } + case WallpaperStyle::BottomRight : + { + aTargetTopLeft = getLocalObjectRange().getMaximum() - aLogicSize; + break; + } + } + + if(bUseTargetTopLeft) + { + // fill target range + const basegfx::B2DRange aTargetRange(aTargetTopLeft, aTargetTopLeft + aLogicSize); + + // create aligned, single BitmapPrimitive2D + basegfx::B2DHomMatrix aObjectTransform; + + aObjectTransform.set(0, 0, aTargetRange.getWidth()); + aObjectTransform.set(1, 1, aTargetRange.getHeight()); + aObjectTransform.set(0, 2, aTargetRange.getMinX()); + aObjectTransform.set(1, 2, aTargetRange.getMinY()); + + Primitive2DReference xReference( + new BitmapPrimitive2D( + VCLUnoHelper::CreateVCLXBitmap(getBitmapEx()), + aObjectTransform)); + aRetval = xReference; + + // clip when not completely inside object range + bNeedsClipping = !getLocalObjectRange().isInside(aTargetRange); + } + else + { + // WallpaperStyle::Tile, WallpaperStyle::NONE, WallpaperStyle::ApplicationGradient + // convert to relative positions + const basegfx::B2DVector aRelativeSize( + aLogicSize.getX() / (getLocalObjectRange().getWidth() ? getLocalObjectRange().getWidth() : 1.0), + aLogicSize.getY() / (getLocalObjectRange().getHeight() ? getLocalObjectRange().getHeight() : 1.0)); + basegfx::B2DPoint aRelativeTopLeft(0.0, 0.0); + + if(WallpaperStyle::Tile != getWallpaperStyle()) + { + aRelativeTopLeft.setX(0.5 - aRelativeSize.getX()); + aRelativeTopLeft.setY(0.5 - aRelativeSize.getY()); + } + + // prepare FillGraphicAttribute + const attribute::FillGraphicAttribute aFillGraphicAttribute( + Graphic(getBitmapEx()), + basegfx::B2DRange(aRelativeTopLeft, aRelativeTopLeft+ aRelativeSize), + true); + + // create ObjectTransform + const basegfx::B2DHomMatrix aObjectTransform( + basegfx::utils::createScaleTranslateB2DHomMatrix( + getLocalObjectRange().getRange(), + getLocalObjectRange().getMinimum())); + + // create FillBitmapPrimitive + const drawinglayer::primitive2d::Primitive2DReference xFillBitmap( + new drawinglayer::primitive2d::FillGraphicPrimitive2D( + aObjectTransform, + aFillGraphicAttribute)); + aRetval = xFillBitmap; + + // always embed tiled fill to clipping + bNeedsClipping = true; + } + + if(bNeedsClipping) + { + // embed to clipping; this is necessary for tiled fills + const basegfx::B2DPolyPolygon aPolyPolygon( + basegfx::utils::createPolygonFromRect(getLocalObjectRange())); + const drawinglayer::primitive2d::Primitive2DReference xClippedFill( + new drawinglayer::primitive2d::MaskPrimitive2D( + aPolyPolygon, + { aRetval })); + aRetval = xClippedFill; + } + } + } + } + + if (aRetval.is()) + rContainer.push_back(aRetval); + } + + WallpaperBitmapPrimitive2D::WallpaperBitmapPrimitive2D( + const basegfx::B2DRange& rObjectRange, + const BitmapEx& rBitmapEx, + WallpaperStyle eWallpaperStyle) + : ViewTransformationDependentPrimitive2D(), + maObjectRange(rObjectRange), + maBitmapEx(rBitmapEx), + meWallpaperStyle(eWallpaperStyle) + { + } + + bool WallpaperBitmapPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(ViewTransformationDependentPrimitive2D::operator==(rPrimitive)) + { + const WallpaperBitmapPrimitive2D& rCompare = static_cast<const WallpaperBitmapPrimitive2D&>(rPrimitive); + + return (getLocalObjectRange() == rCompare.getLocalObjectRange() + && getBitmapEx() == rCompare.getBitmapEx() + && getWallpaperStyle() == rCompare.getWallpaperStyle()); + } + + return false; + } + + basegfx::B2DRange WallpaperBitmapPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const + { + return getLocalObjectRange(); + } + + // provide unique ID + ImplPrimitive2DIDBlock(WallpaperBitmapPrimitive2D, PRIMITIVE2D_ID_WALLPAPERBITMAPPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/drawinglayer/source/primitive2d/wrongspellprimitive2d.cxx b/drawinglayer/source/primitive2d/wrongspellprimitive2d.cxx new file mode 100644 index 000000000..f0eb91833 --- /dev/null +++ b/drawinglayer/source/primitive2d/wrongspellprimitive2d.cxx @@ -0,0 +1,103 @@ +/* -*- 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/wrongspellprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <drawinglayer/primitive2d/polygonprimitive2d.hxx> +#include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> + + +namespace drawinglayer::primitive2d +{ + void WrongSpellPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const + { + // ATM this decompose is view-independent, what the original VCL-Display is not. To mimic + // the old behaviour here if wanted it is necessary to add get2DDecomposition and implement + // it similar to the usage in e.g. HelplinePrimitive2D. Remembering the ViewTransformation + // should be enough then. + // The view-independent wavelines work well (if You ask me). Maybe the old VCL-Behaviour is only + // in place because it was not possible/too expensive at that time to scale the wavelines with the + // view... + // With the VCL-PixelRenderer this will not even be used since it implements WrongSpellPrimitive2D + // directly and mimics the old VCL-Display there. If You implemented a new renderer without + // direct WrongSpellPrimitive2D support, You may want to do the described change here. + + // get the font height (part of scale), so decompose the matrix + basegfx::B2DVector aScale, aTranslate; + double fRotate, fShearX; + getTransformation().decompose(aScale, aTranslate, fRotate, fShearX); + + // calculate distances based on a static default (to allow testing in debugger) + static const double fDefaultDistance(0.03); + const double fFontHeight(aScale.getY()); + const double fUnderlineDistance(fFontHeight * fDefaultDistance); + const double fWaveWidth(2.0 * fUnderlineDistance); + + // the Y-distance needs to be relative to FontHeight since the points get + // transformed with the transformation containing that scale already. + const double fRelativeUnderlineDistance(basegfx::fTools::equalZero(aScale.getY()) ? 0.0 : fUnderlineDistance / aScale.getY()); + basegfx::B2DPoint aStart(getStart(), fRelativeUnderlineDistance); + basegfx::B2DPoint aStop(getStop(), fRelativeUnderlineDistance); + basegfx::B2DPolygon aPolygon; + + aPolygon.append(getTransformation() * aStart); + aPolygon.append(getTransformation() * aStop); + + // prepare line attribute + const attribute::LineAttribute aLineAttribute(getColor()); + + // create the waveline primitive + rContainer.push_back(new PolygonWavePrimitive2D(aPolygon, aLineAttribute, fWaveWidth, 0.5 * fWaveWidth)); + } + + WrongSpellPrimitive2D::WrongSpellPrimitive2D( + const basegfx::B2DHomMatrix& rTransformation, + double fStart, + double fStop, + const basegfx::BColor& rColor) + : BufferedDecompositionPrimitive2D(), + maTransformation(rTransformation), + mfStart(fStart), + mfStop(fStop), + maColor(rColor) + { + } + + bool WrongSpellPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const + { + if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) + { + const WrongSpellPrimitive2D& rCompare = static_cast<const WrongSpellPrimitive2D&>(rPrimitive); + + return (getTransformation() == rCompare.getTransformation() + && getStart() == rCompare.getStart() + && getStop() == rCompare.getStop() + && getColor() == rCompare.getColor()); + } + + return false; + } + + // provide unique ID + ImplPrimitive2DIDBlock(WrongSpellPrimitive2D, PRIMITIVE2D_ID_WRONGSPELLPRIMITIVE2D) + +} // end of namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |