oox: export custom-shape equations and handles to DrawingML custGeom
A custom shape that has no matching OOXML preset is exported as
<a:custGeom>. Until now that path baked every coordinate to a fixed
number and wrote empty <a:avLst> and <a:ahLst>, so the shape lost its
adjustment values and adjust handles: it became frozen and could no
longer be edited by its control points after saving to docx/pptx/xlsx.
Convert the shape's ODF "Equations" to DrawingML guide formulas by
walking the parsed expression tree (FunctionParser::parseFunction). At
that level the original structure is intact, which lets the export:
- emit <a:gdLst> guides for the equations, keeping width/height symbolic
("w"/"h") so the geometry scales correctly on resize;
- express the trigonometric functions (sin/cos/tan): the angle-conversion
idiom (X*pi/180 or pi*X/10800000) is recognised and reconciled to
OOXML's 60000ths of a degree, for both trig arguments and standalone
angle values such as arc parameters;
- declare the referenced adjustment values in <a:avLst> ("adj1", ...) so
the guides stay driven by the shape's handles;
- emit an <a:ahXY> handle for each ODF handle that drives an adjustment
value, referencing the matching adjustment guide;
- reference an equation from a path coordinate (or handle) by the token
that holds its value.
svx gains two small ExpressionNode accessors (getChild/getIndex) so the
expression tree can be walked from the export filter.
Equations or handles that cannot be expressed yet (raw-radians angles
without the pi/const idiom, atan2/cat2/sat2, ellipse, polar handles) fall
back to resolved coordinates, as before.
Also update the now-outdated comment in ShapeExport that claimed ODF
equations cannot be converted to DrawingML. Adds a test on a symbol shape
(tdf#142603) and a circular arrow (tdf#157289 trig guides), and updates
testFlipAndRotateCustomShape / testEquationGuidesExport for the new
parametric output.
Signed-off-by: Andras Timar <andras.timar@collabora.com>
Change-Id: I21cf73df018417fcfe9c2f9ecd185c0ac29a198d
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/4464
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 40967878aeaee989216b6e0cfd6cf81af4a8b1d8)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/206685
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
committed by
Xisco Fauli
parent
6e0dd5492d
commit
fd02bc931f
@@ -365,13 +365,15 @@ protected:
|
||||
void WriteSoftEdgeEffect(const css::uno::Reference<css::beans::XPropertySet>& rXPropSet);
|
||||
void WriteCustomGeometryPoint(const css::drawing::EnhancedCustomShapeParameterPair& rParamPair,
|
||||
const EnhancedCustomShape2d& rCustomShape2d,
|
||||
const bool bReplaceGeoWidth, const bool bReplaceGeoHeight);
|
||||
const bool bReplaceGeoWidth, const bool bReplaceGeoHeight,
|
||||
const std::vector<OString>* pCoordGuides = nullptr);
|
||||
bool WriteCustomGeometrySegment(
|
||||
const sal_Int16 eCommand, const sal_Int32 nCount,
|
||||
const css::uno::Sequence<css::drawing::EnhancedCustomShapeParameterPair>& rPairs,
|
||||
sal_Int32& rnPairIndex, double& rfCurrentX, double& rfCurrentY, bool& rbCurrentValid,
|
||||
const EnhancedCustomShape2d& rCustomShape2d,
|
||||
const bool bReplaceGeoWidth, const bool bReplaceGeoHeight);
|
||||
const bool bReplaceGeoWidth, const bool bReplaceGeoHeight,
|
||||
const std::vector<OString>* pCoordGuides = nullptr);
|
||||
|
||||
public:
|
||||
OOX_DLLPUBLIC DrawingML(::sax_fastparser::FSHelperPtr pFS, ::oox::core::XmlFilterBase* pFB, DocumentType eDocumentType = DOCUMENT_PPTX, DMLTextExport* pTextExport = nullptr);
|
||||
|
||||
@@ -162,6 +162,18 @@ public:
|
||||
*/
|
||||
virtual ExpressionFunct getType() const = 0;
|
||||
|
||||
/** Child sub-expression by index (0-based), or nullptr if out of range.
|
||||
|
||||
Lets a caller walk the expression tree: unary nodes have child 0, binary
|
||||
nodes children 0 and 1, the ternary "if" node children 0..2. Leaf nodes
|
||||
(constants, adjustment/equation/enum references) have no children.
|
||||
*/
|
||||
virtual const ExpressionNode* getChild(sal_Int32 /*nIndex*/) const { return nullptr; }
|
||||
|
||||
/** Index of an adjustment ($n) or equation (?fn) reference, else -1.
|
||||
*/
|
||||
virtual sal_Int32 getIndex() const { return -1; }
|
||||
|
||||
/** Operator to retrieve the ms version of expression
|
||||
*/
|
||||
virtual css::drawing::EnhancedCustomShapeParameter fillNode(
|
||||
|
||||
Binary file not shown.
@@ -139,6 +139,29 @@ CPPUNIT_TEST_FIXTURE(Test, testDmlGroupshapePolygon)
|
||||
assertXPath(pXmlDoc, "//wps:spPr/a:xfrm/a:ext", "cx", u"5328360");
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(Test, testCustomShapeAdjustHandleExport)
|
||||
{
|
||||
// tdf#142603: a custom shape that is exported as <a:custGeom> must keep its
|
||||
// adjustment handle so it stays editable. The handle (<a:ahXY>) references an
|
||||
// adjustment guide declared in <a:avLst>, which the geometry formulas
|
||||
// (<a:gdLst>) use in turn. Without the fix <a:ahLst> and <a:avLst> were empty
|
||||
// and the shape was frozen after export.
|
||||
loadFromFile(u"tdf142603.odt");
|
||||
save(TestFilter::DOCX);
|
||||
xmlDocUniquePtr pXmlDoc = parseExport(u"word/document.xml"_ustr);
|
||||
|
||||
// Pick the shape (a "moon") whose handle drives adjustment adj1 in X.
|
||||
OString aShape
|
||||
= "(//wps:wsp[.//a:custGeom/a:ahLst/a:ahXY/@gdRefX])[1]/wps:spPr/a:custGeom"_ostr;
|
||||
// The adjustment value is declared in the adjust-value list.
|
||||
assertXPath(pXmlDoc, aShape + "/a:avLst/a:gd[@name='adj1']", 1);
|
||||
// A geometry guide uses it (so changing it re-evaluates the geometry).
|
||||
assertXPath(pXmlDoc, aShape + "/a:gdLst/a:gd[contains(@fmla,'adj1')][1]", 1);
|
||||
// The handle drives adj1 and is positioned at it.
|
||||
assertXPath(pXmlDoc, aShape + "/a:ahLst/a:ahXY", "gdRefX", u"adj1");
|
||||
assertXPath(pXmlDoc, aShape + "/a:ahLst/a:ahXY/a:pos", "x", u"adj1");
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(Test, testCustomShapeArrowExport)
|
||||
{
|
||||
// Given a document with a few different kinds of arrow shapes in it:
|
||||
@@ -1326,6 +1349,14 @@ CPPUNIT_TEST_FIXTURE(Test, testTdf157289CircularArrowExport)
|
||||
xmlDocUniquePtr pXmlDoc = parseExport(u"ppt/slides/slide1.xml"_ustr);
|
||||
assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo[1]", "wR", u"6750");
|
||||
assertXPath(pXmlDoc, "//a:pathLst/a:path/a:arcTo[1]", "hR", u"6750");
|
||||
|
||||
// The shape's trigonometric equations are exported as <a:gd> guides: the angle
|
||||
// adjustment (degrees) is converted to OOXML's 60000ths of a degree (x*10800000/180)
|
||||
// and then used by a sin/cos guide.
|
||||
OString aGdLst = "//a:custGeom/a:gdLst"_ostr;
|
||||
CPPUNIT_ASSERT(countXPathNodes(pXmlDoc, aGdLst + "/a:gd[@fmla='*/ adj1 10800000 180']") > 0);
|
||||
CPPUNIT_ASSERT(countXPathNodes(pXmlDoc, aGdLst + "/a:gd[starts-with(@fmla,'sin ')]") > 0);
|
||||
CPPUNIT_ASSERT(countXPathNodes(pXmlDoc, aGdLst + "/a:gd[starts-with(@fmla,'cos ')]") > 0);
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(Test, testTdf134401_ExportAutoGrowToTextWordWrap)
|
||||
|
||||
+488
-17
@@ -40,6 +40,7 @@
|
||||
#include <sax/fastattribs.hxx>
|
||||
#include <comphelper/diagnose_ex.hxx>
|
||||
#include <comphelper/processfactory.hxx>
|
||||
#include <comphelper/sequenceashashmap.hxx>
|
||||
#include <comphelper/string.hxx>
|
||||
#include <i18nlangtag/languagetag.hxx>
|
||||
#include <basegfx/matrix/b2dhommatrixtools.hxx>
|
||||
@@ -58,6 +59,7 @@
|
||||
#include <com/sun/star/awt/FontUnderline.hpp>
|
||||
#include <com/sun/star/awt/Gradient.hpp>
|
||||
#include <com/sun/star/awt/Gradient2.hpp>
|
||||
#include <com/sun/star/beans/PropertyValues.hpp>
|
||||
#include <com/sun/star/beans/XPropertySet.hpp>
|
||||
#include <com/sun/star/beans/XPropertyState.hpp>
|
||||
#include <com/sun/star/beans/XPropertySetInfo.hpp>
|
||||
@@ -137,6 +139,7 @@
|
||||
#include <editeng/unonrule.hxx>
|
||||
#include <docmodel/uno/UnoComplexColor.hxx>
|
||||
#include <svx/svdoashp.hxx>
|
||||
#include <svx/EnhancedCustomShapeFunctionParser.hxx>
|
||||
#include <svx/svdomedia.hxx>
|
||||
#include <svx/svdtrans.hxx>
|
||||
#include <svx/unoshape.hxx>
|
||||
@@ -5065,6 +5068,416 @@ OUString GetFormula(const OUString& sEquation)
|
||||
return OUString();
|
||||
}
|
||||
|
||||
// Convert a custom shape's ODF equations directly from the parsed expression tree
|
||||
// into OOXML guide formulas. Working from the expression tree (rather than the
|
||||
// flattened binary form the DOC filter uses) keeps width/height symbolic ("w"/"h")
|
||||
// and lets us express the trigonometric functions, because the angle-conversion
|
||||
// idiom is still visible; the angle unit is reconciled to OOXML's 60000ths of a
|
||||
// degree.
|
||||
using EnhancedCustomShape::ExpressionNode;
|
||||
using ExpressionFunct = EnhancedCustomShape::ExpressionFunct;
|
||||
|
||||
// Carries the guides built so far while one expression tree is walked.
|
||||
struct OoxmlEqContext
|
||||
{
|
||||
std::vector<Guide>& rGuides; // <a:gdLst> guides, in dependency order
|
||||
std::set<sal_Int32>& rUsedAdjustments; // adjustment indices referenced
|
||||
const std::vector<OString>& rEqNames; // source equation index -> its OOXML token
|
||||
const std::vector<bool>& rEqIsAngle; // source equation index -> token is an angle (60000ths)
|
||||
bool bFailed = false;
|
||||
|
||||
OString addGuide(const OString& rFormula)
|
||||
{
|
||||
OString aName = "f" + OString::number(rGuides.size());
|
||||
rGuides.push_back({ aName, rFormula });
|
||||
return aName;
|
||||
}
|
||||
};
|
||||
|
||||
OString lcl_EmitOoxmlNode(const ExpressionNode* pNode, OoxmlEqContext& rCtx);
|
||||
|
||||
// Is p the sub-expression "pi / <const>"? Returns the constant in rConst.
|
||||
bool lcl_IsPiOverConst(const ExpressionNode* p, double& rConst)
|
||||
{
|
||||
if (p && p->getType() == ExpressionFunct::BinaryDiv)
|
||||
{
|
||||
const ExpressionNode* pNum = p->getChild(0);
|
||||
const ExpressionNode* pDen = p->getChild(1);
|
||||
if (pNum && pDen && pNum->getType() == ExpressionFunct::EnumPi && pDen->isConstant())
|
||||
{
|
||||
rConst = (*pDen)();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// If pArg is a radians angle of the form X*(pi/c) or (pi*X)/c, emit it converted
|
||||
// to OOXML 60000ths of a degree (X*10800000/c) and return true; else return false
|
||||
// (leaving rCtx untouched). This is the only way to express an angle in OOXML,
|
||||
// whose guide formulas have no pi constant to cancel against. Used both for
|
||||
// standalone angle values (e.g. arc parameters) and trigonometric arguments.
|
||||
bool lcl_TryEmitAngle60000(const ExpressionNode* pArg, OoxmlEqContext& rCtx, OString& rResult)
|
||||
{
|
||||
// A reference to an equation that is itself an angle is already in 60000ths.
|
||||
if (pArg && pArg->getType() == ExpressionFunct::EnumEquation)
|
||||
{
|
||||
const sal_Int32 nEquation = pArg->getIndex();
|
||||
if (nEquation >= 0 && o3tl::make_unsigned(nEquation) < rCtx.rEqIsAngle.size()
|
||||
&& rCtx.rEqIsAngle[nEquation] && !rCtx.rEqNames[nEquation].isEmpty())
|
||||
{
|
||||
rResult = rCtx.rEqNames[nEquation];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const ExpressionNode* pX = nullptr;
|
||||
double fConst = 0.0;
|
||||
if (pArg && pArg->getType() == ExpressionFunct::BinaryMul)
|
||||
{
|
||||
if (lcl_IsPiOverConst(pArg->getChild(0), fConst))
|
||||
pX = pArg->getChild(1);
|
||||
else if (lcl_IsPiOverConst(pArg->getChild(1), fConst))
|
||||
pX = pArg->getChild(0);
|
||||
}
|
||||
else if (pArg && pArg->getType() == ExpressionFunct::BinaryDiv)
|
||||
{
|
||||
const ExpressionNode* pNum = pArg->getChild(0);
|
||||
const ExpressionNode* pDen = pArg->getChild(1);
|
||||
if (pNum && pDen && pDen->isConstant() && pNum->getType() == ExpressionFunct::BinaryMul)
|
||||
{
|
||||
const ExpressionNode* pA = pNum->getChild(0);
|
||||
const ExpressionNode* pB = pNum->getChild(1);
|
||||
if (pA && pA->getType() == ExpressionFunct::EnumPi)
|
||||
pX = pB;
|
||||
else if (pB && pB->getType() == ExpressionFunct::EnumPi)
|
||||
pX = pA;
|
||||
if (pX)
|
||||
fConst = (*pDen)();
|
||||
}
|
||||
}
|
||||
if (!pX || fConst == 0.0)
|
||||
return false;
|
||||
OString aX = lcl_EmitOoxmlNode(pX, rCtx);
|
||||
rResult = rCtx.addGuide("*/ " + aX + " 10800000 "
|
||||
+ OString::number(static_cast<sal_Int32>(std::lround(fConst))));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lcl_IsTrig(const ExpressionNode* p)
|
||||
{
|
||||
const ExpressionFunct e = p->getType();
|
||||
return e == ExpressionFunct::UnarySin || e == ExpressionFunct::UnaryCos
|
||||
|| e == ExpressionFunct::UnaryTan;
|
||||
}
|
||||
|
||||
// Emit "sin|cos|tan <mul> <angle>"; pMul is the multiplier in "mul*sin(x)", or null (1).
|
||||
OString lcl_EmitTrig(const ExpressionNode* pTrig, const ExpressionNode* pMul, OoxmlEqContext& rCtx)
|
||||
{
|
||||
OString aAngle;
|
||||
if (!lcl_TryEmitAngle60000(pTrig->getChild(0), rCtx, aAngle))
|
||||
{
|
||||
// Angle not in the pi/const idiom (e.g. raw radians) - not expressible.
|
||||
rCtx.bFailed = true;
|
||||
return "0"_ostr;
|
||||
}
|
||||
const char* pOp = pTrig->getType() == ExpressionFunct::UnarySin
|
||||
? "sin"
|
||||
: (pTrig->getType() == ExpressionFunct::UnaryCos ? "cos" : "tan");
|
||||
OString aMul = pMul ? lcl_EmitOoxmlNode(pMul, rCtx) : "1"_ostr;
|
||||
return rCtx.addGuide(OString::Concat(pOp) + " " + aMul + " " + aAngle);
|
||||
}
|
||||
|
||||
OString lcl_EmitOoxmlNode(const ExpressionNode* pNode, OoxmlEqContext& rCtx)
|
||||
{
|
||||
if (!pNode)
|
||||
{
|
||||
rCtx.bFailed = true;
|
||||
return "0"_ostr;
|
||||
}
|
||||
// A constant sub-expression collapses to its literal value.
|
||||
if (pNode->isConstant())
|
||||
return OString::number(static_cast<sal_Int32>(std::lround((*pNode)())));
|
||||
|
||||
switch (pNode->getType())
|
||||
{
|
||||
case ExpressionFunct::EnumAdjustment:
|
||||
{
|
||||
const sal_Int32 nAdjustment = pNode->getIndex();
|
||||
rCtx.rUsedAdjustments.insert(nAdjustment);
|
||||
return "adj" + OString::number(nAdjustment + 1);
|
||||
}
|
||||
case ExpressionFunct::EnumEquation:
|
||||
{
|
||||
const sal_Int32 nEquation = pNode->getIndex();
|
||||
if (nEquation >= 0 && o3tl::make_unsigned(nEquation) < rCtx.rEqNames.size()
|
||||
&& !rCtx.rEqNames[nEquation].isEmpty())
|
||||
return rCtx.rEqNames[nEquation];
|
||||
rCtx.bFailed = true;
|
||||
return "0"_ostr;
|
||||
}
|
||||
case ExpressionFunct::EnumWidth:
|
||||
case ExpressionFunct::EnumLogWidth:
|
||||
return "w"_ostr;
|
||||
case ExpressionFunct::EnumHeight:
|
||||
case ExpressionFunct::EnumLogHeight:
|
||||
return "h"_ostr;
|
||||
case ExpressionFunct::EnumLeft:
|
||||
return "l"_ostr;
|
||||
case ExpressionFunct::EnumTop:
|
||||
return "t"_ostr;
|
||||
case ExpressionFunct::EnumRight:
|
||||
return "r"_ostr;
|
||||
case ExpressionFunct::EnumBottom:
|
||||
return "b"_ostr;
|
||||
case ExpressionFunct::BinaryPlus:
|
||||
return rCtx.addGuide("+- " + lcl_EmitOoxmlNode(pNode->getChild(0), rCtx) + " "
|
||||
+ lcl_EmitOoxmlNode(pNode->getChild(1), rCtx) + " 0");
|
||||
case ExpressionFunct::BinaryMinus:
|
||||
return rCtx.addGuide("+- " + lcl_EmitOoxmlNode(pNode->getChild(0), rCtx) + " 0 "
|
||||
+ lcl_EmitOoxmlNode(pNode->getChild(1), rCtx));
|
||||
case ExpressionFunct::BinaryMul:
|
||||
{
|
||||
OString aAngle;
|
||||
if (lcl_TryEmitAngle60000(pNode, rCtx, aAngle))
|
||||
return aAngle; // X*(pi/c) used as a standalone angle value
|
||||
const ExpressionNode* pA = pNode->getChild(0);
|
||||
const ExpressionNode* pB = pNode->getChild(1);
|
||||
if (lcl_IsTrig(pA))
|
||||
return lcl_EmitTrig(pA, pB, rCtx);
|
||||
if (lcl_IsTrig(pB))
|
||||
return lcl_EmitTrig(pB, pA, rCtx);
|
||||
return rCtx.addGuide("*/ " + lcl_EmitOoxmlNode(pA, rCtx) + " "
|
||||
+ lcl_EmitOoxmlNode(pB, rCtx) + " 1");
|
||||
}
|
||||
case ExpressionFunct::BinaryDiv:
|
||||
{
|
||||
OString aAngle;
|
||||
if (lcl_TryEmitAngle60000(pNode, rCtx, aAngle))
|
||||
return aAngle; // (pi*X)/c used as a standalone angle value
|
||||
return rCtx.addGuide("*/ " + lcl_EmitOoxmlNode(pNode->getChild(0), rCtx) + " 1 "
|
||||
+ lcl_EmitOoxmlNode(pNode->getChild(1), rCtx));
|
||||
}
|
||||
case ExpressionFunct::BinaryMin:
|
||||
return rCtx.addGuide("min " + lcl_EmitOoxmlNode(pNode->getChild(0), rCtx) + " "
|
||||
+ lcl_EmitOoxmlNode(pNode->getChild(1), rCtx));
|
||||
case ExpressionFunct::BinaryMax:
|
||||
return rCtx.addGuide("max " + lcl_EmitOoxmlNode(pNode->getChild(0), rCtx) + " "
|
||||
+ lcl_EmitOoxmlNode(pNode->getChild(1), rCtx));
|
||||
case ExpressionFunct::UnaryNeg:
|
||||
return rCtx.addGuide("*/ " + lcl_EmitOoxmlNode(pNode->getChild(0), rCtx) + " -1 1");
|
||||
case ExpressionFunct::UnaryAbs:
|
||||
return rCtx.addGuide("abs " + lcl_EmitOoxmlNode(pNode->getChild(0), rCtx));
|
||||
case ExpressionFunct::UnarySqrt:
|
||||
return rCtx.addGuide("sqrt " + lcl_EmitOoxmlNode(pNode->getChild(0), rCtx));
|
||||
case ExpressionFunct::TernaryIf:
|
||||
return rCtx.addGuide("?: " + lcl_EmitOoxmlNode(pNode->getChild(0), rCtx) + " "
|
||||
+ lcl_EmitOoxmlNode(pNode->getChild(1), rCtx) + " "
|
||||
+ lcl_EmitOoxmlNode(pNode->getChild(2), rCtx));
|
||||
case ExpressionFunct::UnarySin:
|
||||
case ExpressionFunct::UnaryCos:
|
||||
case ExpressionFunct::UnaryTan:
|
||||
return lcl_EmitTrig(pNode, nullptr, rCtx);
|
||||
default:
|
||||
// atan2/cat2/sat2, bare pi, stretch/has-fill flags: not expressible yet.
|
||||
rCtx.bFailed = true;
|
||||
return "0"_ostr;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert all of a shape's ODF "Equations" to OOXML guides. On success rGuides
|
||||
// holds the <a:gdLst> guides, rUsedAdjustments the adjustments for <a:avLst>, and
|
||||
// rEqNames maps each source equation to the OOXML token (guide name or literal)
|
||||
// that holds its value. Returns false if any equation uses a construct we cannot
|
||||
// express, so the caller can fall back to resolved coordinates.
|
||||
bool lcl_CreateOoxmlGuides(const EnhancedCustomShape2d& rCustomShape2d,
|
||||
const css::uno::Sequence<OUString>& rEquationStrings,
|
||||
std::vector<Guide>& rGuides, std::set<sal_Int32>& rUsedAdjustments,
|
||||
std::vector<OString>& rEqNames)
|
||||
{
|
||||
rEqNames.assign(rEquationStrings.getLength(), OString());
|
||||
std::vector<bool> aEqIsAngle(rEquationStrings.getLength(), false);
|
||||
OoxmlEqContext aContext{ rGuides, rUsedAdjustments, rEqNames, aEqIsAngle };
|
||||
for (sal_Int32 i = 0; i < rEquationStrings.getLength(); ++i)
|
||||
{
|
||||
std::shared_ptr<ExpressionNode> pNode;
|
||||
try
|
||||
{
|
||||
pNode = EnhancedCustomShape::FunctionParser::parseFunction(rEquationStrings[i],
|
||||
rCustomShape2d);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
aContext.bFailed = false;
|
||||
// An equation that is itself an angle (X*pi/c) is converted to 60000ths of
|
||||
// a degree and remembered, so trig functions referencing it work.
|
||||
OString aToken;
|
||||
if (lcl_TryEmitAngle60000(pNode.get(), aContext, aToken))
|
||||
aEqIsAngle[i] = true;
|
||||
else
|
||||
aToken = lcl_EmitOoxmlNode(pNode.get(), aContext);
|
||||
if (aContext.bFailed)
|
||||
return false;
|
||||
rEqNames[i] = std::move(aToken);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Format a single EnhancedCustomShapeParameter (a handle position or range
|
||||
// value) as an OOXML adjust-handle token: an adjustment as "adj<n>", an equation
|
||||
// result as its guide "f<n>", the built-in box edges as l/t/r/b, anything else
|
||||
// as its literal value.
|
||||
OString lcl_GetHandleValue(const css::drawing::EnhancedCustomShapeParameter& rParam,
|
||||
const std::vector<OString>* pEqNames)
|
||||
{
|
||||
sal_Int32 nValue = 0;
|
||||
if (double fValue = 0.0; rParam.Value >>= fValue)
|
||||
nValue = std::lround(fValue);
|
||||
else
|
||||
rParam.Value >>= nValue;
|
||||
|
||||
switch (rParam.Type)
|
||||
{
|
||||
case css::drawing::EnhancedCustomShapeParameterType::ADJUSTMENT:
|
||||
return "adj" + OString::number(nValue + 1);
|
||||
case css::drawing::EnhancedCustomShapeParameterType::EQUATION:
|
||||
if (pEqNames && nValue >= 0 && o3tl::make_unsigned(nValue) < pEqNames->size()
|
||||
&& !(*pEqNames)[nValue].isEmpty())
|
||||
return (*pEqNames)[nValue];
|
||||
return OString::number(nValue);
|
||||
case css::drawing::EnhancedCustomShapeParameterType::LEFT:
|
||||
return "l"_ostr;
|
||||
case css::drawing::EnhancedCustomShapeParameterType::TOP:
|
||||
return "t"_ostr;
|
||||
case css::drawing::EnhancedCustomShapeParameterType::RIGHT:
|
||||
return "r"_ostr;
|
||||
case css::drawing::EnhancedCustomShapeParameterType::BOTTOM:
|
||||
return "b"_ostr;
|
||||
default:
|
||||
return OString::number(nValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Write the <a:ahLst> adjust handles. An ODF handle whose position drives an
|
||||
// adjustment value becomes an <a:ahXY> handle that references the matching
|
||||
// adjustment guide ("adj<n>"), so the handle stays draggable after export. Only
|
||||
// adjustments declared in <a:avLst> (rUsedAdjustments) are referenced; polar
|
||||
// handles are not converted yet.
|
||||
void lcl_WriteAdjustHandles(const sax_fastparser::FSHelperPtr& rFS,
|
||||
const uno::Sequence<beans::PropertyValues>& rHandles,
|
||||
const std::vector<OString>* pEqNames,
|
||||
const std::set<sal_Int32>& rUsedAdjustments)
|
||||
{
|
||||
struct XYHandle
|
||||
{
|
||||
OString aPosX, aPosY, aGdRefX, aMinX, aMaxX, aGdRefY, aMinY, aMaxY;
|
||||
};
|
||||
auto getAdjustment
|
||||
= [&rUsedAdjustments](const css::drawing::EnhancedCustomShapeParameter& rParam,
|
||||
sal_Int32& rIndex) {
|
||||
if (rParam.Type != css::drawing::EnhancedCustomShapeParameterType::ADJUSTMENT)
|
||||
return false;
|
||||
rIndex = -1;
|
||||
rParam.Value >>= rIndex;
|
||||
return rIndex >= 0 && rUsedAdjustments.count(rIndex) > 0;
|
||||
};
|
||||
|
||||
std::vector<XYHandle> aHandlesOut;
|
||||
for (const beans::PropertyValues& rHandle : rHandles)
|
||||
{
|
||||
comphelper::SequenceAsHashMap aMap(rHandle);
|
||||
if (aMap.find(u"Polar"_ustr) != aMap.end())
|
||||
continue; // polar handles are not converted yet
|
||||
|
||||
css::drawing::EnhancedCustomShapeParameterPair aPosition;
|
||||
auto itPos = aMap.find(u"Position"_ustr);
|
||||
if (itPos == aMap.end() || !(itPos->second >>= aPosition))
|
||||
continue;
|
||||
|
||||
XYHandle aOut;
|
||||
aOut.aPosX = lcl_GetHandleValue(aPosition.First, pEqNames);
|
||||
aOut.aPosY = lcl_GetHandleValue(aPosition.Second, pEqNames);
|
||||
|
||||
auto readRange = [&aMap, pEqNames](std::u16string_view rName, OString& rOut) {
|
||||
css::drawing::EnhancedCustomShapeParameter aParam;
|
||||
if (auto it = aMap.find(OUString(rName)); it != aMap.end() && (it->second >>= aParam))
|
||||
rOut = lcl_GetHandleValue(aParam, pEqNames);
|
||||
};
|
||||
|
||||
sal_Int32 nAdjustment = -1;
|
||||
if (getAdjustment(aPosition.First, nAdjustment))
|
||||
{
|
||||
aOut.aGdRefX = "adj" + OString::number(nAdjustment + 1);
|
||||
readRange(u"RangeXMinimum", aOut.aMinX);
|
||||
readRange(u"RangeXMaximum", aOut.aMaxX);
|
||||
}
|
||||
if (getAdjustment(aPosition.Second, nAdjustment))
|
||||
{
|
||||
aOut.aGdRefY = "adj" + OString::number(nAdjustment + 1);
|
||||
readRange(u"RangeYMinimum", aOut.aMinY);
|
||||
readRange(u"RangeYMaximum", aOut.aMaxY);
|
||||
}
|
||||
|
||||
// Only a handle that drives an adjustment value is useful.
|
||||
if (!aOut.aGdRefX.isEmpty() || !aOut.aGdRefY.isEmpty())
|
||||
aHandlesOut.push_back(aOut);
|
||||
}
|
||||
|
||||
if (aHandlesOut.empty())
|
||||
{
|
||||
rFS->singleElementNS(XML_a, XML_ahLst);
|
||||
return;
|
||||
}
|
||||
|
||||
rFS->startElementNS(XML_a, XML_ahLst);
|
||||
for (const XYHandle& rHandle : aHandlesOut)
|
||||
{
|
||||
rtl::Reference<sax_fastparser::FastAttributeList> xAttrs
|
||||
= sax_fastparser::FastSerializerHelper::createAttrList();
|
||||
if (!rHandle.aGdRefX.isEmpty())
|
||||
xAttrs->add(XML_gdRefX, rHandle.aGdRefX);
|
||||
if (!rHandle.aMinX.isEmpty())
|
||||
xAttrs->add(XML_minX, rHandle.aMinX);
|
||||
if (!rHandle.aMaxX.isEmpty())
|
||||
xAttrs->add(XML_maxX, rHandle.aMaxX);
|
||||
if (!rHandle.aGdRefY.isEmpty())
|
||||
xAttrs->add(XML_gdRefY, rHandle.aGdRefY);
|
||||
if (!rHandle.aMinY.isEmpty())
|
||||
xAttrs->add(XML_minY, rHandle.aMinY);
|
||||
if (!rHandle.aMaxY.isEmpty())
|
||||
xAttrs->add(XML_maxY, rHandle.aMaxY);
|
||||
rFS->startElementNS(XML_a, XML_ahXY, xAttrs);
|
||||
rFS->singleElementNS(XML_a, XML_pos, XML_x, rHandle.aPosX, XML_y, rHandle.aPosY);
|
||||
rFS->endElementNS(XML_a, XML_ahXY);
|
||||
}
|
||||
rFS->endElementNS(XML_a, XML_ahLst);
|
||||
}
|
||||
|
||||
// Format one path coordinate. When the coordinate is an equation, pCoordGuides
|
||||
// gives the token holding its value - a guide name ("f<n>") for a parametric
|
||||
// equation, or a plain number for a constant one - so the point follows the
|
||||
// guides. A stretched coordinate (StretchX/StretchY) keeps its resolved value,
|
||||
// because the guide does not carry the stretch adjustment.
|
||||
OString lcl_GetCustomGeometryCoordinate(const drawing::EnhancedCustomShapeParameter& rParam,
|
||||
const EnhancedCustomShape2d& rCustomShape2d,
|
||||
const std::vector<OString>* pCoordGuides,
|
||||
const bool bReplaceGeoWidth, const bool bReplaceGeoHeight)
|
||||
{
|
||||
if (pCoordGuides && !bReplaceGeoWidth && !bReplaceGeoHeight
|
||||
&& rParam.Type == css::drawing::EnhancedCustomShapeParameterType::EQUATION)
|
||||
{
|
||||
sal_Int32 nIndex = -1;
|
||||
rParam.Value >>= nIndex;
|
||||
if (nIndex >= 0 && o3tl::make_unsigned(nIndex) < pCoordGuides->size()
|
||||
&& !(*pCoordGuides)[nIndex].isEmpty())
|
||||
return (*pCoordGuides)[nIndex];
|
||||
}
|
||||
return OString::number(
|
||||
GetCustomGeometryPointValue(rParam, rCustomShape2d, bReplaceGeoWidth, bReplaceGeoHeight));
|
||||
}
|
||||
|
||||
void prepareGluePoints(std::vector<Guide>& rGuideList,
|
||||
const css::uno::Sequence<OUString>& aEquations,
|
||||
const uno::Sequence<drawing::EnhancedCustomShapeParameterPair>& rGluePoints,
|
||||
@@ -5305,9 +5718,50 @@ bool DrawingML::WriteCustomGeometry(
|
||||
std::vector<Guide> aGuideList; // for now only for <a:rect>
|
||||
prepareTextArea(aCustomShape2d, aGuideList, aTextAreaRect);
|
||||
prepareGluePoints(aGuideList, aEquationSeq, aGluePoints, nViewBoxWidth, nViewBoxHeight);
|
||||
|
||||
// Convert the shape's "Equations" into <a:gd> guides ("f0", "f1", ...) from the
|
||||
// parsed expression tree (keeps width/height symbolic and handles the
|
||||
// trigonometric functions). These are kept apart from aGuideList because they
|
||||
// reference each other by name, which IsValidOOXMLFormula() (used to validate
|
||||
// the text-area/glue-point guides below) does not accept.
|
||||
std::vector<Guide> aEquationGuides;
|
||||
std::set<sal_Int32> aUsedAdjustments;
|
||||
std::vector<OString> aEqNames;
|
||||
const bool bHaveEquationGuides = lcl_CreateOoxmlGuides(
|
||||
aCustomShape2d, aEquationSeq, aEquationGuides, aUsedAdjustments, aEqNames);
|
||||
// Declare the referenced adjustment values in <a:avLst> holding their current
|
||||
// value, so the equation guides stay driven by the shape's handles.
|
||||
std::vector<Guide> aAdjGuides;
|
||||
if (bHaveEquationGuides)
|
||||
{
|
||||
for (const sal_Int32 nAdjustment : aUsedAdjustments)
|
||||
{
|
||||
css::drawing::EnhancedCustomShapeParameter aParam;
|
||||
aParam.Type = css::drawing::EnhancedCustomShapeParameterType::ADJUSTMENT;
|
||||
aParam.Value <<= nAdjustment;
|
||||
double fValue = 0.0;
|
||||
aCustomShape2d.GetParameter(fValue, aParam, false, false);
|
||||
aAdjGuides.push_back({ "adj" + OString::number(nAdjustment + 1),
|
||||
"val " + OString::number(std::lround(fValue)) });
|
||||
}
|
||||
}
|
||||
// Path coordinates and handles reference an equation by the token (guide name,
|
||||
// literal, adjustment or w/h) holding its value.
|
||||
const std::vector<OString>* pEqNames = bHaveEquationGuides ? &aEqNames : nullptr;
|
||||
|
||||
mpFS->startElementNS(XML_a, XML_custGeom);
|
||||
mpFS->singleElementNS(XML_a, XML_avLst);
|
||||
if (aGuideList.empty())
|
||||
if (aAdjGuides.empty())
|
||||
{
|
||||
mpFS->singleElementNS(XML_a, XML_avLst);
|
||||
}
|
||||
else
|
||||
{
|
||||
mpFS->startElementNS(XML_a, XML_avLst);
|
||||
for (auto const& elem : aAdjGuides)
|
||||
mpFS->singleElementNS(XML_a, XML_gd, XML_name, elem.sName, XML_fmla, elem.sFormula);
|
||||
mpFS->endElementNS(XML_a, XML_avLst);
|
||||
}
|
||||
if (aGuideList.empty() && aEquationGuides.empty())
|
||||
{
|
||||
mpFS->singleElementNS(XML_a, XML_gdLst);
|
||||
}
|
||||
@@ -5319,9 +5773,26 @@ bool DrawingML::WriteCustomGeometry(
|
||||
assert(IsValidOOXMLFormula(OUString::fromUtf8(elem.sFormula)));
|
||||
mpFS->singleElementNS(XML_a, XML_gd, XML_name, elem.sName, XML_fmla, elem.sFormula);
|
||||
}
|
||||
for (auto const& elem : aEquationGuides)
|
||||
mpFS->singleElementNS(XML_a, XML_gd, XML_name, elem.sName, XML_fmla, elem.sFormula);
|
||||
mpFS->endElementNS(XML_a, XML_gdLst);
|
||||
}
|
||||
mpFS->singleElementNS(XML_a, XML_ahLst);
|
||||
|
||||
// Adjust handles. They reference the avLst/gdLst guides written above, so we
|
||||
// can only write them when the equation guides were created successfully.
|
||||
uno::Sequence<beans::PropertyValues> aHandles;
|
||||
if (bHaveEquationGuides)
|
||||
{
|
||||
auto pHandlesProp = std::find_if(
|
||||
std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq),
|
||||
[](const beans::PropertyValue& rProp) { return rProp.Name == "Handles"; });
|
||||
if (pHandlesProp != std::cend(*pGeometrySeq))
|
||||
pHandlesProp->Value >>= aHandles;
|
||||
}
|
||||
if (aHandles.hasElements())
|
||||
lcl_WriteAdjustHandles(mpFS, aHandles, pEqNames, aUsedAdjustments);
|
||||
else
|
||||
mpFS->singleElementNS(XML_a, XML_ahLst);
|
||||
|
||||
if (!aGuideList.empty())
|
||||
{
|
||||
@@ -5419,7 +5890,7 @@ bool DrawingML::WriteCustomGeometry(
|
||||
{
|
||||
bOK = WriteCustomGeometrySegment(rSegment.Command, k, aPairs, nPairIndex, fCurrentX,
|
||||
fCurrentY, bCurrentValid, aCustomShape2d,
|
||||
bReplaceGeoWidth, bReplaceGeoHeight);
|
||||
bReplaceGeoWidth, bReplaceGeoHeight, pEqNames);
|
||||
}
|
||||
} // end loop over all commands of subpath
|
||||
// finish this subpath in any case
|
||||
@@ -5444,7 +5915,7 @@ bool DrawingML::WriteCustomGeometrySegment(
|
||||
const uno::Sequence<css::drawing::EnhancedCustomShapeParameterPair>& rPairs,
|
||||
sal_Int32& rnPairIndex, double& rfCurrentX, double& rfCurrentY, bool& rbCurrentValid,
|
||||
const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth,
|
||||
const bool bReplaceGeoHeight)
|
||||
const bool bReplaceGeoHeight, const std::vector<OString>* pCoordGuides)
|
||||
{
|
||||
switch (eCommand)
|
||||
{
|
||||
@@ -5455,7 +5926,7 @@ bool DrawingML::WriteCustomGeometrySegment(
|
||||
|
||||
mpFS->startElementNS(XML_a, XML_moveTo);
|
||||
WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
|
||||
bReplaceGeoHeight);
|
||||
bReplaceGeoHeight, pCoordGuides);
|
||||
mpFS->endElementNS(XML_a, XML_moveTo);
|
||||
rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth,
|
||||
false);
|
||||
@@ -5476,14 +5947,14 @@ bool DrawingML::WriteCustomGeometrySegment(
|
||||
{
|
||||
mpFS->startElementNS(XML_a, XML_lnTo);
|
||||
WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
|
||||
bReplaceGeoHeight);
|
||||
bReplaceGeoHeight, pCoordGuides);
|
||||
mpFS->endElementNS(XML_a, XML_lnTo);
|
||||
}
|
||||
else
|
||||
{
|
||||
mpFS->startElementNS(XML_a, XML_moveTo);
|
||||
WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
|
||||
bReplaceGeoHeight);
|
||||
bReplaceGeoHeight, pCoordGuides);
|
||||
mpFS->endElementNS(XML_a, XML_moveTo);
|
||||
}
|
||||
rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth,
|
||||
@@ -5503,7 +5974,7 @@ bool DrawingML::WriteCustomGeometrySegment(
|
||||
for (sal_uInt8 i = 0; i <= 2; ++i)
|
||||
{
|
||||
WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth,
|
||||
bReplaceGeoHeight);
|
||||
bReplaceGeoHeight, pCoordGuides);
|
||||
}
|
||||
mpFS->endElementNS(XML_a, XML_cubicBezTo);
|
||||
rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth,
|
||||
@@ -5709,7 +6180,7 @@ bool DrawingML::WriteCustomGeometrySegment(
|
||||
// faulty path, but we continue with the target point
|
||||
mpFS->startElementNS(XML_a, XML_moveTo);
|
||||
WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
|
||||
bReplaceGeoHeight);
|
||||
bReplaceGeoHeight, pCoordGuides);
|
||||
mpFS->endElementNS(XML_a, XML_moveTo);
|
||||
}
|
||||
rfCurrentX = fX;
|
||||
@@ -5727,7 +6198,7 @@ bool DrawingML::WriteCustomGeometrySegment(
|
||||
for (sal_uInt8 i = 0; i < 2; ++i)
|
||||
{
|
||||
WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth,
|
||||
bReplaceGeoHeight);
|
||||
bReplaceGeoHeight, pCoordGuides);
|
||||
}
|
||||
mpFS->endElementNS(XML_a, XML_quadBezTo);
|
||||
rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 1].First, bReplaceGeoWidth,
|
||||
@@ -5777,14 +6248,14 @@ bool DrawingML::WriteCustomGeometrySegment(
|
||||
void DrawingML::WriteCustomGeometryPoint(
|
||||
const drawing::EnhancedCustomShapeParameterPair& rParamPair,
|
||||
const EnhancedCustomShape2d& rCustomShape2d, const bool bReplaceGeoWidth,
|
||||
const bool bReplaceGeoHeight)
|
||||
const bool bReplaceGeoHeight, const std::vector<OString>* pCoordGuides)
|
||||
{
|
||||
sal_Int32 nX
|
||||
= GetCustomGeometryPointValue(rParamPair.First, rCustomShape2d, bReplaceGeoWidth, false);
|
||||
sal_Int32 nY
|
||||
= GetCustomGeometryPointValue(rParamPair.Second, rCustomShape2d, false, bReplaceGeoHeight);
|
||||
OString sX = lcl_GetCustomGeometryCoordinate(rParamPair.First, rCustomShape2d, pCoordGuides,
|
||||
bReplaceGeoWidth, false);
|
||||
OString sY = lcl_GetCustomGeometryCoordinate(rParamPair.Second, rCustomShape2d, pCoordGuides,
|
||||
false, bReplaceGeoHeight);
|
||||
|
||||
mpFS->singleElementNS(XML_a, XML_pt, XML_x, OString::number(nX), XML_y, OString::number(nY));
|
||||
mpFS->singleElementNS(XML_a, XML_pt, XML_x, sX, XML_y, sY);
|
||||
}
|
||||
|
||||
void DrawingML::WriteEmptyCustomGeometry()
|
||||
|
||||
@@ -1103,8 +1103,10 @@ ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape )
|
||||
pFS->startElementNS(mnXmlNamespace, XML_spPr);
|
||||
|
||||
// we export non-primitive shapes to custom geometry
|
||||
// we also export non-ooxml shapes which have handles/equations to custom geometry, because
|
||||
// we cannot convert ODF equations to DrawingML equations. TODO: see what binary DOC export filter does.
|
||||
// we also export non-ooxml shapes which have handles/equations to custom geometry; their
|
||||
// ODF equations, adjustment values and handles are converted to DrawingML guides in
|
||||
// WriteCustomGeometry (a few constructs that cannot be expressed, e.g. polar handles, fall
|
||||
// back to resolved coordinates)
|
||||
// but our WritePolyPolygon()/WriteCustomGeometry() functions are incomplete, therefore we use a denylist
|
||||
// we use a allowlist for shapes where mapping to MSO preset shape is not optimal
|
||||
bool bCustGeom = true;
|
||||
|
||||
@@ -84,6 +84,24 @@ CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest3, testTdf169705_invalidValuesAOff)
|
||||
OUString::number(std::numeric_limits<sal_Int32>::max()));
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest3, testEquationGuidesExport)
|
||||
{
|
||||
// The shape's "Equations" are converted into <a:gd> guides ("f0", "f1", ...)
|
||||
// by reusing the same flattening the binary export filter uses.
|
||||
createSdImpressDoc("pptx/tdf165670.pptx");
|
||||
save(TestFilter::PPTX);
|
||||
|
||||
xmlDocUniquePtr pXmlDoc1 = parseExport(u"ppt/slides/slide1.xml"_ustr);
|
||||
OString aGdLst = "/p:sld/p:cSld/p:spTree/p:sp/p:spPr/a:custGeom/a:gdLst"_ostr;
|
||||
|
||||
// The equation guides follow the text-area (4) and glue-point (10) guides.
|
||||
// Width/height stay symbolic ("w"/"h") instead of being baked to a fixed value.
|
||||
assertXPath(pXmlDoc1, aGdLst + "/a:gd[@name='f0']", "fmla", u"*/ 690465 w 1");
|
||||
assertXPath(pXmlDoc1, aGdLst + "/a:gd[@name='f2']", "fmla", u"*/ 802433 h 1");
|
||||
// A guide referencing the result of an earlier guide by name.
|
||||
assertXPath(pXmlDoc1, aGdLst + "/a:gd[@name='f1']", "fmla", u"*/ f0 1 2407298");
|
||||
}
|
||||
|
||||
CPPUNIT_TEST_FIXTURE(SdOOXMLExportTest3, testTdf165262)
|
||||
{
|
||||
createSdImpressDoc("ppt/tdf165262.ppt");
|
||||
|
||||
@@ -171,6 +171,7 @@ public:
|
||||
{
|
||||
return ExpressionFunct::EnumAdjustment;
|
||||
}
|
||||
virtual sal_Int32 getIndex() const override { return mnIndex; }
|
||||
virtual EnhancedCustomShapeParameter fillNode( std::vector< EnhancedCustomShapeEquation >& /*rEquations*/, ExpressionNode* /*pOptionalArg*/, sal_uInt32 /*nFlags*/ ) override
|
||||
{
|
||||
EnhancedCustomShapeParameter aRet;
|
||||
@@ -211,6 +212,7 @@ public:
|
||||
{
|
||||
return ExpressionFunct::EnumEquation;
|
||||
}
|
||||
virtual sal_Int32 getIndex() const override { return mnIndex; }
|
||||
virtual EnhancedCustomShapeParameter fillNode( std::vector< EnhancedCustomShapeEquation >& /*rEquations*/, ExpressionNode* /*pOptionalArg*/, sal_uInt32 /*nFlags*/ ) override
|
||||
{
|
||||
EnhancedCustomShapeParameter aRet;
|
||||
@@ -326,6 +328,10 @@ public:
|
||||
{
|
||||
return meFunct;
|
||||
}
|
||||
virtual const ExpressionNode* getChild(sal_Int32 nIndex) const override
|
||||
{
|
||||
return nIndex == 0 ? mpArg.get() : nullptr;
|
||||
}
|
||||
virtual EnhancedCustomShapeParameter fillNode( std::vector< EnhancedCustomShapeEquation >& rEquations, ExpressionNode* pOptionalArg, sal_uInt32 nFlags ) override
|
||||
{
|
||||
EnhancedCustomShapeParameter aRet;
|
||||
@@ -497,6 +503,14 @@ public:
|
||||
{
|
||||
return meFunct;
|
||||
}
|
||||
virtual const ExpressionNode* getChild(sal_Int32 nIndex) const override
|
||||
{
|
||||
if (nIndex == 0)
|
||||
return mpFirstArg.get();
|
||||
if (nIndex == 1)
|
||||
return mpSecondArg.get();
|
||||
return nullptr;
|
||||
}
|
||||
virtual EnhancedCustomShapeParameter fillNode( std::vector< EnhancedCustomShapeEquation >& rEquations, ExpressionNode* /*pOptionalArg*/, sal_uInt32 nFlags ) override
|
||||
{
|
||||
EnhancedCustomShapeParameter aRet;
|
||||
@@ -707,6 +721,16 @@ public:
|
||||
{
|
||||
return ExpressionFunct::TernaryIf;
|
||||
}
|
||||
virtual const ExpressionNode* getChild(sal_Int32 nIndex) const override
|
||||
{
|
||||
switch (nIndex)
|
||||
{
|
||||
case 0: return mpFirstArg.get();
|
||||
case 1: return mpSecondArg.get();
|
||||
case 2: return mpThirdArg.get();
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
virtual EnhancedCustomShapeParameter fillNode( std::vector< EnhancedCustomShapeEquation >& rEquations, ExpressionNode* /*pOptionalArg*/, sal_uInt32 nFlags ) override
|
||||
{
|
||||
EnhancedCustomShapeParameter aRet;
|
||||
|
||||
@@ -1219,19 +1219,16 @@ CPPUNIT_TEST_FIXTURE(Test, testFlipAndRotateCustomShape)
|
||||
// point values depend on path size, values as of March 2022
|
||||
assertXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path", "w", u"21600");
|
||||
assertXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path", "h", u"21600");
|
||||
// check the first few coordinates of the polygon
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(
|
||||
0, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:moveTo/a:pt", "x").toInt32(), 1);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(
|
||||
15831, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:moveTo/a:pt", "y").toInt32(), 1);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(
|
||||
6098, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:lnTo[1]/a:pt", "x").toInt32(), 1);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(
|
||||
10062, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:lnTo[1]/a:pt", "y").toInt32(), 1);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(
|
||||
13284, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:lnTo[4]/a:pt", "x").toInt32(), 1);
|
||||
CPPUNIT_ASSERT_DOUBLES_EQUAL(
|
||||
6098, getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:lnTo[4]/a:pt", "y").toInt32(), 1);
|
||||
// This shape (mso-spt89) has adjustment handles, so it is exported as an
|
||||
// adjustable custom geometry: its adjustment values are declared in <a:avLst>,
|
||||
// the handles in <a:ahLst>, and the adjustment-dependent path coordinates
|
||||
// reference the <a:gdLst> guides (e.g. "f5") instead of being baked to fixed
|
||||
// numbers. A literal coordinate (here the moveTo x) stays plain.
|
||||
assertXPath(pXmlDoc, "//a:custGeom/a:avLst/a:gd", 3);
|
||||
assertXPath(pXmlDoc, "//a:custGeom/a:ahLst/a:ahXY", 2);
|
||||
assertXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:moveTo/a:pt", "x", u"0");
|
||||
CPPUNIT_ASSERT(
|
||||
getXPath(pXmlDoc, "//a:custGeom/a:pathLst/a:path/a:moveTo/a:pt", "y").startsWith("f"));
|
||||
// check path is closed
|
||||
assertXPath(pXmlDoc, "//a:close", 1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user