1
0
Fork 0
libreoffice/svx/source/sdr/primitive2d/sdrdecompositiontools.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

962 lines
42 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <sdr/primitive2d/sdrdecompositiontools.hxx>
#include <sdr/primitive2d/sdrcellprimitive.hxx>
#include <drawinglayer/primitive2d/baseprimitive2d.hxx>
#include <drawinglayer/primitive2d/PolygonStrokeArrowPrimitive2D.hxx>
#include <drawinglayer/primitive2d/PolyPolygonGradientPrimitive2D.hxx>
#include <drawinglayer/primitive2d/PolyPolygonHatchPrimitive2D.hxx>
#include <drawinglayer/primitive2d/PolyPolygonGraphicPrimitive2D.hxx>
#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
#include <drawinglayer/primitive2d/softedgeprimitive2d.hxx>
#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx>
#include <drawinglayer/primitive2d/transparenceprimitive2d.hxx>
#include <drawinglayer/primitive2d/PolyPolygonRGBAPrimitive2D.hxx>
#include <drawinglayer/primitive2d/PolyPolygonAlphaGradientPrimitive2D.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <drawinglayer/primitive2d/fillgradientprimitive2d.hxx>
#include <drawinglayer/attribute/strokeattribute.hxx>
#include <drawinglayer/attribute/linestartendattribute.hxx>
#include <drawinglayer/attribute/sdrfillgraphicattribute.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <drawinglayer/primitive2d/shadowprimitive2d.hxx>
#include <sdr/attribute/sdrtextattribute.hxx>
#include <drawinglayer/primitive2d/glowprimitive2d.hxx>
#include <sdr/primitive2d/sdrtextprimitive2d.hxx>
#include <svx/svdotext.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <drawinglayer/primitive2d/animatedprimitive2d.hxx>
#include <drawinglayer/animation/animationtiming.hxx>
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
#include <drawinglayer/geometry/viewinformation2d.hxx>
#include <drawinglayer/primitive2d/texthierarchyprimitive2d.hxx>
#include <drawinglayer/attribute/sdrfillattribute.hxx>
#include <drawinglayer/attribute/sdrlineattribute.hxx>
#include <drawinglayer/attribute/sdrlinestartendattribute.hxx>
#include <drawinglayer/attribute/sdrshadowattribute.hxx>
#include <drawinglayer/attribute/sdrglowattribute.hxx>
#include <drawinglayer/attribute/sdrglowtextattribute.hxx>
#include <docmodel/theme/FormatScheme.hxx>
#include <osl/diagnose.h>
// for SlideBackgroundFillPrimitive2D
#include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
#include <svx/unoapi.hxx>
#include <svx/svdpage.hxx>
#include <sdr/primitive2d/sdrattributecreator.hxx>
#include <sdr/contact/viewcontactofmasterpagedescriptor.hxx>
using namespace com::sun::star;
namespace drawinglayer::primitive2d
{
namespace
{
/// @returns the offset to apply/unapply to scale according to correct origin for a given alignment.
basegfx::B2DTuple getShadowScaleOriginOffset(const basegfx::B2DTuple& aScale,
model::RectangleAlignment eAlignment)
{
switch (eAlignment)
{
case model::RectangleAlignment::TopLeft:
return { 0, 0 };
case model::RectangleAlignment::Top:
return { aScale.getX() / 2, 0 };
case model::RectangleAlignment::TopRight:
return { aScale.getX(), 0 };
case model::RectangleAlignment::Left:
return { 0, aScale.getY() / 2 };
case model::RectangleAlignment::Center:
return { aScale.getX() / 2, aScale.getY() / 2 };
case model::RectangleAlignment::Right:
return { aScale.getX(), aScale.getY() / 2 };
case model::RectangleAlignment::BottomLeft:
return { 0, aScale.getY() };
case model::RectangleAlignment::Bottom:
return { aScale.getX() / 2, aScale.getY() };
case model::RectangleAlignment::BottomRight:
return { aScale.getX(), aScale.getY() };
default:
return { 0, 0 };
}
};
// See also: SdrTextObj::AdjustRectToTextDistance
basegfx::B2DRange getTextAnchorRange(const attribute::SdrTextAttribute& rText,
const basegfx::B2DRange& rSnapRange)
{
// Take vertical text orientation into account when deciding
// which dimension is its width, and which is its height
const OutlinerParaObject& rOutlinerParaObj = rText.getOutlinerParaObject();
const bool bVerticalWriting(rOutlinerParaObj.IsEffectivelyVertical());
const double fWidthForText = bVerticalWriting ? rSnapRange.getHeight() : rSnapRange.getWidth();
// create a range describing the wanted text position and size (aTextAnchorRange). This
// means to use the text distance values here
// If the margin is larger than the entire width of the text area, then limit the
// margin.
const double fTextLeftDistance
= std::min(static_cast<double>(rText.getTextLeftDistance()), fWidthForText);
const double nTextRightDistance
= std::min(static_cast<double>(rText.getTextRightDistance()), fWidthForText);
double fDistanceForTextL, fDistanceForTextT, fDistanceForTextR, fDistanceForTextB;
if (!bVerticalWriting)
{
fDistanceForTextL = fTextLeftDistance;
fDistanceForTextT = rText.getTextUpperDistance();
fDistanceForTextR = nTextRightDistance;
fDistanceForTextB = rText.getTextLowerDistance();
}
else if (rOutlinerParaObj.IsTopToBottom())
{
fDistanceForTextL = rText.getTextLowerDistance();
fDistanceForTextT = fTextLeftDistance;
fDistanceForTextR = rText.getTextUpperDistance();
fDistanceForTextB = nTextRightDistance;
}
else
{
fDistanceForTextL = rText.getTextUpperDistance();
fDistanceForTextT = nTextRightDistance;
fDistanceForTextR = rText.getTextLowerDistance();
fDistanceForTextB = fTextLeftDistance;
}
const basegfx::B2DPoint aTopLeft(rSnapRange.getMinX() + fDistanceForTextL,
rSnapRange.getMinY() + fDistanceForTextT);
const basegfx::B2DPoint aBottomRight(rSnapRange.getMaxX() - fDistanceForTextR,
rSnapRange.getMaxY() - fDistanceForTextB);
basegfx::B2DRange aAnchorRange;
aAnchorRange.expand(aTopLeft);
aAnchorRange.expand(aBottomRight);
// If the shape has no width, then don't attempt to break the text into multiple
// lines, not a single character would satisfy a zero width requirement.
// SdrTextObj::impDecomposeBlockTextPrimitive() uses the same constant to
// effectively set no limits.
if (!bVerticalWriting && aAnchorRange.getWidth() == 0)
{
aAnchorRange.expand(basegfx::B2DPoint(aTopLeft.getX() - 1000000, aTopLeft.getY()));
aAnchorRange.expand(basegfx::B2DPoint(aBottomRight.getX() + 1000000, aBottomRight.getY()));
}
else if (bVerticalWriting && aAnchorRange.getHeight() == 0)
{
aAnchorRange.expand(basegfx::B2DPoint(aTopLeft.getX(), aTopLeft.getY() - 1000000));
aAnchorRange.expand(basegfx::B2DPoint(aBottomRight.getX(), aBottomRight.getY() + 1000000));
}
return aAnchorRange;
}
drawinglayer::attribute::SdrFillAttribute getMasterPageFillAttribute(
const geometry::ViewInformation2D& rViewInformation,
basegfx::B2DVector& rPageSize)
{
drawinglayer::attribute::SdrFillAttribute aRetval;
// get SdrPage
const SdrPage* pVisualizedPage(GetSdrPageFromXDrawPage(rViewInformation.getVisualizedPage()));
if(nullptr != pVisualizedPage)
{
// copy needed values for further processing
rPageSize.setX(pVisualizedPage->GetWidth());
rPageSize.setY(pVisualizedPage->GetHeight());
if(pVisualizedPage->IsMasterPage())
{
// the page is a MasterPage, so we are in MasterPage view
// still need #i110846#, see ViewContactOfMasterPage
if(pVisualizedPage->getSdrPageProperties().GetStyleSheet())
{
// create page fill attributes with correct properties
aRetval = drawinglayer::primitive2d::createNewSdrFillAttribute(
pVisualizedPage->getSdrPageProperties().GetItemSet());
}
}
else
{
// the page is *no* MasterPage, we are in normal Page view, get the MasterPage
if(pVisualizedPage->TRG_HasMasterPage())
{
sdr::contact::ViewContact& rVC(pVisualizedPage->TRG_GetMasterPageDescriptorViewContact());
sdr::contact::ViewContactOfMasterPageDescriptor* pVCOMPD(
dynamic_cast<sdr::contact::ViewContactOfMasterPageDescriptor*>(&rVC));
if(nullptr != pVCOMPD)
{
// in this case the still needed #i110846# is part of
// getCorrectSdrPageProperties, that's the main reason to re-use
// that call/functionality here
const SdrPageProperties* pCorrectProperties(
pVCOMPD->GetMasterPageDescriptor().getCorrectSdrPageProperties());
if(pCorrectProperties)
{
// create page fill attributes when correct properties were identified
aRetval = drawinglayer::primitive2d::createNewSdrFillAttribute(
pCorrectProperties->GetItemSet());
}
}
}
}
}
return aRetval;
}
// provide a Primitive2D for the SlideBackgroundFill-mode. It is capable
// of expressing that state of fill and it's decomposition implements all
// needed preparation of the geometry in an isolated and controllable
// space and way.
// It is currently simple buffered (due to being derived from
// BufferedDecompositionPrimitive2D) and detects if FillStyle changes
class SlideBackgroundFillPrimitive2D final : public BufferedDecompositionPrimitive2D
{
private:
/// the basegfx::B2DPolyPolygon geometry
basegfx::B2DPolyPolygon maPolyPolygon;
/// the last SdrFillAttribute the geometry was created for
drawinglayer::attribute::SdrFillAttribute maLastFill;
protected:
// create decomposition data
virtual Primitive2DReference create2DDecomposition(
const geometry::ViewInformation2D& rViewInformation) const override;
public:
/// constructor
SlideBackgroundFillPrimitive2D(
const basegfx::B2DPolyPolygon& rPolyPolygon);
/// check existing decomposition data, call parent
virtual void get2DDecomposition(
Primitive2DDecompositionVisitor& rVisitor,
const geometry::ViewInformation2D& rViewInformation) const override;
/// data read access
const basegfx::B2DPolyPolygon& getB2DPolyPolygon() const { return maPolyPolygon; }
/// compare operator
virtual bool operator==(const BasePrimitive2D& rPrimitive) const override;
/// get range
virtual basegfx::B2DRange getB2DRange(const geometry::ViewInformation2D& rViewInformation) const override;
/// provide unique ID
virtual sal_uInt32 getPrimitive2DID() const override;
};
SlideBackgroundFillPrimitive2D::SlideBackgroundFillPrimitive2D(
const basegfx::B2DPolyPolygon& rPolyPolygon)
: BufferedDecompositionPrimitive2D()
, maPolyPolygon(rPolyPolygon)
, maLastFill()
{
}
Primitive2DReference SlideBackgroundFillPrimitive2D::create2DDecomposition(
const geometry::ViewInformation2D& rViewInformation) const
{
basegfx::B2DVector aPageSize;
// get fill from target Page, this will check for all needed things
// like MasterPage/relationships, etc. (see getMasterPageFillAttribute impl above)
drawinglayer::attribute::SdrFillAttribute aFill(
getMasterPageFillAttribute(rViewInformation, aPageSize));
// if fill is on default (empty), nothing will be shown, we are done
if(aFill.isDefault())
return nullptr;
// Get PolygonRange of own local geometry
const basegfx::B2DRange aPolygonRange(getB2DPolyPolygon().getB2DRange());
// if local geometry is empty, nothing will be shown, we are done
if(aPolygonRange.isEmpty())
return nullptr;
// Get PageRange
const basegfx::B2DRange aPageRange(0.0, 0.0, aPageSize.getX(), aPageSize.getY());
// if local geometry does not overlap with PageRange, nothing will be shown, we are done
if(!aPageRange.overlaps(aPolygonRange))
return nullptr;
// create FillPrimitive2D with the geometry (the PolyPolygon) and
// the page's definitonRange to:
// - on one hand limit to geometry
// - on the other hand allow continuation of fill outside of
// MasterPage's range
const attribute::FillGradientAttribute aEmptyFillTransparenceGradient;
const Primitive2DReference aCreatedFill(
createPolyPolygonFillPrimitive(
getB2DPolyPolygon(), // geometry
aPageRange, // definition range
aFill,
aEmptyFillTransparenceGradient));
return aCreatedFill;
}
void SlideBackgroundFillPrimitive2D::get2DDecomposition(
Primitive2DDecompositionVisitor& rVisitor,
const geometry::ViewInformation2D& rViewInformation) const
{
basegfx::B2DVector aPageSize;
drawinglayer::attribute::SdrFillAttribute aFill;
if(getBuffered2DDecomposition())
{
aFill = getMasterPageFillAttribute(rViewInformation, aPageSize);
if(!(aFill == maLastFill))
{
// conditions of last local decomposition have changed, delete
const_cast< SlideBackgroundFillPrimitive2D* >(this)->setBuffered2DDecomposition(nullptr);
}
}
if(!getBuffered2DDecomposition())
{
// remember last Fill
const_cast< SlideBackgroundFillPrimitive2D* >(this)->maLastFill = std::move(aFill);
}
// use parent implementation
BufferedDecompositionPrimitive2D::get2DDecomposition(rVisitor, rViewInformation);
}
bool SlideBackgroundFillPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
{
if (BufferedDecompositionPrimitive2D::operator==(rPrimitive))
{
const SlideBackgroundFillPrimitive2D& rCompare
= static_cast<const SlideBackgroundFillPrimitive2D&>(rPrimitive);
return getB2DPolyPolygon() == rCompare.getB2DPolyPolygon();
}
return false;
}
basegfx::B2DRange SlideBackgroundFillPrimitive2D::getB2DRange(
const geometry::ViewInformation2D& /*rViewInformation*/) const
{
// return range
return basegfx::utils::getRange(getB2DPolyPolygon());
}
// provide unique ID
sal_uInt32 SlideBackgroundFillPrimitive2D::getPrimitive2DID() const
{
return PRIMITIVE2D_ID_SLIDEBACKGROUNDFILLPRIMITIVE2D;
}
}; // end of anonymous namespace
Primitive2DReference createPolyPolygonFillPrimitive(
const basegfx::B2DPolyPolygon& rPolyPolygon,
const attribute::SdrFillAttribute& rFill,
const attribute::FillGradientAttribute& rAlphaGradient)
{
// when we have no given definition range, use the range of the given geometry
// also for definition (simplest case)
const basegfx::B2DRange aRange(basegfx::utils::getRange(rPolyPolygon));
return createPolyPolygonFillPrimitive(
rPolyPolygon,
aRange,
rFill,
rAlphaGradient);
}
Primitive2DReference createPolyPolygonFillPrimitive(
const basegfx::B2DPolyPolygon& rPolyPolygon,
const basegfx::B2DRange& rDefinitionRange,
const attribute::SdrFillAttribute& rFill,
const attribute::FillGradientAttribute& rAlphaGradient)
{
if(basegfx::fTools::moreOrEqual(rFill.getTransparence(), 1.0))
{
return Primitive2DReference();
}
// prepare access to FillGradientAttribute
const attribute::FillGradientAttribute& rFillGradient(rFill.getGradient());
// prepare fully scaled polygon
rtl::Reference<BasePrimitive2D> pNewFillPrimitive;
if(!rFillGradient.isDefault())
{
const bool bHasTransparency(!basegfx::fTools::equalZero(rFill.getTransparence()));
// note: need to use !bHasTransparency to do the same as below
// where embedding to transparency is done. There, simple transparency
// gets priority over gradient transparency (and none). Thus here only one
// option is used. Note that the implementation of FillGradientPrimitive2D
// and PolyPolygonGradientPrimitive2D do support both alphas being used
const bool bHasCompatibleAlphaGradient(!bHasTransparency
&& !rAlphaGradient.isDefault()
&& rFillGradient.sameDefinitionThanAlpha(rAlphaGradient));
if(bHasTransparency || bHasCompatibleAlphaGradient)
{
// SDPR: check early if we have a gradient and an alpha
// gradient that 'fits' in its geometric definition
// so that it can be rendered as RGBA directly. If yes,
// create it and return early
return new PolyPolygonGradientPrimitive2D(
rPolyPolygon,
rDefinitionRange,
rFillGradient,
bHasCompatibleAlphaGradient ? &rAlphaGradient : nullptr,
bHasTransparency ? rFill.getTransparence() : 0.0);
}
pNewFillPrimitive = new PolyPolygonGradientPrimitive2D(
rPolyPolygon,
rDefinitionRange,
rFillGradient);
}
else if(!rFill.getHatch().isDefault())
{
pNewFillPrimitive = new PolyPolygonHatchPrimitive2D(
rPolyPolygon,
rDefinitionRange,
rFill.getColor(),
rFill.getHatch());
}
else if(!rFill.getFillGraphic().isDefault())
{
// SDPR: check early if we have alpha and add directly
if(0.0 != rFill.getTransparence())
{
return new PolyPolygonGraphicPrimitive2D(
rPolyPolygon,
rDefinitionRange,
rFill.getFillGraphic().createFillGraphicAttribute(rDefinitionRange),
rFill.getTransparence());
}
pNewFillPrimitive = new PolyPolygonGraphicPrimitive2D(
rPolyPolygon,
rDefinitionRange,
rFill.getFillGraphic().createFillGraphicAttribute(rDefinitionRange));
}
else if(rFill.isSlideBackgroundFill())
{
// create needed Primitive2D representation for
// SlideBackgroundFill-mode
pNewFillPrimitive = new SlideBackgroundFillPrimitive2D(
rPolyPolygon);
}
else
{
// SDPR: check early if we have alpha and add directly
if(0.0 != rFill.getTransparence())
{
return new PolyPolygonRGBAPrimitive2D(
rPolyPolygon,
rFill.getColor(),
rFill.getTransparence());
}
// SDPR: check early if we have alpha gradient and add directly
// This may be useful for some SDPRs like Cairo: It can render RGBA
// gradients quick and direct, so it can use polygon color as RGB
// (no real gradient steps) combined with the existing alpha steps
if (!rAlphaGradient.isDefault())
{
return new PolyPolygonAlphaGradientPrimitive2D(
rPolyPolygon,
rFill.getColor(),
rAlphaGradient);
}
return new PolyPolygonColorPrimitive2D(
rPolyPolygon,
rFill.getColor());
}
if(0.0 != rFill.getTransparence())
{
// create simpleTransparencePrimitive, add created fill primitive
Primitive2DContainer aContent { pNewFillPrimitive };
return new UnifiedTransparencePrimitive2D(std::move(aContent), rFill.getTransparence());
}
if(!rAlphaGradient.isDefault())
{
// create sequence with created fill primitive
Primitive2DContainer aContent { pNewFillPrimitive };
// create FillGradientPrimitive2D for transparence and add to new sequence
// fillGradientPrimitive is enough here (compared to PolyPolygonGradientPrimitive2D) since float transparence will be masked anyways
Primitive2DContainer aAlpha {
new FillGradientPrimitive2D(
basegfx::utils::getRange(rPolyPolygon),
rDefinitionRange,
rAlphaGradient)
};
// create TransparencePrimitive2D using alpha and content
return new TransparencePrimitive2D(std::move(aContent), std::move(aAlpha));
}
// add to decomposition
return pNewFillPrimitive;
}
Primitive2DReference createPolygonLinePrimitive(
const basegfx::B2DPolygon& rPolygon,
const attribute::SdrLineAttribute& rLine,
const attribute::SdrLineStartEndAttribute& rStroke)
{
// create line and stroke attribute
const attribute::LineAttribute aLineAttribute(rLine.getColor(), rLine.getWidth(), rLine.getJoin(), rLine.getCap());
attribute::StrokeAttribute aStrokeAttribute(std::vector(rLine.getDotDashArray()), rLine.getFullDotDashLen());
rtl::Reference<BasePrimitive2D> pNewLinePrimitive;
if(!rPolygon.isClosed() && !rStroke.isDefault())
{
attribute::LineStartEndAttribute aStart(rStroke.getStartWidth(), rStroke.getStartPolyPolygon(), rStroke.isStartCentered());
attribute::LineStartEndAttribute aEnd(rStroke.getEndWidth(), rStroke.getEndPolyPolygon(), rStroke.isEndCentered());
// create data
pNewLinePrimitive = new PolygonStrokeArrowPrimitive2D(rPolygon, aLineAttribute, aStrokeAttribute, aStart, aEnd);
}
else
{
// create data
pNewLinePrimitive = new PolygonStrokePrimitive2D(rPolygon, aLineAttribute, std::move(aStrokeAttribute));
}
if(0.0 != rLine.getTransparence())
{
// create simpleTransparencePrimitive, add created fill primitive
Primitive2DContainer aContent { pNewLinePrimitive };
return new UnifiedTransparencePrimitive2D(std::move(aContent), rLine.getTransparence());
}
else
{
// add to decomposition
return pNewLinePrimitive;
}
}
Primitive2DReference createTextPrimitive(
const basegfx::B2DPolyPolygon& rUnitPolyPolygon,
const basegfx::B2DHomMatrix& rObjectTransform,
const attribute::SdrTextAttribute& rText,
const attribute::SdrLineAttribute& rStroke,
bool bCellText,
bool bWordWrap)
{
basegfx::B2DHomMatrix aAnchorTransform(rObjectTransform);
rtl::Reference<SdrTextPrimitive2D> pNew;
if(rText.isContour())
{
// contour text
if(!rStroke.isDefault() && 0.0 != rStroke.getWidth())
{
// take line width into account and shrink contour polygon accordingly
// decompose to get scale
basegfx::B2DVector aScale, aTranslate;
double fRotate, fShearX;
rObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX);
// scale outline to object's size to allow growing with value relative to that size
// and also to keep aspect ratio
basegfx::B2DPolyPolygon aScaledUnitPolyPolygon(rUnitPolyPolygon);
aScaledUnitPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(
fabs(aScale.getX()), fabs(aScale.getY())));
// grow the polygon. To shrink, use negative value (half width)
aScaledUnitPolyPolygon = basegfx::utils::growInNormalDirection(aScaledUnitPolyPolygon, -(rStroke.getWidth() * 0.5));
// scale back to unit polygon
aScaledUnitPolyPolygon.transform(basegfx::utils::createScaleB2DHomMatrix(
0.0 != aScale.getX() ? 1.0 / aScale.getX() : 1.0,
0.0 != aScale.getY() ? 1.0 / aScale.getY() : 1.0));
// create with unit polygon
pNew = new SdrContourTextPrimitive2D(
&rText.getSdrText(),
rText.getOutlinerParaObject(),
std::move(aScaledUnitPolyPolygon),
rObjectTransform);
}
else
{
// create with unit polygon
pNew = new SdrContourTextPrimitive2D(
&rText.getSdrText(),
rText.getOutlinerParaObject(),
rUnitPolyPolygon,
rObjectTransform);
}
}
else if(!rText.getSdrFormTextAttribute().isDefault())
{
// text on path, use scaled polygon
basegfx::B2DPolyPolygon aScaledPolyPolygon(rUnitPolyPolygon);
aScaledPolyPolygon.transform(rObjectTransform);
pNew = new SdrPathTextPrimitive2D(
&rText.getSdrText(),
rText.getOutlinerParaObject(),
std::move(aScaledPolyPolygon),
rText.getSdrFormTextAttribute());
}
else
{
// rObjectTransform is the whole SdrObject transformation from unit rectangle
// to its size and position. Decompose to allow working with single values.
basegfx::B2DVector aScale, aTranslate;
double fRotate, fShearX;
rObjectTransform.decompose(aScale, aTranslate, fRotate, fShearX);
// extract mirroring
const bool bMirrorX(basegfx::fTools::less(aScale.getX(), 0.0));
const bool bMirrorY(basegfx::fTools::less(aScale.getY(), 0.0));
aScale = basegfx::absolute(aScale);
// Get the real size, since polygon outline and scale
// from the object transformation may vary (e.g. ellipse segments)
basegfx::B2DHomMatrix aJustScaleTransform;
aJustScaleTransform.set(0, 0, aScale.getX());
aJustScaleTransform.set(1, 1, aScale.getY());
basegfx::B2DPolyPolygon aScaledUnitPolyPolygon(rUnitPolyPolygon);
aScaledUnitPolyPolygon.transform(aJustScaleTransform);
const basegfx::B2DRange aTextAnchorRange
= getTextAnchorRange(rText, basegfx::utils::getRange(aScaledUnitPolyPolygon));
// now create a transformation from this basic range (aTextAnchorRange)
// #i121494# if we have no scale use at least 1.0 to have a carrier e.g. for
// mirror values, else these will get lost
aAnchorTransform = basegfx::utils::createScaleTranslateB2DHomMatrix(
basegfx::fTools::equalZero(aTextAnchorRange.getWidth()) ? 1.0 : aTextAnchorRange.getWidth(),
basegfx::fTools::equalZero(aTextAnchorRange.getHeight()) ? 1.0 : aTextAnchorRange.getHeight(),
aTextAnchorRange.getMinX(), aTextAnchorRange.getMinY());
// apply mirroring
aAnchorTransform.scale(bMirrorX ? -1.0 : 1.0, bMirrorY ? -1.0 : 1.0);
// apply object's other transforms
aAnchorTransform = basegfx::utils::createShearXRotateTranslateB2DHomMatrix(fShearX, fRotate, aTranslate)
* aAnchorTransform;
if(rText.isFitToSize())
{
// stretched text in range
pNew = new SdrStretchTextPrimitive2D(
&rText.getSdrText(),
rText.getOutlinerParaObject(),
aAnchorTransform,
rText.isFixedCellHeight());
}
else if(rText.isAutoFit())
{
// isotropically scaled text in range
pNew = new SdrAutoFitTextPrimitive2D(
&rText.getSdrText(),
rText.getOutlinerParaObject(),
aAnchorTransform,
bWordWrap,
rText.isFixedCellHeight());
}
else if( rText.isChainable() && !rText.isInEditMode() )
{
pNew = new SdrChainedTextPrimitive2D(
&rText.getSdrText(),
rText.getOutlinerParaObject(),
aAnchorTransform );
}
else // text in range
{
// build new primitive
pNew = new SdrBlockTextPrimitive2D(
&rText.getSdrText(),
rText.getOutlinerParaObject(),
aAnchorTransform,
rText.getSdrTextHorzAdjust(),
rText.getSdrTextVertAdjust(),
rText.isFixedCellHeight(),
rText.isScroll(),
bCellText,
bWordWrap);
}
}
OSL_ENSURE(pNew != nullptr, "createTextPrimitive: no text primitive created (!)");
if(rText.isBlink())
{
// prepare animation and primitive list
drawinglayer::animation::AnimationEntryList aAnimationList;
rText.getBlinkTextTiming(aAnimationList);
if(0.0 != aAnimationList.getDuration())
{
// create content sequence
Primitive2DContainer aContent { pNew };
// create and add animated switch primitive
return new AnimatedBlinkPrimitive2D(aAnimationList, std::move(aContent));
}
else
{
// add to decomposition
return pNew;
}
}
if(rText.isScroll())
{
// suppress scroll when FontWork
if(rText.getSdrFormTextAttribute().isDefault())
{
// get scroll direction
const SdrTextAniDirection eDirection(rText.getSdrText().GetObject().GetTextAniDirection());
const bool bHorizontal(SdrTextAniDirection::Left == eDirection || SdrTextAniDirection::Right == eDirection);
// decompose to get separated values for the scroll box
basegfx::B2DVector aScale, aTranslate;
double fRotate, fShearX;
aAnchorTransform.decompose(aScale, aTranslate, fRotate, fShearX);
// build transform from scaled only to full AnchorTransform and inverse
const basegfx::B2DHomMatrix aSRT(basegfx::utils::createShearXRotateTranslateB2DHomMatrix(
fShearX, fRotate, aTranslate));
basegfx::B2DHomMatrix aISRT(aSRT);
aISRT.invert();
// bring the primitive back to scaled only and get scaled range, create new clone for this
rtl::Reference<SdrTextPrimitive2D> pNew2 = pNew->createTransformedClone(aISRT);
OSL_ENSURE(pNew2, "createTextPrimitive: Could not create transformed clone of text primitive (!)");
pNew = pNew2.get();
// create neutral geometry::ViewInformation2D for local range and decompose calls. This is okay
// since the decompose is view-independent
geometry::ViewInformation2D aViewInformation2D;
// get range
const basegfx::B2DRange aScaledRange(pNew->getB2DRange(aViewInformation2D));
// create left outside and right outside transformations. Also take care
// of the clip rectangle
basegfx::B2DHomMatrix aLeft, aRight;
basegfx::B2DPoint aClipTopLeft(0.0, 0.0);
basegfx::B2DPoint aClipBottomRight(aScale.getX(), aScale.getY());
if(bHorizontal)
{
aClipTopLeft.setY(aScaledRange.getMinY());
aClipBottomRight.setY(aScaledRange.getMaxY());
aLeft.translate(-aScaledRange.getMaxX(), 0.0);
aRight.translate(aScale.getX() - aScaledRange.getMinX(), 0.0);
}
else
{
aClipTopLeft.setX(aScaledRange.getMinX());
aClipBottomRight.setX(aScaledRange.getMaxX());
aLeft.translate(0.0, -aScaledRange.getMaxY());
aRight.translate(0.0, aScale.getY() - aScaledRange.getMinY());
}
aLeft *= aSRT;
aRight *= aSRT;
// prepare animation list
drawinglayer::animation::AnimationEntryList aAnimationList;
if(bHorizontal)
{
rText.getScrollTextTiming(aAnimationList, aScale.getX(), aScaledRange.getWidth());
}
else
{
rText.getScrollTextTiming(aAnimationList, aScale.getY(), aScaledRange.getHeight());
}
if(0.0 != aAnimationList.getDuration())
{
// create a new Primitive2DContainer containing the animated text in its scaled only state.
// use the decomposition to force to simple text primitives, those will no longer
// need the outliner for formatting (alternatively it is also possible to just add
// pNew to aNewPrimitiveSequence)
Primitive2DContainer aAnimSequence;
pNew->get2DDecomposition(aAnimSequence, aViewInformation2D);
pNew.clear();
// create a new animatedInterpolatePrimitive and add it
Primitive2DContainer aContent {
new AnimatedInterpolatePrimitive2D({ aLeft, aRight }, aAnimationList, std::move(aAnimSequence))
};
// scrolling needs an encapsulating clipping primitive
const basegfx::B2DRange aClipRange(aClipTopLeft, aClipBottomRight);
basegfx::B2DPolygon aClipPolygon(basegfx::utils::createPolygonFromRect(aClipRange));
aClipPolygon.transform(aSRT);
return new MaskPrimitive2D(basegfx::B2DPolyPolygon(aClipPolygon), std::move(aContent));
}
else
{
// add to decomposition
return pNew;
}
}
}
if(rText.isInEditMode())
{
// #i97628#
// encapsulate with TextHierarchyEditPrimitive2D to allow renderers
// to suppress actively edited content if needed
Primitive2DContainer aContent { pNew };
// create and add TextHierarchyEditPrimitive2D primitive
return new TextHierarchyEditPrimitive2D(std::move(aContent));
}
else
{
// add to decomposition
return pNew;
}
}
Primitive2DContainer createEmbeddedShadowPrimitive(
Primitive2DContainer&& rContent,
const attribute::SdrShadowAttribute& rShadow,
const basegfx::B2DHomMatrix& rObjectMatrix,
const Primitive2DContainer* pContentForShadow)
{
if(rContent.empty())
return std::move(rContent);
basegfx::B2DHomMatrix aShadowOffset;
if(rShadow.getSize().getX() != 100000)
{
basegfx::B2DTuple aScale;
basegfx::B2DTuple aTranslate;
double fRotate = 0;
double fShearX = 0;
rObjectMatrix.decompose(aScale, aTranslate, fRotate, fShearX);
// Scale the shadow
aTranslate += getShadowScaleOriginOffset(aScale, rShadow.getAlignment());
aShadowOffset.translate(-aTranslate);
aShadowOffset.scale(rShadow.getSize().getX() * 0.00001, rShadow.getSize().getY() * 0.00001);
aShadowOffset.translate(aTranslate);
}
aShadowOffset.translate(rShadow.getOffset().getX(), rShadow.getOffset().getY());
// create shadow primitive and add content
const Primitive2DContainer& rContentForShadow
= pContentForShadow ? *pContentForShadow : rContent;
int nContentWithTransparence = std::count_if(
rContentForShadow.begin(), rContentForShadow.end(),
[](const Primitive2DReference& xChild) {
auto pChild = dynamic_cast<SdrCellPrimitive2D*>(xChild.get());
return pChild && pChild->getTransparenceForShadow() != 0;
});
if (nContentWithTransparence == 0)
{
Primitive2DContainer aRetval(2);
aRetval[0] =
new ShadowPrimitive2D(
aShadowOffset,
rShadow.getColor(),
rShadow.getBlur(),
Primitive2DContainer(pContentForShadow ? *pContentForShadow : rContent));
if (0.0 != rShadow.getTransparence())
{
// create SimpleTransparencePrimitive2D
Primitive2DContainer aTempContent{ aRetval[0] };
aRetval[0] =
new UnifiedTransparencePrimitive2D(
std::move(aTempContent),
rShadow.getTransparence());
}
aRetval[1] = new GroupPrimitive2D(std::move(rContent));
return aRetval;
}
Primitive2DContainer aRetval;
for (const auto& xChild : rContentForShadow)
{
aRetval.emplace_back(
new ShadowPrimitive2D(aShadowOffset, rShadow.getColor(), rShadow.getBlur(),
Primitive2DContainer({ xChild })));
if (rShadow.getTransparence() != 0.0)
{
Primitive2DContainer aTempContent{ aRetval.back() };
aRetval.back() = new UnifiedTransparencePrimitive2D(
std::move(aTempContent), rShadow.getTransparence());
}
}
aRetval.push_back(new GroupPrimitive2D(std::move(rContent)));
return aRetval;
}
Primitive2DContainer createEmbeddedGlowPrimitive(
Primitive2DContainer&& rContent,
const attribute::SdrGlowAttribute& rGlow)
{
if(rContent.empty())
return std::move(rContent);
Primitive2DContainer aRetval(2);
aRetval[0] = new GlowPrimitive2D(rGlow.getColor(), rGlow.getRadius(), Primitive2DContainer(rContent));
aRetval[1] = new GroupPrimitive2D(Primitive2DContainer(std::move(rContent)));
return aRetval;
}
Primitive2DContainer createEmbeddedTextGlowPrimitive(
Primitive2DContainer&& rContent,
const attribute::SdrGlowTextAttribute& rGlow)
{
if (rContent.empty())
return std::move(rContent);
Primitive2DContainer aRetval(2);
aRetval[0] = new GlowPrimitive2D(rGlow.getTextColor(), rGlow.getTextRadius(), Primitive2DContainer(rContent));
aRetval[1] = new GroupPrimitive2D(Primitive2DContainer(std::move(rContent)));
return aRetval;
}
Primitive2DContainer createEmbeddedSoftEdgePrimitive(Primitive2DContainer&& aContent,
sal_Int32 nRadius)
{
if (aContent.empty() || !nRadius)
return std::move(aContent);
Primitive2DContainer aRetval(1);
aRetval[0] = new SoftEdgePrimitive2D(nRadius, std::move(aContent));
return aRetval;
}
} // end of namespace
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */