/* -*- 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 .
*/
// check if this is a special point if ( ( nDat >> 16 ) == 0x8000 )
{
nNewValue = static_cast<sal_uInt16>(nDat);
rParameter.Type = EnhancedCustomShapeParameterType::EQUATION;
} else
rParameter.Type = EnhancedCustomShapeParameterType::NORMAL;
rParameter.Value <<= nNewValue;
}
ClearItem( SDRATTR_TEXTDIRECTION ); //SJ: vertical writing is not required, by removing this item no outliner is created
// #i105323# For 2D AutoShapes, the shadow attribute does not need to be applied to any // of the constructed helper SdrObjects. This would lead to problems since the shadow // of one helper object would fall on one helper object behind it (e.g. with the // eyes of the smiley shape). This is not wanted; instead a single shadow 'behind' // the AutoShape visualisation is wanted. This is done with primitive functionality // now in SdrCustomShapePrimitive2D::create2DDecomposition, but only for 2D objects // (see there and in EnhancedCustomShape3d::Create3DObject to read more). // This exception may be removed later when AutoShapes will create primitives directly. // So, currently remove the ShadowAttribute from the ItemSet to not apply it to any // 2D helper shape.
ClearItem(SDRATTR_SHADOW);
staticdouble lcl_getXAdjustmentValue(std::u16string_view rShapeType, const sal_uInt32 nHandleIndex, constdouble fX, constdouble fW, constdouble fH)
{ // degenerated shapes are not worth to calculate special case for each shape type if (fW <= 0.0 || fH <= 0.0) return 50000;
// pattern x / w * 100000, simple scaling if (o3tl::starts_with(rShapeType, u"ooxml-")) return fX / fW * 100000.0;
return fX; // method is unknown
}
staticdouble lcl_getYAdjustmentValue(std::u16string_view rShapeType, const sal_uInt32 nHandleIndex, constdouble fY, constdouble fW, constdouble fH)
{ // degenerated shapes are not worth to calculate a special case for each shape type if (fW <= 0.0 || fH <= 0.0) return 50000;
// special pattern smiley if (rShapeType == u"ooxml-smileyFace") return (fY - fH * 16515.0 / 21600.0) / fH * 100000.0;
// special pattern for star with odd number of tips, because center of star not center of shape if (rShapeType == u"ooxml-star5") return (fH / 2.0 - fY * 100000.0 / 110557.0) / fH * 100000.0; if (rShapeType == u"ooxml-star7") return (fH / 2.0 - fY * 100000.0 / 105210.0) / fH * 100000.0;
// special pattern swooshArrow if (rShapeType == u"ooxml-swooshArrow") return (fY - std::min(fW, fH) / 8.0) / fH * 100000.0;
// special pattern leftRightRibbon if (rShapeType == u"ooxml-leftRightRibbon") return fY / fH * 200000 - 100000;
// pattern y / h * 100000, simple scaling if (o3tl::starts_with(rShapeType, u"ooxml-")) return fY / fH * 100000.0;
return fY; // method is unknown
}
staticdouble lcl_getAngleInOOXMLUnit(double fDY, double fDX)
{ if (fDX != 0.0 || fDY != 0.0)
{ double fAngleRad(atan2(fDY, fDX)); double fAngle = basegfx::rad2deg(fAngleRad); // atan2 returns angle in ]-pi; pi], OOXML preset shapes use [0;360[. if (fAngle < 0.0)
fAngle += 360.0; // OOXML uses angle unit 1/60000 degree.
fAngle *= 60000.0; return fAngle;
} return 0.0; // no angle defined for origin in polar coordinate system
}
staticdouble lcl_getRadiusDistance(double fWR, double fHR, double fX, double fY)
{ // Get D so, that point (fX|fY) is on the ellipse, that has width fWR-D and // height fHR-D and center in origin. // Get solution of ellipse equation (fX/(fWR-D))^2 + (fY/(fHR-D)^2 = 1 by solving // fX^2*(fHR-D)^2 + fY^2*(fWR-D)^2 - (fWR-D)^2 * (fHR-D)^2 = 0 with Newton-method. if (fX == 0.0) return std::min(fHR - fY, fWR); elseif (fY == 0.0) return std::min(fWR - fX, fHR);
bool EnhancedCustomShape2d::SetHandleControllerPosition( const sal_uInt32 nIndex, const css::awt::Point& rPosition )
{ // The method name is misleading. Essentially it calculates the adjustment values from a given // handle position.
// For ooxml-foo shapes, the way to calculate the adjustment value from the handle position depends on // the type of the shape, therefore need 'Type'.
OUString sShapeType(u"non-primitive"_ustr); // default for ODF const SdrCustomShapeGeometryItem& rGeometryItem(mrSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY )); const Any* pAny = rGeometryItem.GetPropertyValueByName(u"Type"_ustr); if (pAny)
*pAny >>= sShapeType;
bool bRetValue = false; if ( nIndex < GetHdlCount() )
{
Handle aHandle; if ( ConvertSequenceToEnhancedCustomShape2dHandle( m_seqHandles[ nIndex ], aHandle ) )
{
Point aP( rPosition.X, rPosition.Y ); // apply the negative object rotation to the controller position
// ODF shapes are expected to use a direct binding between position and adjustment // values. OOXML preset shapes use known formulas. These are calculated backward to // get the adjustment values. So far we do not have a general method to calculate // the adjustment values for any shape from the handle position. if ( aHandle.aPosition.First.Type == EnhancedCustomShapeParameterType::ADJUSTMENT )
aHandle.aPosition.First.Value >>= nFirstAdjustmentValue; if ( aHandle.aPosition.Second.Type == EnhancedCustomShapeParameterType::ADJUSTMENT )
aHandle.aPosition.Second.Value>>= nSecondAdjustmentValue;
if (aHandle.nFlags & HandleFlags::REFR)
nFirstAdjustmentValue = aHandle.nRefR; if (aHandle.nFlags & HandleFlags::REFANGLE)
nSecondAdjustmentValue = aHandle.nRefAngle;
double fAngle(0.0); double fRadius(0.0); // 'then' treats only shapes of type "ooxml-foo", fontwork shapes have been mapped // to MS binary import and will be treated in 'else'. if (m_bOOXMLShape)
{ // DrawingML polar handles set REFR or REFANGLE instead of POLAR // use the shape center instead. double fDX = fPos1 - fWidth / 2.0; double fDY = fPos2 - fHeight / 2.0;
// There exists no common pattern. 'radius' or 'angle' might have special meaning. if (sShapeType == "ooxml-blockArc" && nIndex == 1)
{ // usual angle, special radius
fAngle = lcl_getAngleInOOXMLUnit(fDY, fDX); // The value connected to REFR is the _difference_ between the outer // ellipse given by shape width and height and the inner ellipse through // the handle position. double fRadiusDifference
= lcl_getRadiusDistance(fWidth / 2.0, fHeight / 2.0, fDX, fDY); double fss(std::min(fWidth, fHeight)); if (fss != 0)
fRadius = fRadiusDifference * 100000.0 / fss;
} elseif (sShapeType == "ooxml-donut" || sShapeType == "ooxml-noSmoking")
{ // no angle adjustment, radius bound to x-coordinate of handle double fss(std::min(fWidth, fHeight)); if (fss != 0.0)
fRadius = fPos1 * 100000.0 / fss;
} elseif ((sShapeType == "ooxml-circularArrow"
|| sShapeType == "ooxml-leftRightCircularArrow"
|| sShapeType == "ooxml-leftCircularArrow")
&& nIndex == 0)
{ // The value adj2 is the increase compared to the angle in adj3 double fHandleAngle = lcl_getAngleInOOXMLUnit(fDY, fDX); if (sShapeType == "ooxml-leftCircularArrow")
fAngle = GetAdjustValueAsDouble(2) - fHandleAngle; else
fAngle = fHandleAngle - GetAdjustValueAsDouble(2); if (fAngle < 0.0) // 0deg to 360deg cut
fAngle += 21600000.0; // no REFR
} elseif ((sShapeType == "ooxml-circularArrow"
|| sShapeType == "ooxml-leftCircularArrow"
|| sShapeType == "ooxml-leftRightCircularArrow")
&& nIndex == 2)
{ // The value adj1 connected to REFR is the thickness of the arc. The adjustvalue adj5 // has the _difference_ between the outer ellipse given by shape width and height // and the middle ellipse of the arc. The handle is on the outer side of the // arc. So we calculate the difference between the ellipse through the handle // and the outer ellipse and subtract then. double fRadiusDifferenceHandle
= lcl_getRadiusDistance(fWidth / 2.0, fHeight / 2.0, fDX, fDY); double fadj5(GetAdjustValueAsDouble(4)); double fss(std::min(fWidth, fHeight)); if (fss != 0.0)
{
fadj5 = fadj5 * fss / 100000.0;
fRadius = 2.0 * (fadj5 - fRadiusDifferenceHandle);
fRadius = fRadius * 100000.0 / fss;
} // ToDo: Get angle adj3 exact. Use approximation for now
fAngle = lcl_getAngleInOOXMLUnit(fDY, fDX);
} elseif ((sShapeType == "ooxml-circularArrow"
|| sShapeType == "ooxml-leftCircularArrow"
|| sShapeType == "ooxml-leftRightCircularArrow")
&& nIndex == 3)
{ // ToDo: Getting handle position from adjustment value adj5 is complex. // Analytical or numerical solution for backward calculation is missing. // Approximation for now, using a line from center through handle position. double fAngleRad(0.0); if (fDX != 0.0 || fDY != 0.0)
fAngleRad = atan2(fDY, fDX); double fHelpX = cos(fAngleRad) * fHeight / 2.0; double fHelpY = sin(fAngleRad) * fWidth / 2.0; if (fHelpX != 0.0 || fHelpY != 0.0)
{ double fHelpAngle = atan2(fHelpY, fHelpX); double fOuterX = fWidth / 2.0 * cos(fHelpAngle); double fOuterY = fHeight / 2.0 * sin(fHelpAngle); double fOuterRadius = std::hypot(fOuterX, fOuterY); double fHandleRadius = std::hypot(fDX, fDY);
fRadius = (fOuterRadius - fHandleRadius) / 2.0; double fss(std::min(fWidth, fHeight)); if (fss != 0.0)
fRadius = fRadius * 100000.0 / fss;
} // no REFANGLE
} elseif (sShapeType == "ooxml-mathNotEqual" && nIndex == 1)
{ double fadj1(GetAdjustValueAsDouble(0)); double fadj3(GetAdjustValueAsDouble(2));
fadj1 = fadj1 * fHeight / 100000.0;
fadj3 = fadj3 * fHeight / 100000.0; double fDYRefHorizBar = fDY + fadj1 + fadj3; if (fDX != 0.0 || fDYRefHorizBar != 0.0)
{ double fRawAngleDeg = basegfx::rad2deg(atan2(fDYRefHorizBar, fDX));
fAngle = (fRawAngleDeg + 180.0) * 60000.0;
} // no REFR
} else
{ // no special meaning of radius or angle, suitable for "ooxml-arc", // "ooxml-chord", "ooxml-pie" and circular arrows value adj4.
fAngle = lcl_getAngleInOOXMLUnit(fDY, fDX);
fRadius = std::hypot(fDX, fDY); double fss(std::min(fWidth, fHeight)); if (fss != 0.0)
fRadius = fRadius * 100000.0 / fss;
}
} else// e.g. shapes from ODF, MS binary import or shape type "fontwork-foo"
{ double fXRef, fYRef; if (aHandle.nFlags & HandleFlags::POLAR)
{
GetParameter(fXRef, aHandle.aPolar.First, false, false);
GetParameter(fYRef, aHandle.aPolar.Second, false, false);
} else
{
fXRef = fWidth / 2.0;
fYRef = fHeight / 2.0;
} constdouble fDX = fPos1 - fXRef; constdouble fDY = fPos2 - fYRef; // ToDo: MS binary uses fixed-point number for the angle. Make sure conversion // to double is done in import and export. // ToDo: Angle unit is degree, but range ]-180;180] or [0;360[? Assume ]-180;180]. if (fDX != 0.0 || fDY != 0.0)
{
fRadius = std::hypot(fDX, fDY);
fAngle = basegfx::rad2deg(atan2(fDY, fDX));
}
}
// All formats can restrict the radius to a range if ( aHandle.nFlags & HandleFlags::RADIUS_RANGE_MINIMUM )
{ double fMin;
GetParameter( fMin, aHandle.aRadiusRangeMinimum, false, false ); if ( fRadius < fMin )
fRadius = fMin;
} if ( aHandle.nFlags & HandleFlags::RADIUS_RANGE_MAXIMUM )
{ double fMax;
GetParameter( fMax, aHandle.aRadiusRangeMaximum, false, false ); if ( fRadius > fMax )
fRadius = fMax;
}
if ( nFirstAdjustmentValue >= 0 )
SetAdjustValueAsDouble( fRadius, nFirstAdjustmentValue ); if ( nSecondAdjustmentValue >= 0 )
SetAdjustValueAsDouble( fAngle, nSecondAdjustmentValue );
} else// XY-Handle
{ // Calculating the adjustment values follows in most cases some patterns, which only // need width and height of the shape and handle position. These patterns are calculated // in the static, local methods. More complex calculations or additional steps are // done here. // Values for corner cases like 'root(negative)' or 'div zero' are meaningless dummies. // Identifiers often refer to guide names in OOXML shape definitions. double fAdjustX = fPos1; double fAdjustY = fPos2; if (aHandle.nFlags & HandleFlags::REFX)
{
nFirstAdjustmentValue = aHandle.nRefX; if ((sShapeType == "ooxml-gear6") || (sShapeType == "ooxml-gear9"))
{ // special, needs angle calculations double fss(std::min(fWidth, fHeight)); double fadj1(GetAdjustValueAsDouble(0)); // from point D6 or D9 double fth(fadj1 * fss / 100000.0); // radius difference double frw(fWidth / 2.0 - fth); // inner ellipse double frh(fHeight / 2.0 - fth); double fDX(fPos1 - fWidth / 2.0); double fDY(fPos2 - fHeight / 2.0); double fbA(-1.7); // effective angle for point A6 or A9, dummy value if (fDX != 0.0 || fDY != 0.0)
fbA = atan2(fDY, fDX); double faA(fbA); // corresponding circle angle, dummy value double ftmpX(frh * cos(fbA)); double ftmpY(frw * sin(fbA)); if (ftmpX != 0.0 || ftmpY != 0.0)
faA = atan2(ftmpY, ftmpX); // range ]-pi..pi], here -pi < faA < -pi/2 // screen 270 deg = mathematic coordinate system -pi/2 double fha(-M_PI_2 - faA); // positive circle angle difference to 270 deg if (abs(fha) == M_PI_2) // should not happen, but ensure no tan(90deg)
fha = 0.12; // dummy value double flFD(2 * std::min(frw, frh) * tan(fha) - fth); if (fss != 0.0)
fAdjustX = flFD / fss * 100000.0;
} else
{
fAdjustX
= lcl_getXAdjustmentValue(sShapeType, nIndex, fPos1, fWidth, fHeight); if ((sShapeType == "ooxml-curvedDownArrow")
|| (sShapeType == "ooxml-curvedUpArrow"))
{ double fss(std::min(fWidth, fHeight)); if (fss != 0.0)
{ double fadj3(GetAdjustValueAsDouble(2)); double fHScaled(100000.0 * fHeight / fss); double fRadicand(fHScaled * fHScaled - fadj3 * fadj3); double fSqrt = fRadicand >= 0.0 ? sqrt(fRadicand) : 0.0; double fPart(200000.0 * fWidth / fss * (fSqrt + fHScaled));
fAdjustX = fPart - 4.0 * fHScaled * fAdjustX; if (nIndex == 0)
{ // calculate adj1 double fadj2(GetAdjustValueAsDouble(1));
fAdjustX = fAdjustX - fadj2 * (fSqrt + fHScaled); double fDenominator(fSqrt - 3.0 * fHScaled);
fAdjustX /= fDenominator != 0.0 ? fDenominator : 1.0;
} else
{ // nIndex == 1, calculate adj2 double fadj1(GetAdjustValueAsDouble(0));
fAdjustX = fAdjustX - fadj1 * (fSqrt - fHScaled); double fDenominator(fSqrt + 3.0 * fHScaled);
fAdjustX /= fDenominator != 0.0 ? fDenominator : 1.0;
}
}
}
}
}
if ( nFirstAdjustmentValue >= 0 )
{ if ( aHandle.nFlags & HandleFlags::RANGE_X_MINIMUM ) // check if horizontal handle needs to be within a range
{ double fXMin;
GetParameter( fXMin, aHandle.aXRangeMinimum, false, false ); if (fAdjustX < fXMin)
fAdjustX = fXMin;
} if ( aHandle.nFlags & HandleFlags::RANGE_X_MAXIMUM ) // check if horizontal handle needs to be within a range
{ double fXMax;
GetParameter( fXMax, aHandle.aXRangeMaximum, false, false ); if (fAdjustX > fXMax)
fAdjustX = fXMax;
}
SetAdjustValueAsDouble(fAdjustX, nFirstAdjustmentValue);
} if ( nSecondAdjustmentValue >= 0 )
{ if ( aHandle.nFlags & HandleFlags::RANGE_Y_MINIMUM ) // check if vertical handle needs to be within a range
{ double fYMin;
GetParameter( fYMin, aHandle.aYRangeMinimum, false, false ); if (fAdjustY < fYMin)
fAdjustY = fYMin;
} if ( aHandle.nFlags & HandleFlags::RANGE_Y_MAXIMUM ) // check if vertical handle needs to be within a range
{ double fYMax;
GetParameter( fYMax, aHandle.aYRangeMaximum, false, false ); if (fAdjustY > fYMax)
fAdjustY = fYMax;
}
SetAdjustValueAsDouble(fAdjustY, nSecondAdjustmentValue);
}
} // and writing them back into the GeometryItem
SdrCustomShapeGeometryItem aGeometryItem(mrSdrObjCustomShape.GetMergedItem( SDRATTR_CUSTOMSHAPE_GEOMETRY ));
css::beans::PropertyValue aPropVal;
aPropVal.Name = "AdjustmentValues";
aPropVal.Value <<= m_seqAdjustmentValues;
aGeometryItem.SetPropertyValue( aPropVal );
mrSdrObjCustomShape.SetMergedItem( aGeometryItem );
bRetValue = true;
}
} return bRetValue;
}
aNewB2DPolygon.clear();
}
} break; case CURVETO :
{ for ( sal_uInt16 i = 0; ( i < nPntCount ) && ( ( rSrcPt + 2 ) < nCoordSize ); i++ )
{ const Point aControlA(GetPoint( m_seqCoordinates[ rSrcPt++ ], true, true )); const Point aControlB(GetPoint( m_seqCoordinates[ rSrcPt++ ], true, true )); const Point aEnd(GetPoint( m_seqCoordinates[ rSrcPt++ ], true, true ));
DBG_ASSERT(aNewB2DPolygon.count(), "EnhancedCustomShape2d::CreateSubPath: Error in adding control point (!)");
aNewB2DPolygon.appendBezierSegment(
basegfx::B2DPoint(aControlA.X(), aControlA.Y()),
basegfx::B2DPoint(aControlB.X(), aControlB.Y()),
basegfx::B2DPoint(aEnd.X(), aEnd.Y()));
}
} break;
case ANGLEELLIPSE: // command U case ANGLEELLIPSETO: // command T
{ // Some shapes will need special handling, decide on property 'Type'.
OUString sShpType; const SdrCustomShapeGeometryItem& rGeometryItem = mrSdrObjCustomShape.GetMergedItem(SDRATTR_CUSTOMSHAPE_GEOMETRY); const Any* pAny = rGeometryItem.GetPropertyValueByName(u"Type"_ustr); if (pAny)
*pAny >>= sShpType; // User defined shapes in MS binary format, which contain command U or T after import // in LibreOffice, starts with "mso". constbool bIsFromBinaryImport(sShpType.startsWith("mso")); // The only own or imported preset shapes with U command are those listed below. // Command T is not used in preset shapes. const std::unordered_set<OUString> aPresetShapesWithU =
{ u"ellipse"_ustr, u"ring"_ustr, u"smiley"_ustr, u"sun"_ustr, u"forbidden"_ustr, u"flowchart-connector"_ustr,
u"flowchart-summing-junction"_ustr, u"flowchart-or"_ustr, u"cloud-callout"_ustr};
std::unordered_set<OUString>::const_iterator aIter = aPresetShapesWithU.find(sShpType); constbool bIsPresetShapeWithU(aIter != aPresetShapesWithU.end());
for (sal_uInt16 i = 0; (i < nPntCount) && ((rSrcPt + 2) < nCoordSize); i++)
{ // ANGLEELLIPSE is the same as ANGLEELLIPSETO, only that it // makes an implicit MOVETO. That ends the previous subpath. if (ANGLEELLIPSE == nCommand)
{ if (aNewB2DPolygon.count() > 1)
{ // #i76201# Add conversion to closed polygon when first and last points are equal
basegfx::utils::checkClosed(aNewB2DPolygon);
aNewB2DPolyPolygon.append(aNewB2DPolygon);
}
aNewB2DPolygon.clear();
}
// Read all parameters, but do not finally handle them.
basegfx::B2DPoint aCenter(GetPointAsB2DPoint(m_seqCoordinates[ rSrcPt ], true, true)); double fWR; // horizontal ellipse radius double fHR; // vertical ellipse radius
GetParameter(fWR, m_seqCoordinates[rSrcPt + 1].First, true, false);
GetParameter(fHR, m_seqCoordinates[rSrcPt + 1].Second, false, true); double fStartAngle;
GetParameter(fStartAngle, m_seqCoordinates[rSrcPt + 2].First, false, false); double fEndAngle;
GetParameter(fEndAngle, m_seqCoordinates[rSrcPt + 2].Second, false, false); // Increasing here allows flat case differentiation tree by using 'continue'.
rSrcPt += 3;
double fScaledWR(fWR * m_fXScale); double fScaledHR(fHR * m_fYScale); if (fScaledWR == 0.0 && fScaledHR == 0.0)
{ // degenerated ellipse, add center point
aNewB2DPolygon.append(aCenter); continue;
}
if (bIsFromBinaryImport)
{ // If a shape comes from MS binary ('escher') import, the angles are in degrees*2^16 // and the second angle is not an end angle, but a swing angle. // MS Word shows this behavior: 0deg right, 90deg top, 180deg left and 270deg // bottom. Third and forth parameter are horizontal and vertical radius, not width // and height as noted in VML spec. A positive swing angle goes counter-clock // wise (in user view). The swing angle might go several times around in case // abs(swing angle) >= 360deg. Stroke accumulates, so that e.g. dash-dot might fill the // gaps of previous turn. Fill does not accumulate but uses even-odd rule, semi-transparent // fill does not become darker. The start and end points of the arc are calculated by // using the angles on a circle and then scaling the circle to the ellipse. Caution, that // is different from angle handling in ARCANGLETO and ODF. // The following implementation generates such rendering. It is only for rendering legacy // MS shapes and independent of the meaning of commands U and T in ODF specification.
// The WordArt shape 'RingOutside' has already angles in degree, all other need // conversion from fixed-point number. double fSwingAngle = fEndAngle; if (sShpType != "mso-spt143")
{
fStartAngle /= 65536.0;
fSwingAngle = fEndAngle / 65536.0;
} // Convert orientation
fStartAngle = -fStartAngle;
fSwingAngle = -fSwingAngle;
fEndAngle = fStartAngle + fSwingAngle; if (fSwingAngle < 0.0)
std::swap(fStartAngle, fEndAngle); double fFrom(fStartAngle); double fTo(fFrom + 180.0);
basegfx::B2DPolygon aTempB2DPolygon; double fS; // fFrom in radians in [0..2Pi[ double fE; // fTo or fEndAngle in radians in [0..2PI[ while (fTo < fEndAngle)
{
fS = lcl_getNormalizedAngleRad(fFrom);
fE = lcl_getNormalizedAngleRad(fTo);
aTempB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fScaledWR, fScaledHR, fS,fE));
fFrom = fTo;
fTo += 180.0;
}
fS = lcl_getNormalizedAngleRad(fFrom);
fE = lcl_getNormalizedAngleRad(fEndAngle);
aTempB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fScaledWR, fScaledHR,fS, fE)); if (fSwingAngle < 0)
aTempB2DPolygon.flip();
aNewB2DPolygon.append(aTempB2DPolygon); continue;
}
// The not yet handled shapes are own preset shapes, or preset shapes from MS binary import, or user // defined shapes, or foreign shapes. Shapes from OOXML import do not use ANGLEELLIPSE or // ANGLEELLIPSETO, but use ARCANGLETO. if (bIsPresetShapeWithU)
{ // Besides "cloud-callout" all preset shapes have angle values '0 360'. // The imported "cloud-callout" has angle values '0 360' too, only our own "cloud-callout" // has values '0 23592960'. But that is fixedfloat and means 360*2^16. Thus all these shapes // have a full ellipse with start at 0deg.
aNewB2DPolygon.append(basegfx::utils::createPolygonFromEllipse(aCenter, fScaledWR, fScaledHR)); continue;
}
// In all other cases, full ODF conform handling is necessary. ODF rules: // Third and forth parameter are horizontal and vertical radius. // An angle determines the start or end point of the segment by intersection of the second angle // leg with the ellipse. The first angle leg is always the positive x-axis. For the position // of the intersection points the angle is used modulo 360deg in range [0deg..360deg[. // The position of range [0deg..360deg[ is the same as in command ARCANGLETO, with 0deg right, // 90deg bottom, 180deg left and 270deg top. Only if abs(end angle - start angle) == 360 deg, // a full ellipse is drawn. The segment is always drawn clock wise (in user view) from start // point to end point. The end point of the segment becomes the new "current" point.
if (fabs(fabs(fEndAngle - fStartAngle) - 360.0) < 1.0E-15)
{ // draw full ellipse // Because createPolygonFromEllipseSegment cannot create full ellipse and // createPolygonFromEllipse has no varying starts, we use two half ellipses. constdouble fS(lcl_getNormalizedCircleAngleRad(fWR, fHR, fStartAngle)); constdouble fH(lcl_getNormalizedCircleAngleRad(fWR, fHR, fStartAngle + 180.0)); constdouble fE(lcl_getNormalizedCircleAngleRad(fWR, fHR, fEndAngle));
aNewB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fScaledWR, fScaledHR, fS, fH));
aNewB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fScaledWR, fScaledHR, fH, fE)); continue;
}
// remaining cases with central segment angle < 360 double fS(lcl_getNormalizedCircleAngleRad(fWR, fHR, fStartAngle)); double fE(lcl_getNormalizedCircleAngleRad(fWR, fHR, fEndAngle));
aNewB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fScaledWR, fScaledHR, fS, fE));
} // end for
} // end case break;
case QUADRATICCURVETO :
{ for ( sal_Int32 i(0); ( i < nPntCount ) && ( rSrcPt + 1 < nCoordSize ); i++ )
{
DBG_ASSERT(aNewB2DPolygon.count(), "EnhancedCustomShape2d::CreateSubPath: Error no previous point for Q (!)"); if (aNewB2DPolygon.count() > 0)
{ const basegfx::B2DPoint aPreviousEndPoint(aNewB2DPolygon.getB2DPoint(aNewB2DPolygon.count()-1)); const basegfx::B2DPoint aControlQ(GetPointAsB2DPoint( m_seqCoordinates[ rSrcPt++ ], true, true )); const basegfx::B2DPoint aEnd(GetPointAsB2DPoint( m_seqCoordinates[ rSrcPt++ ], true, true )); const basegfx::B2DPoint aControlA((aPreviousEndPoint + (aControlQ * 2)) / 3); const basegfx::B2DPoint aControlB(((aControlQ * 2) + aEnd) / 3);
aNewB2DPolygon.appendBezierSegment(aControlA, aControlB, aEnd);
} else// no previous point; ill structured path, but try to draw as much as possible
{
rSrcPt++; // skip control point const basegfx::B2DPoint aEnd(GetPointAsB2DPoint( m_seqCoordinates[ rSrcPt++ ], true, true ));
aNewB2DPolygon.append(aEnd);
}
}
} break;
// tdf#122323 MS Office clamps the swing angle to [-360,360]. Such restriction // is neither in OOXML nor in ODF. Nevertheless, to be compatible we do it for // "ooxml-foo" shapes. Those shapes have their origin in MS Office. if (m_bOOXMLShape)
{
fSwingAngle = std::clamp(fSwingAngle, -360.0, 360.0);
}
if (aNewB2DPolygon.count() > 0) // otherwise no "current point"
{ // use similar methods as in command U
basegfx::B2DPolygon aTempB2DPolygon;
if (fWR == 0.0 && fHR == 0.0)
{ // degenerated ellipse, add this one point
aTempB2DPolygon.append(basegfx::B2DPoint(0.0, 0.0));
} else
{ double fEndAngle = fStartAngle + fSwingAngle; // Generate arc with ellipse left|top = 0|0.
basegfx::B2DPoint aCenter(fWR, fHR); if (fSwingAngle < 0.0)
std::swap(fStartAngle, fEndAngle); double fS; // fFrom in radians in [0..2Pi[ double fE; // fTo or fEndAngle in radians in [0..2PI[ double fFrom(fStartAngle); // createPolygonFromEllipseSegment expects angles in [0..2PI[. if (fSwingAngle >= 360.0 || fSwingAngle <= -360.0)
{ double fTo(fFrom + 180.0); while (fTo < fEndAngle)
{
fS = lcl_getNormalizedCircleAngleRad(fWR, fHR, fFrom);
fE = lcl_getNormalizedCircleAngleRad(fWR, fHR, fTo);
aTempB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fWR, fHR, fS,fE));
fFrom = fTo;
fTo += 180.0;
}
}
fS = lcl_getNormalizedCircleAngleRad(fWR, fHR, fFrom);
fE = lcl_getNormalizedCircleAngleRad(fWR, fHR, fEndAngle);
aTempB2DPolygon.append(basegfx::utils::createPolygonFromEllipseSegment(aCenter, fWR, fHR,fS, fE)); if (fSwingAngle < 0)
aTempB2DPolygon.flip();
aTempB2DPolygon.removeDoublePoints();
} // Scale arc to 1/100mm
basegfx::B2DHomMatrix aMatrix = basegfx::utils::createScaleB2DHomMatrix(m_fXScale, m_fYScale);
aTempB2DPolygon.transform(aMatrix);
// Now that we have the arc, move it to the "current point".
basegfx::B2DPoint aCurrentPointB2D( aNewB2DPolygon.getB2DPoint(aNewB2DPolygon.count() - 1 ) ); constdouble fDx(aCurrentPointB2D.getX() - aTempB2DPolygon.getB2DPoint(0).getX()); constdouble fDy(aCurrentPointB2D.getY() - aTempB2DPolygon.getB2DPoint(0).getY());
aTempB2DPolygon.translate(fDx, fDy);
aNewB2DPolygon.append(aTempB2DPolygon);
}
rSrcPt += 2;
}
} break;
case ELLIPTICALQUADRANTX : case ELLIPTICALQUADRANTY :
{ if (nPntCount && (rSrcPt < nCoordSize))
{ // The arc starts at the previous point and ends at the point given in the parameter.
basegfx::B2DPoint aStart;
basegfx::B2DPoint aEnd;
sal_uInt16 i = 0; if (rSrcPt)
{
aStart = GetPointAsB2DPoint(m_seqCoordinates[rSrcPt - 1], true, true);
} else
{ // no previous point, path is ill-structured. But we want to show as much as possible. // Thus make a moveTo to the point given as parameter and continue from there.
aStart = GetPointAsB2DPoint(m_seqCoordinates[static_cast<sal_uInt16>(rSrcPt)], true, true);
aNewB2DPolygon.append(aStart);
rSrcPt++;
i++;
} // If there are several points, then the direction changes with every point. bool bIsXDirection(nCommand == ELLIPTICALQUADRANTX);
basegfx::B2DPolygon aArc; for ( ; ( i < nPntCount ) && ( rSrcPt < nCoordSize ); i++ )
{
aEnd = GetPointAsB2DPoint(m_seqCoordinates[rSrcPt], true, true);
basegfx::B2DPoint aCenter; double fRadiusX = fabs(aEnd.getX() - aStart.getX()); double fRadiusY = fabs(aEnd.getY() - aStart.getY()); if (bIsXDirection)
{
aCenter = basegfx::B2DPoint(aStart.getX(),aEnd.getY()); if (aEnd.getX()<aStart.getX())
{ if (aEnd.getY()<aStart.getY()) // left, up
{
aArc = basegfx::utils::createPolygonFromEllipseSegment(aCenter, fRadiusX, fRadiusY, M_PI_2, M_PI);
} else// left, down
{
aArc = basegfx::utils::createPolygonFromEllipseSegment(aCenter, fRadiusX, fRadiusY, M_PI, 1.5*M_PI);
aArc.flip();
}
} else// aEnd.getX()>=aStart.getX()
{ if (aEnd.getY()<aStart.getY()) // right, up
{
aArc = basegfx::utils::createPolygonFromEllipseSegment(aCenter, fRadiusX, fRadiusY, 0.0, M_PI_2);
aArc.flip();
} else// right, down
{
aArc = basegfx::utils::createPolygonFromEllipseSegment(aCenter, fRadiusX, fRadiusY, 1.5*M_PI, 2*M_PI);
}
}
} else// y-direction
{
aCenter = basegfx::B2DPoint(aEnd.getX(),aStart.getY()); if (aEnd.getX()<aStart.getX())
{ if (aEnd.getY()<aStart.getY()) // up, left
{
aArc = basegfx::utils::createPolygonFromEllipseSegment(aCenter, fRadiusX, fRadiusY, 1.5*M_PI, 2*M_PI);
aArc.flip();
} else// down, left
{
aArc = basegfx::utils::createPolygonFromEllipseSegment(aCenter, fRadiusX, fRadiusY, 0.0, M_PI_2);
}
} else// aEnd.getX()>=aStart.getX()
{ if (aEnd.getY()<aStart.getY()) // up, right
{
aArc = basegfx::utils::createPolygonFromEllipseSegment(aCenter, fRadiusX, fRadiusY, M_PI, 1.5*M_PI);
} else// down, right
{
aArc = basegfx::utils::createPolygonFromEllipseSegment(aCenter, fRadiusX, fRadiusY, M_PI_2, M_PI);
aArc.flip();
}
}
}
aNewB2DPolygon.append(aArc);
rSrcPt++;
bIsXDirection = !bIsXDirection;
aStart = aEnd;
}
} // else error in path syntax, do nothing
} break;
if(aNewB2DPolygon.count() > 1)
{ // #i76201# Add conversion to closed polygon when first and last points are equal
basegfx::utils::checkClosed(aNewB2DPolygon);
aNewB2DPolyPolygon.append(aNewB2DPolygon);
}
if(!bNoStroke)
{ // there is no reason to use OBJ_PLIN here when the polygon is actually closed, // the non-fill is defined by XFILL_NONE. Since SdrPathObj::ImpForceKind() needs // to correct the polygon (here: open it) using the type, the last edge may get lost. // Thus, use a type that fits the polygon
rtl::Reference<SdrPathObj> pStroke(new SdrPathObj(
mrSdrObjCustomShape.getSdrModelFromSdrObject(),
aNewB2DPolyPolygon.isClosed() ? SdrObjKind::Polygon : SdrObjKind::PolyLine,
aNewB2DPolyPolygon));
SfxItemSet aTempSet(*this);
aTempSet.Put(makeSdrShadowItem(false));
aTempSet.Put(XFillStyleItem(drawing::FillStyle_NONE));
pStroke->SetMergedItemSet(aTempSet);
rObjectList.push_back(std::pair< rtl::Reference<SdrPathObj>, double >(std::move(pStroke), dBrightness));
}
} else
{
rtl::Reference<SdrPathObj> pObj;
SfxItemSet aTempSet(*this);
aTempSet.Put(makeSdrShadowItem(false));
if(bNoFill)
{ // see comment above about OBJ_PLIN
pObj = new SdrPathObj(
mrSdrObjCustomShape.getSdrModelFromSdrObject(),
aNewB2DPolyPolygon.isClosed() ? SdrObjKind::Polygon : SdrObjKind::PolyLine,
aNewB2DPolyPolygon);
aTempSet.Put(XFillStyleItem(drawing::FillStyle_NONE));
} else
{
aNewB2DPolyPolygon.setClosed(true);
pObj = new SdrPathObj(
mrSdrObjCustomShape.getSdrModelFromSdrObject(),
SdrObjKind::Polygon,
std::move(aNewB2DPolyPolygon));
}
staticvoid CorrectCalloutArrows(
MSO_SPT eSpType,
sal_uInt32 nLineObjectCount,
std::vector< std::pair< rtl::Reference<SdrPathObj>, double> >& vObjectList )
{ bool bAccent = false; switch( eSpType )
{ case mso_sptCallout1 : case mso_sptBorderCallout1 : case mso_sptCallout90 : case mso_sptBorderCallout90 : default: break;
case mso_sptAccentCallout1 : case mso_sptAccentBorderCallout1 : case mso_sptAccentCallout90 : case mso_sptAccentBorderCallout90 :
{
sal_uInt32 nLine = 0;
// OperationSmiley: when we have access to the SdrObjCustomShape and the // CustomShape is built with more than a single filled Geometry, use it // to define that all helper geometries defined here (SdrObjects currently) // will use the same FillGeometryDefinition (from the referenced SdrObjCustomShape). // This will all same-filled objects look like filled smoothly with the same style.
pObj->setFillGeometryDefiningShape(&mrSdrObjCustomShape);
}
}
// #i88870# correct line arrows for callouts if ( nLineObjectCount )
{
CorrectCalloutArrows(
m_eSpType,
nLineObjectCount,
vObjectList);
}
// sort objects so that filled ones are in front. Necessary // for some strange objects if(bSortFilledObjectsToBack)
{
std::vector< std::pair< rtl::Reference<SdrPathObj>, double> > vTempList;
vTempList.reserve(vObjectList.size());
for ( std::pair< rtl::Reference<SdrPathObj>, double >& rCandidate : vObjectList )
{
SdrPathObj* pObj(rCandidate.first.get()); if ( !pObj->IsLine() )
vTempList.push_back(std::move(rCandidate));
}
for ( std::pair< rtl::Reference<SdrPathObj>, double >& rCandidate : vObjectList )
{ if ( rCandidate.first )
vTempList.push_back(std::move(rCandidate));
}
vObjectList = std::move(vTempList);
}
}
}
// #i37011# if(!vObjectList.empty())
{ // copy remaining objects to pRet if(vObjectList.size() > 1)
{
pRet = new SdrObjGroup(mrSdrObjCustomShape.getSdrModelFromSdrObject());
if ( m_eSpType == mso_sptRectangle )
{
pRet = new SdrRectObj(mrSdrObjCustomShape.getSdrModelFromSdrObject(), m_aLogicRect);
pRet->SetMergedItemSet( *this );
} if ( !pRet )
pRet = CreatePathObj( bLineGeometryNeededOnly );
return pRet;
}
/** Set the stylesheet immediately after creation, to avoid unnecessary stylesheet adding and removing. */
rtl::Reference<SdrObject> EnhancedCustomShape2d::CreateObject( bool bLineGeometryNeededOnly, SfxStyleSheet* pNewStyleSheet )
{
rtl::Reference<SdrObject> pRet;
if ( m_eSpType == mso_sptRectangle )
{
pRet = new SdrRectObj(mrSdrObjCustomShape.getSdrModelFromSdrObject(), m_aLogicRect);
pRet->NbcSetStyleSheet(pNewStyleSheet, true);
pRet->SetMergedItemSet( *this );
} else
{
pRet = CreatePathObj( bLineGeometryNeededOnly ); if (pRet)
pRet->NbcSetStyleSheet(pNewStyleSheet, true);
}
return pRet;
}
static SdrEscapeDirection lcl_GetEscapeDirection(sal_Int32 nDirection)
{ switch (nDirection)
{ case 1: return SdrEscapeDirection::LEFT; case 2: return SdrEscapeDirection::RIGHT; case 3: return SdrEscapeDirection::TOP; case 4: return SdrEscapeDirection::BOTTOM; default: return SdrEscapeDirection::SMART;
}
}
void EnhancedCustomShape2d::ApplyGluePoints(SdrObject* pObj)
{ if ( !pObj ) return;
SdrEscapeDirection aDirection = SdrEscapeDirection::SMART; for (size_t i = 0; i < m_seqGluePoints.size(); i++)
{
EnhancedCustomShapeParameterPair aGluePointPair = m_seqGluePoints[i]; if (m_seqGluePointLeavingDirections.hasElements())
{
sal_Int32 aGluePointLeavingDirection = m_seqGluePointLeavingDirections[i];
aDirection = lcl_GetEscapeDirection(aGluePointLeavingDirection);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.