Currently LibreOffice uses the VML fallback, when a docx document has a wpc:wpc element. This patch implements to use the choice part with the wpc:wpc element. That is often called 'drawing canvas'. The patch uses a similar approach as for SmartArt. The drawing canvas is imported as group shape and for the background an additional rectangular shape is inserted as first in the children vector. Not using VML has the advantage, that the custom shape import is used for preset shapes. VML import produces problems because some properties are not available in VML or the current VML import has deficits. The test suite shows examples, what is better without using the VML fallback. Affected bug reports are e.g. tdf#104671 or tdf#154828. A drawing canvas must be used in Word for connector shapes. A connector in Word on the drawing canvas is not written as cxnSp element, but as ordinary wsp element with additional wps:cNvCnPr child element. The patch generates a connector in such case. Unsolved problems: The path of a curved connector in OOXML is basically incompatible to the path which LibreOffice generates. This patch uses the default path for a curved connector. Same is done in import in Impress. Using the VML fallback had generated a custom shape with the current path and handles, but the connections to the target shapes were lost. Export to docx is missing. The drawing canvas is not recreated, instead a group with the additional background shape is exported. That is no regression, using VML has produced a group too on export. I don't know whether XML_graphicFrame can occur in WordprocessingCanvasContext. At least charts and math equations are not possible on the drawing canvas in Word. Import of WordArt shapes does not work. That is not regression. It works neither in the VML import. Change-Id: I04bf8407efd1939cdf3137775f8afad420b74014 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/156629 Tested-by: Jenkins Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
983 lines
46 KiB
C++
983 lines
46 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/.
|
|
*/
|
|
|
|
#include "WpsContext.hxx"
|
|
#include "WpgContext.hxx"
|
|
#include "WordprocessingCanvasContext.hxx"
|
|
#include <basegfx/matrix/b2dhommatrix.hxx>
|
|
#include <basegfx/tuple/b2dtuple.hxx>
|
|
#include <comphelper/propertyvalue.hxx>
|
|
#include <comphelper/sequence.hxx>
|
|
#include <comphelper/sequenceashashmap.hxx>
|
|
#include <drawingml/customshapegeometry.hxx>
|
|
#include <drawingml/customshapeproperties.hxx>
|
|
#include <drawingml/fontworkhelpers.hxx>
|
|
#include <drawingml/textbody.hxx>
|
|
#include <drawingml/textbodyproperties.hxx>
|
|
#include <oox/drawingml/color.hxx>
|
|
#include <oox/drawingml/connectorshapecontext.hxx>
|
|
#include <oox/drawingml/drawingmltypes.hxx>
|
|
#include <oox/drawingml/shape.hxx>
|
|
#include <oox/drawingml/shapepropertymap.hxx>
|
|
#include <oox/helper/attributelist.hxx>
|
|
#include <oox/token/namespaces.hxx>
|
|
#include <oox/token/tokens.hxx>
|
|
#include <svx/svdoashp.hxx>
|
|
|
|
#include <com/sun/star/beans/PropertyAttribute.hpp>
|
|
#include <com/sun/star/beans/XPropertySet.hpp>
|
|
#include <com/sun/star/beans/XPropertySetInfo.hpp>
|
|
#include <com/sun/star/beans/XPropertyState.hpp>
|
|
#include <com/sun/star/container/XEnumerationAccess.hpp>
|
|
#include <com/sun/star/drawing/HomogenMatrix3.hpp>
|
|
#include <com/sun/star/drawing/TextHorizontalAdjust.hpp>
|
|
#include <com/sun/star/geometry/IntegerRectangle2D.hpp>
|
|
#include <com/sun/star/lang/XServiceInfo.hpp>
|
|
#include <com/sun/star/text/XText.hpp>
|
|
#include <com/sun/star/text/XTextCursor.hpp>
|
|
#include <com/sun/star/text/WritingMode.hpp>
|
|
#include <com/sun/star/text/WritingMode2.hpp>
|
|
|
|
#include <optional>
|
|
|
|
using namespace com::sun::star;
|
|
|
|
namespace
|
|
{
|
|
bool lcl_getTextPropsFromFrameText(const uno::Reference<text::XText>& xText,
|
|
std::vector<beans::PropertyValue>& rTextPropVec)
|
|
{
|
|
if (!xText.is())
|
|
return false;
|
|
uno::Reference<text::XTextCursor> xTextCursor = xText->createTextCursor();
|
|
xTextCursor->gotoStart(false);
|
|
xTextCursor->gotoEnd(true);
|
|
uno::Reference<container::XEnumerationAccess> paraEnumAccess(xText, uno::UNO_QUERY);
|
|
if (!paraEnumAccess.is())
|
|
return false;
|
|
uno::Reference<container::XEnumeration> paraEnum(paraEnumAccess->createEnumeration());
|
|
while (paraEnum->hasMoreElements())
|
|
{
|
|
uno::Reference<text::XTextRange> xParagraph(paraEnum->nextElement(), uno::UNO_QUERY);
|
|
uno::Reference<container::XEnumerationAccess> runEnumAccess(xParagraph, uno::UNO_QUERY);
|
|
if (!runEnumAccess.is())
|
|
continue;
|
|
uno::Reference<container::XEnumeration> runEnum = runEnumAccess->createEnumeration();
|
|
while (runEnum->hasMoreElements())
|
|
{
|
|
uno::Reference<text::XTextRange> xRun(runEnum->nextElement(), uno::UNO_QUERY);
|
|
if (xRun->getString().isEmpty())
|
|
continue;
|
|
uno::Reference<beans::XPropertySet> xRunPropSet(xRun, uno::UNO_QUERY);
|
|
if (!xRunPropSet.is())
|
|
continue;
|
|
auto xRunPropSetInfo = xRunPropSet->getPropertySetInfo();
|
|
if (!xRunPropSetInfo.is())
|
|
continue;
|
|
|
|
// We have found a non-empty run. Collect its properties.
|
|
auto aRunPropInfoSequence = xRunPropSetInfo->getProperties();
|
|
for (const beans::Property& aProp : aRunPropInfoSequence)
|
|
{
|
|
rTextPropVec.push_back(comphelper::makePropertyValue(
|
|
aProp.Name, xRunPropSet->getPropertyValue(aProp.Name)));
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// CharInteropGrabBag puts all attributes of an element into a property with Name="attributes" and
|
|
// Value being a sequence of the attributes. This methods finds the value of an individual rName
|
|
// attribute and puts it into rValue parameter. If it does not find it, rValue is unchanged and
|
|
// the method returns false, otherwise it returns true.
|
|
bool lcl_getAttributeAsString(const uno::Sequence<beans::PropertyValue>& aPropertyValueAsSeq,
|
|
const OUString& rName, OUString& rValue)
|
|
{
|
|
comphelper::SequenceAsHashMap aPropertyValueAsMap(aPropertyValueAsSeq);
|
|
uno::Sequence<beans::PropertyValue> aAttributesSeq;
|
|
if (!((aPropertyValueAsMap.getValue("attributes") >>= aAttributesSeq)
|
|
&& aAttributesSeq.hasElements()))
|
|
return false;
|
|
comphelper::SequenceAsHashMap aAttributesMap(aAttributesSeq);
|
|
OUString sRet;
|
|
if (!(aAttributesMap.getValue(rName) >>= sRet))
|
|
return false;
|
|
rValue = sRet;
|
|
return true;
|
|
}
|
|
|
|
// Same as above for a number as attribute value
|
|
bool lcl_getAttributeAsNumber(const uno::Sequence<beans::PropertyValue>& rPropertyValueAsSeq,
|
|
const OUString& rName, sal_Int32& rValue)
|
|
{
|
|
comphelper::SequenceAsHashMap aPropertyValueAsMap(rPropertyValueAsSeq);
|
|
uno::Sequence<beans::PropertyValue> aAttributesSeq;
|
|
if (!((aPropertyValueAsMap.getValue("attributes") >>= aAttributesSeq)
|
|
&& aAttributesSeq.hasElements()))
|
|
return false;
|
|
comphelper::SequenceAsHashMap aAttributesMap(aAttributesSeq);
|
|
sal_Int32 nRet;
|
|
if (!(aAttributesMap.getValue(rName) >>= nRet))
|
|
return false;
|
|
rValue = nRet;
|
|
return true;
|
|
}
|
|
|
|
void lcl_getColorTransformationsFromPropSeq(const uno::Sequence<beans::PropertyValue>& rPropSeq,
|
|
oox::drawingml::Color& rColor)
|
|
{
|
|
auto isValidPropName = [](const OUString& rName) -> bool {
|
|
return rName == u"tint" || rName == u"shade" || rName == u"alpha" || rName == u"hueMod"
|
|
|| rName == u"sat" || rName == u"satOff" || rName == u"satMod" || rName == u"lum"
|
|
|| rName == u"lumOff" || rName == u"lumMod";
|
|
};
|
|
for (auto it = rPropSeq.begin(); it < rPropSeq.end(); ++it)
|
|
{
|
|
if (isValidPropName((*it).Name))
|
|
{
|
|
uno::Sequence<beans::PropertyValue> aValueSeq;
|
|
sal_Int32 nNumber(0); // dummy value to make compiler happy, "val" should exist
|
|
if (((*it).Value >>= aValueSeq)
|
|
&& lcl_getAttributeAsNumber(aValueSeq, u"val"_ustr, nNumber))
|
|
{
|
|
// char w14:alpha contains transparency, whereas shape fill a:alpha contains opacity.
|
|
if ((*it).Name == u"alpha")
|
|
rColor.addTransformation(
|
|
oox::NMSP_dml | oox::AttributeConversion::decodeToken((*it).Name),
|
|
oox::drawingml::MAX_PERCENT - nNumber);
|
|
else
|
|
rColor.addTransformation(
|
|
oox::NMSP_w14 | oox::AttributeConversion::decodeToken((*it).Name), nNumber);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Expected: rPropSeq contains a property "schemeClr" or a property "srgbClr".
|
|
bool lcl_getColorFromPropSeq(const uno::Sequence<beans::PropertyValue>& rPropSeq,
|
|
oox::drawingml::Color& rColor)
|
|
{
|
|
bool bColorFound = false;
|
|
comphelper::SequenceAsHashMap aPropMap(rPropSeq);
|
|
uno::Sequence<beans::PropertyValue> aColorDetailSeq;
|
|
if (aPropMap.getValue(u"schemeClr"_ustr) >>= aColorDetailSeq)
|
|
{
|
|
OUString sColorString;
|
|
bColorFound = lcl_getAttributeAsString(aColorDetailSeq, u"val"_ustr, sColorString);
|
|
if (bColorFound)
|
|
{
|
|
sal_Int32 nColorToken = oox::AttributeConversion::decodeToken(sColorString);
|
|
rColor.setSchemeClr(nColorToken);
|
|
rColor.setSchemeName(sColorString);
|
|
}
|
|
}
|
|
if (!bColorFound && (aPropMap.getValue(u"srgbClr"_ustr) >>= aColorDetailSeq))
|
|
{
|
|
OUString sColorString;
|
|
bColorFound = lcl_getAttributeAsString(aColorDetailSeq, u"val"_ustr, sColorString);
|
|
if (bColorFound)
|
|
{
|
|
sal_Int32 nColor = oox::AttributeConversion::decodeIntegerHex(sColorString);
|
|
rColor.setSrgbClr(nColor);
|
|
}
|
|
}
|
|
// Without color, color transformations are pointless.
|
|
if (bColorFound)
|
|
lcl_getColorTransformationsFromPropSeq(aColorDetailSeq, rColor);
|
|
return bColorFound;
|
|
}
|
|
|
|
void lcl_getFillDetailsFromPropSeq(const uno::Sequence<beans::PropertyValue>& rTextFillSeq,
|
|
oox::drawingml::FillProperties& rFillProperties)
|
|
{
|
|
// rTextFillSeq should have an item containing either "noFill" or "solidFill" or "gradFill"
|
|
// property.
|
|
if (!rTextFillSeq.hasElements())
|
|
return;
|
|
comphelper::SequenceAsHashMap aTextFillMap(rTextFillSeq);
|
|
if (aTextFillMap.find(u"noFill"_ustr) != aTextFillMap.end())
|
|
{
|
|
rFillProperties.moFillType = oox::XML_noFill;
|
|
return;
|
|
}
|
|
|
|
uno::Sequence<beans::PropertyValue> aPropSeq;
|
|
if ((aTextFillMap.getValue(u"solidFill"_ustr) >>= aPropSeq) && aPropSeq.hasElements())
|
|
{
|
|
rFillProperties.moFillType = oox::XML_solidFill;
|
|
lcl_getColorFromPropSeq(aPropSeq, rFillProperties.maFillColor);
|
|
return;
|
|
}
|
|
|
|
if ((aTextFillMap.getValue(u"gradFill"_ustr) >>= aPropSeq) && aPropSeq.hasElements())
|
|
{
|
|
rFillProperties.moFillType = oox::XML_gradFill;
|
|
// aPropSeq should have two items. One is "gsLst" for the stop colors, the other is
|
|
// either "lin" or "path" for the kind of gradient.
|
|
// First get stop colors
|
|
comphelper::SequenceAsHashMap aPropMap(aPropSeq);
|
|
uno::Sequence<beans::PropertyValue> aGsLstSeq;
|
|
if (aPropMap.getValue("gsLst") >>= aGsLstSeq)
|
|
{
|
|
for (auto it = aGsLstSeq.begin(); it < aGsLstSeq.end(); ++it)
|
|
{
|
|
// (*it) is a bean::PropertyValue with Name="gs". Its Value is a property sequence.
|
|
uno::Sequence<beans::PropertyValue> aColorStopSeq;
|
|
if ((*it).Value >>= aColorStopSeq)
|
|
{
|
|
// aColorStopSeq should have an item for the color and an item for the position
|
|
sal_Int32 nPos;
|
|
oox::drawingml::Color aColor;
|
|
if (lcl_getAttributeAsNumber(aColorStopSeq, u"pos"_ustr, nPos)
|
|
&& lcl_getColorFromPropSeq(aColorStopSeq, aColor))
|
|
{
|
|
// The position in maGradientStops is relative, thus in range [0.0;1.0].
|
|
double fPos = nPos / 100000.0;
|
|
rFillProperties.maGradientProps.maGradientStops.insert({ fPos, aColor });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Now determine kind of gradient.
|
|
uno::Sequence<beans::PropertyValue> aKindSeq;
|
|
if (aPropMap.getValue("lin") >>= aKindSeq)
|
|
{
|
|
// aKindSeq contains the attributes "ang" and "scaled"
|
|
sal_Int32 nAngle; // in 1/60000 deg
|
|
if (lcl_getAttributeAsNumber(aKindSeq, "ang", nAngle))
|
|
rFillProperties.maGradientProps.moShadeAngle = nAngle;
|
|
OUString sScaledString;
|
|
if (lcl_getAttributeAsString(aKindSeq, "scaled", sScaledString))
|
|
rFillProperties.maGradientProps.moShadeScaled
|
|
= sScaledString == u"1" || sScaledString == u"true";
|
|
return;
|
|
}
|
|
if (aPropMap.getValue("path") >>= aKindSeq)
|
|
{
|
|
// aKindSeq contains the attribute "path" for the kind of path and a property "fillToRect"
|
|
// which defines the center rectangle of the gradient. The property "a:tileRect" known from
|
|
// fill of shapes does not exist in w14 namespace.
|
|
OUString sKind;
|
|
if (lcl_getAttributeAsString(aKindSeq, "path", sKind))
|
|
rFillProperties.maGradientProps.moGradientPath
|
|
= oox::AttributeConversion::decodeToken(sKind);
|
|
comphelper::SequenceAsHashMap aKindMap(aKindSeq);
|
|
uno::Sequence<beans::PropertyValue> aFillToRectSeq;
|
|
if (aKindMap.getValue("fillToRect") >>= aFillToRectSeq)
|
|
{
|
|
// The values l, t, r and b are not coordinates, but determine an offset from the
|
|
// edge of the bounding box of the shape. This unusual meaning of X1, Y1, X2 and
|
|
// Y2 is needed for method pushToPropMap() of FillProperties.
|
|
geometry::IntegerRectangle2D aRect;
|
|
if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"l"_ustr, aRect.X1))
|
|
aRect.X1 = 0;
|
|
if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"t"_ustr, aRect.Y1))
|
|
aRect.Y1 = 0;
|
|
if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"r"_ustr, aRect.X2))
|
|
aRect.X2 = 0;
|
|
if (!lcl_getAttributeAsNumber(aFillToRectSeq, u"b"_ustr, aRect.Y2))
|
|
aRect.Y2 = 0;
|
|
rFillProperties.maGradientProps.moFillToRect = aRect;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
void lcl_getLineDetailsFromPropSeq(const uno::Sequence<beans::PropertyValue>& rTextOutlineSeq,
|
|
oox::drawingml::LineProperties& rLineProperties)
|
|
{
|
|
if (!rTextOutlineSeq.hasElements())
|
|
{
|
|
rLineProperties.maLineFill.moFillType = oox::XML_noFill; // MS Office default
|
|
return;
|
|
}
|
|
// aTextOulineSeq contains e.g. "attributes" {w, cap, cmpd, ctr}, either
|
|
// "solidFill" or "gradFill or "noFill", and "prstDash" and "lineJoint" properties.
|
|
|
|
// Fill
|
|
lcl_getFillDetailsFromPropSeq(rTextOutlineSeq, rLineProperties.maLineFill);
|
|
|
|
// LineJoint
|
|
comphelper::SequenceAsHashMap aTextOutlineMap(rTextOutlineSeq);
|
|
if (aTextOutlineMap.find(u"bevel"_ustr) != aTextOutlineMap.end())
|
|
rLineProperties.moLineJoint = oox::XML_bevel;
|
|
else if (aTextOutlineMap.find(u"round"_ustr) != aTextOutlineMap.end())
|
|
rLineProperties.moLineJoint = oox::XML_round;
|
|
else if (aTextOutlineMap.find(u"miter"_ustr) != aTextOutlineMap.end())
|
|
{
|
|
// LineProperties has no member to store a miter limit. Therefore some heuristic is
|
|
// added here. 0 is default for attribute "lim" in MS Office. It is rendered same as bevel.
|
|
sal_Int32 nMiterLimit = aTextOutlineMap.getUnpackedValueOrDefault("lim", sal_Int32(0));
|
|
if (nMiterLimit == 0)
|
|
rLineProperties.moLineJoint = oox::XML_bevel;
|
|
else
|
|
rLineProperties.moLineJoint = oox::XML_miter;
|
|
}
|
|
|
|
// Dash
|
|
uno::Sequence<beans::PropertyValue> aDashSeq;
|
|
if (aTextOutlineMap.getValue(u"prstDash"_ustr) >>= aDashSeq)
|
|
{
|
|
// aDashSeq contains the attribute "val" with the kind of dash, e.g. "sysDot"
|
|
OUString sDashKind;
|
|
if (lcl_getAttributeAsString(aDashSeq, u"val"_ustr, sDashKind))
|
|
rLineProperties.moPresetDash = oox::AttributeConversion::decodeToken(sDashKind);
|
|
}
|
|
OUString sCapKind;
|
|
if (lcl_getAttributeAsString(rTextOutlineSeq, u"cap"_ustr, sCapKind))
|
|
rLineProperties.moLineCap = oox::AttributeConversion::decodeToken(sCapKind);
|
|
|
|
// Width
|
|
sal_Int32 nWidth; // EMU
|
|
if (lcl_getAttributeAsNumber(rTextOutlineSeq, u"w"_ustr, nWidth))
|
|
rLineProperties.moLineWidth = nWidth;
|
|
|
|
// Compound. LineProperties has a member for it, however Fontwork can currently only render "sng".
|
|
OUString sCompoundKind;
|
|
if (lcl_getAttributeAsString(rTextOutlineSeq, u"cmpd"_ustr, sCompoundKind))
|
|
rLineProperties.moLineCompound = oox::AttributeConversion::decodeToken(sCompoundKind);
|
|
|
|
// Align. LineProperties has no member for attribute "algn".
|
|
|
|
return;
|
|
}
|
|
|
|
oox::drawingml::LineProperties
|
|
lcl_generateLinePropertiesFromTextProps(const comphelper::SequenceAsHashMap& aTextPropMap)
|
|
{
|
|
oox::drawingml::LineProperties aLineProperties;
|
|
aLineProperties.maLineFill.moFillType = oox::XML_noFill; // default
|
|
|
|
// Get property "textOutline" from aTextPropMap
|
|
uno::Sequence<beans::PropertyValue> aCharInteropGrabBagSeq;
|
|
if (!(aTextPropMap.getValue(u"CharInteropGrabBag"_ustr) >>= aCharInteropGrabBagSeq))
|
|
return aLineProperties;
|
|
if (!aCharInteropGrabBagSeq.hasElements())
|
|
return aLineProperties;
|
|
comphelper::SequenceAsHashMap aCharInteropGrabBagMap(aCharInteropGrabBagSeq);
|
|
beans::PropertyValue aProp;
|
|
if (!(aCharInteropGrabBagMap.getValue(u"CharTextOutlineTextEffect"_ustr) >>= aProp))
|
|
return aLineProperties;
|
|
uno::Sequence<beans::PropertyValue> aTextOutlineSeq;
|
|
if (!(aProp.Name == "textOutline" && (aProp.Value >>= aTextOutlineSeq)
|
|
&& aTextOutlineSeq.hasElements()))
|
|
return aLineProperties;
|
|
|
|
// Copy line properties from aTextOutlineSeq to aLineProperties
|
|
lcl_getLineDetailsFromPropSeq(aTextOutlineSeq, aLineProperties);
|
|
return aLineProperties;
|
|
}
|
|
|
|
oox::drawingml::FillProperties
|
|
lcl_generateFillPropertiesFromTextProps(const comphelper::SequenceAsHashMap& rTextPropMap)
|
|
{
|
|
oox::drawingml::FillProperties aFillProperties;
|
|
aFillProperties.moFillType = oox::XML_solidFill; // default
|
|
// Theme color supersedes direct color. textFill supersedes theme color. Theme color and textFill
|
|
// are in CharInteropGrabBag.
|
|
uno::Sequence<beans::PropertyValue> aCharInteropGrabBagSeq;
|
|
if ((rTextPropMap.getValue(u"CharInteropGrabBag"_ustr) >>= aCharInteropGrabBagSeq)
|
|
&& aCharInteropGrabBagSeq.hasElements())
|
|
{
|
|
// Handle case textFill
|
|
comphelper::SequenceAsHashMap aCharInteropGrabBagMap(aCharInteropGrabBagSeq);
|
|
beans::PropertyValue aProp;
|
|
if (aCharInteropGrabBagMap.getValue(u"CharTextFillTextEffect"_ustr) >>= aProp)
|
|
{
|
|
uno::Sequence<beans::PropertyValue> aTextFillSeq;
|
|
if (aProp.Name == "textFill" && (aProp.Value >>= aTextFillSeq)
|
|
&& aTextFillSeq.hasElements())
|
|
{
|
|
// Copy fill properties from aTextFillSeq to aFillProperties
|
|
lcl_getFillDetailsFromPropSeq(aTextFillSeq, aFillProperties);
|
|
return aFillProperties;
|
|
}
|
|
}
|
|
|
|
// no textFill, look for theme color, tint and shade
|
|
bool bColorFound(false);
|
|
OUString sColorString;
|
|
if (aCharInteropGrabBagMap.getValue("CharThemeOriginalColor") >>= sColorString)
|
|
{
|
|
sal_Int32 nThemeOrigColor = oox::AttributeConversion::decodeIntegerHex(sColorString);
|
|
aFillProperties.maFillColor.setSrgbClr(nThemeOrigColor);
|
|
bColorFound = true;
|
|
}
|
|
if (aCharInteropGrabBagMap.getValue("CharThemeColor") >>= sColorString)
|
|
{
|
|
sal_Int32 nColorToken = oox::AttributeConversion::decodeToken(sColorString);
|
|
aFillProperties.maFillColor.setSchemeClr(nColorToken);
|
|
aFillProperties.maFillColor.setSchemeName(sColorString);
|
|
bColorFound = true;
|
|
// A character color has shade or tint, a shape color has lumMod and lumOff.
|
|
OUString sTransformString;
|
|
if (aCharInteropGrabBagMap.getValue("CharThemeColorTint") >>= sTransformString)
|
|
{
|
|
double fTint = oox::AttributeConversion::decodeIntegerHex(sTransformString);
|
|
fTint = fTint / 255.0 * oox::drawingml::MAX_PERCENT;
|
|
aFillProperties.maFillColor.addTransformation(OOX_TOKEN(w14, lumMod),
|
|
static_cast<sal_Int32>(fTint + 0.5));
|
|
double fOff = oox::drawingml::MAX_PERCENT - fTint;
|
|
aFillProperties.maFillColor.addTransformation(OOX_TOKEN(w14, lumOff),
|
|
static_cast<sal_Int32>(fOff + 0.5));
|
|
}
|
|
else if (aCharInteropGrabBagMap.getValue("CharThemeColorShade") >>= sTransformString)
|
|
{
|
|
double fShade = oox::AttributeConversion::decodeIntegerHex(sTransformString);
|
|
fShade = fShade / 255.0 * oox::drawingml::MAX_PERCENT;
|
|
aFillProperties.maFillColor.addTransformation(OOX_TOKEN(w14, lumMod),
|
|
static_cast<sal_Int32>(fShade + 0.5));
|
|
}
|
|
}
|
|
if (bColorFound)
|
|
return aFillProperties;
|
|
}
|
|
|
|
// Neither textFill nor theme color. Look for direct color.
|
|
sal_Int32 aCharColor = 0;
|
|
if (rTextPropMap.getValue(u"CharColor"_ustr) >>= aCharColor)
|
|
aFillProperties.maFillColor.setSrgbClr(aCharColor);
|
|
else
|
|
aFillProperties.maFillColor.setUnused();
|
|
return aFillProperties;
|
|
}
|
|
|
|
void lcl_applyShapePropsToShape(const uno::Reference<beans::XPropertySet>& xShapePropertySet,
|
|
const oox::drawingml::ShapePropertyMap& rShapeProps)
|
|
{
|
|
for (const auto& rProp : rShapeProps.makePropertyValueSequence())
|
|
{
|
|
xShapePropertySet->setPropertyValue(rProp.Name, rProp.Value);
|
|
}
|
|
}
|
|
|
|
void lcl_setTextAnchorFromTextProps(const uno::Reference<beans::XPropertySet>& xShapePropertySet,
|
|
const comphelper::SequenceAsHashMap& aTextPropMap)
|
|
{
|
|
// Fontwork does not evaluate paragraph alignment but uses text anchor instead
|
|
auto eHorzAdjust(drawing::TextHorizontalAdjust_CENTER);
|
|
sal_Int16 nParaAlign = sal_Int16(drawing::TextHorizontalAdjust_CENTER);
|
|
aTextPropMap.getValue("ParaAdjust") >>= nParaAlign;
|
|
switch (nParaAlign)
|
|
{
|
|
case sal_Int16(style::ParagraphAdjust_LEFT):
|
|
eHorzAdjust = drawing::TextHorizontalAdjust_LEFT;
|
|
break;
|
|
case sal_Int16(style::ParagraphAdjust_RIGHT):
|
|
eHorzAdjust = drawing::TextHorizontalAdjust_RIGHT;
|
|
break;
|
|
default:
|
|
eHorzAdjust = drawing::TextHorizontalAdjust_CENTER;
|
|
}
|
|
xShapePropertySet->setPropertyValue("TextHorizontalAdjust", uno::Any(eHorzAdjust));
|
|
xShapePropertySet->setPropertyValue("TextVerticalAdjust",
|
|
uno::Any(drawing::TextVerticalAdjust_TOP));
|
|
}
|
|
|
|
void lcl_setTextPropsToShape(const uno::Reference<beans::XPropertySet>& xShapePropertySet,
|
|
std::vector<beans::PropertyValue>& aTextPropVec)
|
|
{
|
|
auto xShapePropertySetInfo = xShapePropertySet->getPropertySetInfo();
|
|
if (!xShapePropertySetInfo.is())
|
|
return;
|
|
for (size_t i = 0; i < aTextPropVec.size(); ++i)
|
|
{
|
|
if (xShapePropertySetInfo->hasPropertyByName(aTextPropVec[i].Name)
|
|
&& !(xShapePropertySetInfo->getPropertyByName(aTextPropVec[i].Name).Attributes
|
|
& beans::PropertyAttribute::READONLY)
|
|
&& aTextPropVec[i].Name != u"CharInteropGrabBag")
|
|
{
|
|
xShapePropertySet->setPropertyValue(aTextPropVec[i].Name, aTextPropVec[i].Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
void lcl_applyUsedTextPropsToAllTextRuns(uno::Reference<text::XText>& xText,
|
|
const std::vector<beans::PropertyValue>& aTextPropVec)
|
|
{
|
|
if (!xText.is())
|
|
return;
|
|
uno::Reference<text::XTextCursor> xTextCursor = xText->createTextCursor();
|
|
xTextCursor->gotoStart(false);
|
|
xTextCursor->gotoEnd(true);
|
|
uno::Reference<container::XEnumerationAccess> paraEnumAccess(xText, uno::UNO_QUERY);
|
|
if (!paraEnumAccess.is())
|
|
return;
|
|
uno::Reference<container::XEnumeration> paraEnum(paraEnumAccess->createEnumeration());
|
|
while (paraEnum->hasMoreElements())
|
|
{
|
|
uno::Reference<text::XTextRange> xParagraph(paraEnum->nextElement(), uno::UNO_QUERY);
|
|
uno::Reference<container::XEnumerationAccess> runEnumAccess(xParagraph, uno::UNO_QUERY);
|
|
if (!runEnumAccess.is())
|
|
continue;
|
|
uno::Reference<container::XEnumeration> runEnum = runEnumAccess->createEnumeration();
|
|
while (runEnum->hasMoreElements())
|
|
{
|
|
uno::Reference<text::XTextRange> xRun(runEnum->nextElement(), uno::UNO_QUERY);
|
|
if (xRun->getString().isEmpty())
|
|
continue;
|
|
uno::Reference<beans::XPropertySet> xRunPropSet(xRun, uno::UNO_QUERY);
|
|
if (!xRunPropSet.is())
|
|
continue;
|
|
auto xRunPropSetInfo = xRunPropSet->getPropertySetInfo();
|
|
if (!xRunPropSetInfo.is())
|
|
continue;
|
|
|
|
for (size_t i = 0; i < aTextPropVec.size(); ++i)
|
|
{
|
|
if (xRunPropSetInfo->hasPropertyByName(aTextPropVec[i].Name)
|
|
&& !(xRunPropSetInfo->getPropertyByName(aTextPropVec[i].Name).Attributes
|
|
& beans::PropertyAttribute::READONLY))
|
|
xRunPropSet->setPropertyValue(aTextPropVec[i].Name, aTextPropVec[i].Value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} // anonymous namespace
|
|
|
|
namespace oox::shape
|
|
{
|
|
WpsContext::WpsContext(ContextHandler2Helper const& rParent, uno::Reference<drawing::XShape> xShape,
|
|
const drawingml::ShapePtr& pMasterShapePtr,
|
|
const drawingml::ShapePtr& pShapePtr)
|
|
: ShapeContext(rParent, pMasterShapePtr, pShapePtr)
|
|
, mxShape(std::move(xShape))
|
|
{
|
|
if (mpShapePtr)
|
|
mpShapePtr->setWps(true);
|
|
|
|
if (const auto pParent = dynamic_cast<const WpgContext*>(&rParent))
|
|
m_bHasWPGParent = pParent->isFullWPGSupport();
|
|
else if (dynamic_cast<const WordprocessingCanvasContext*>(&rParent))
|
|
m_bHasWPGParent = true;
|
|
else
|
|
m_bHasWPGParent = false;
|
|
|
|
if ((pMasterShapePtr && pMasterShapePtr->isInWordprocessingCanvas())
|
|
|| dynamic_cast<const WordprocessingCanvasContext*>(&rParent) != nullptr)
|
|
pShapePtr->setWordprocessingCanvas(true);
|
|
}
|
|
|
|
WpsContext::~WpsContext() = default;
|
|
|
|
oox::core::ContextHandlerRef WpsContext::onCreateContext(sal_Int32 nElementToken,
|
|
const oox::AttributeList& rAttribs)
|
|
{
|
|
switch (getBaseToken(nElementToken))
|
|
{
|
|
case XML_wsp:
|
|
break;
|
|
case XML_cNvCnPr:
|
|
{
|
|
// It might be a connector shape in a wordprocessing canvas
|
|
// Replace the custom shape with a connector shape.
|
|
if (!mpShapePtr || !mpShapePtr->isInWordprocessingCanvas() || !mpMasterShapePtr)
|
|
break;
|
|
// Generate new shape
|
|
oox::drawingml::ShapePtr pShape = std::make_shared<oox::drawingml::Shape>(
|
|
"com.sun.star.drawing.ConnectorShape", false);
|
|
pShape->setConnectorShape(true);
|
|
pShape->setWps(true);
|
|
pShape->setWordprocessingCanvas(true);
|
|
// ToDo: Can only copy infos from mpShapePtr to pShape for which getter available.
|
|
pShape->setName(mpShapePtr->getName());
|
|
pShape->setId(mpShapePtr->getId());
|
|
pShape->setWPGChild(mpShapePtr->isWPGChild());
|
|
// And actually replace the shape.
|
|
mpShapePtr = pShape;
|
|
mpMasterShapePtr->getChildren().pop_back();
|
|
mpMasterShapePtr->getChildren().push_back(pShape);
|
|
return new oox::drawingml::ConnectorShapePropertiesContext(
|
|
*this, mpShapePtr, mpShapePtr->getConnectorShapeProperties());
|
|
}
|
|
case XML_bodyPr:
|
|
if (mxShape.is())
|
|
{
|
|
// no evaluation of attribute XML_rot, because Word ignores it, as of 2022-07.
|
|
|
|
uno::Reference<lang::XServiceInfo> xServiceInfo(mxShape, uno::UNO_QUERY);
|
|
uno::Reference<beans::XPropertySet> xPropertySet(mxShape, uno::UNO_QUERY);
|
|
sal_Int32 nVert = rAttribs.getToken(XML_vert, XML_horz);
|
|
// Values 'wordArtVert' and 'wordArtVertRtl' are not implemented.
|
|
// Map them to other vert values.
|
|
if (nVert == XML_eaVert || nVert == XML_wordArtVertRtl)
|
|
{
|
|
xPropertySet->setPropertyValue("TextWritingMode",
|
|
uno::Any(text::WritingMode_TB_RL));
|
|
xPropertySet->setPropertyValue("WritingMode",
|
|
uno::Any(text::WritingMode2::TB_RL));
|
|
}
|
|
else if (nVert == XML_mongolianVert || nVert == XML_wordArtVert)
|
|
{
|
|
xPropertySet->setPropertyValue("WritingMode",
|
|
uno::Any(text::WritingMode2::TB_LR));
|
|
}
|
|
else if (nVert != XML_horz) // cases XML_vert and XML_vert270
|
|
{
|
|
// Hack to get same rendering as after the fix for tdf#87924. If shape rotation
|
|
// plus text direction results in upright text, use horizontal text direction.
|
|
// Remove hack when frame is able to rotate.
|
|
|
|
// Need transformation matrix since RotateAngle does not contain flip.
|
|
drawing::HomogenMatrix3 aMatrix;
|
|
xPropertySet->getPropertyValue("Transformation") >>= aMatrix;
|
|
basegfx::B2DHomMatrix aTransformation;
|
|
aTransformation.set(0, 0, aMatrix.Line1.Column1);
|
|
aTransformation.set(0, 1, aMatrix.Line1.Column2);
|
|
aTransformation.set(0, 2, aMatrix.Line1.Column3);
|
|
aTransformation.set(1, 0, aMatrix.Line2.Column1);
|
|
aTransformation.set(1, 1, aMatrix.Line2.Column2);
|
|
aTransformation.set(1, 2, aMatrix.Line2.Column3);
|
|
// For this to be a valid 2D transform matrix, the last row must be [0,0,1]
|
|
assert(aMatrix.Line3.Column1 == 0);
|
|
assert(aMatrix.Line3.Column2 == 0);
|
|
assert(aMatrix.Line3.Column3 == 1);
|
|
basegfx::B2DTuple aScale;
|
|
basegfx::B2DTuple aTranslate;
|
|
double fRotate = 0;
|
|
double fShearX = 0;
|
|
aTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
|
|
auto nRotate(static_cast<sal_uInt16>(NormAngle360(basegfx::rad2deg(fRotate))));
|
|
if ((nVert == XML_vert && nRotate == 270)
|
|
|| (nVert == XML_vert270 && nRotate == 90))
|
|
{
|
|
xPropertySet->setPropertyValue("WritingMode",
|
|
uno::Any(text::WritingMode2::LR_TB));
|
|
// ToDo: Remember original vert value and remove hack on export.
|
|
}
|
|
else if (nVert == XML_vert)
|
|
xPropertySet->setPropertyValue("WritingMode",
|
|
uno::Any(text::WritingMode2::TB_RL90));
|
|
else // nVert == XML_vert270
|
|
xPropertySet->setPropertyValue("WritingMode",
|
|
uno::Any(text::WritingMode2::BT_LR));
|
|
}
|
|
|
|
if (bool bUpright = rAttribs.getBool(XML_upright, false))
|
|
{
|
|
uno::Sequence<beans::PropertyValue> aGrabBag;
|
|
xPropertySet->getPropertyValue("InteropGrabBag") >>= aGrabBag;
|
|
sal_Int32 length = aGrabBag.getLength();
|
|
aGrabBag.realloc(length + 1);
|
|
auto pGrabBag = aGrabBag.getArray();
|
|
pGrabBag[length].Name = "Upright";
|
|
pGrabBag[length].Value <<= bUpright;
|
|
xPropertySet->setPropertyValue("InteropGrabBag", uno::Any(aGrabBag));
|
|
}
|
|
|
|
if (xServiceInfo.is())
|
|
{
|
|
// Handle inset attributes for Writer textframes.
|
|
sal_Int32 aInsets[] = { XML_lIns, XML_tIns, XML_rIns, XML_bIns };
|
|
std::optional<sal_Int32> oInsets[4];
|
|
for (std::size_t i = 0; i < SAL_N_ELEMENTS(aInsets); ++i)
|
|
{
|
|
std::optional<OUString> oValue = rAttribs.getString(aInsets[i]);
|
|
if (oValue.has_value())
|
|
oInsets[i] = oox::drawingml::GetCoordinate(oValue.value());
|
|
else
|
|
// Defaults from the spec: left/right: 91440 EMU, top/bottom: 45720 EMU
|
|
oInsets[i]
|
|
= (aInsets[i] == XML_lIns || aInsets[i] == XML_rIns) ? 254 : 127;
|
|
}
|
|
const OUString aShapeProps[]
|
|
= { OUString("TextLeftDistance"), OUString("TextUpperDistance"),
|
|
OUString("TextRightDistance"), OUString("TextLowerDistance") };
|
|
for (std::size_t i = 0; i < SAL_N_ELEMENTS(aShapeProps); ++i)
|
|
if (oInsets[i])
|
|
xPropertySet->setPropertyValue(aShapeProps[i], uno::Any(*oInsets[i]));
|
|
}
|
|
|
|
// Handle text vertical adjustment inside a text frame
|
|
if (rAttribs.hasAttribute(XML_anchor))
|
|
{
|
|
drawing::TextVerticalAdjust eAdjust
|
|
= drawingml::GetTextVerticalAdjust(rAttribs.getToken(XML_anchor, XML_t));
|
|
xPropertySet->setPropertyValue("TextVerticalAdjust", uno::Any(eAdjust));
|
|
}
|
|
|
|
// Apply character color of the shape to the shape's textbox.
|
|
uno::Reference<text::XText> xText(mxShape, uno::UNO_QUERY);
|
|
uno::Any xCharColor = xPropertySet->getPropertyValue("CharColor");
|
|
Color aColor = COL_AUTO;
|
|
if ((xCharColor >>= aColor) && aColor != COL_AUTO)
|
|
{
|
|
// tdf#135923 Apply character color of the shape to the textrun
|
|
// when the character color of the textrun is default.
|
|
// tdf#153791 But only if the run has no background color (shd element in OOXML)
|
|
if (uno::Reference<container::XEnumerationAccess> paraEnumAccess{
|
|
xText, uno::UNO_QUERY })
|
|
{
|
|
uno::Reference<container::XEnumeration> paraEnum(
|
|
paraEnumAccess->createEnumeration());
|
|
|
|
while (paraEnum->hasMoreElements())
|
|
{
|
|
uno::Reference<text::XTextRange> xParagraph(paraEnum->nextElement(),
|
|
uno::UNO_QUERY);
|
|
uno::Reference<container::XEnumerationAccess> runEnumAccess(
|
|
xParagraph, uno::UNO_QUERY);
|
|
if (!runEnumAccess.is())
|
|
continue;
|
|
if (uno::Reference<beans::XPropertySet> xParaPropSet{ xParagraph,
|
|
uno::UNO_QUERY })
|
|
if ((xParaPropSet->getPropertyValue("ParaBackColor") >>= aColor)
|
|
&& aColor != COL_AUTO)
|
|
continue;
|
|
|
|
uno::Reference<container::XEnumeration> runEnum
|
|
= runEnumAccess->createEnumeration();
|
|
|
|
while (runEnum->hasMoreElements())
|
|
{
|
|
uno::Reference<text::XTextRange> xRun(runEnum->nextElement(),
|
|
uno::UNO_QUERY);
|
|
const uno::Reference<beans::XPropertyState> xRunState(
|
|
xRun, uno::UNO_QUERY);
|
|
if (!xRunState
|
|
|| xRunState->getPropertyState("CharColor")
|
|
== beans::PropertyState_DEFAULT_VALUE)
|
|
{
|
|
uno::Reference<beans::XPropertySet> xRunPropSet(xRun,
|
|
uno::UNO_QUERY);
|
|
if (!xRunPropSet)
|
|
continue;
|
|
if ((xRunPropSet->getPropertyValue("CharBackColor") >>= aColor)
|
|
&& aColor != COL_AUTO)
|
|
continue;
|
|
if (!(xRunPropSet->getPropertyValue("CharColor") >>= aColor)
|
|
|| aColor == COL_AUTO)
|
|
xRunPropSet->setPropertyValue("CharColor", xCharColor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto nWrappingType = rAttribs.getToken(XML_wrap, XML_square);
|
|
xPropertySet->setPropertyValue("TextWordWrap",
|
|
uno::Any(nWrappingType == XML_square));
|
|
|
|
return this;
|
|
}
|
|
else if (m_bHasWPGParent && mpShapePtr)
|
|
{
|
|
// this WPS context has to be inside a WPG shape, so the <BodyPr> element
|
|
// cannot be applied to mxShape member, use mpShape instead, and after the
|
|
// the parent shape finished, apply it for its children.
|
|
mpShapePtr->setWPGChild(true);
|
|
oox::drawingml::TextBodyPtr pTextBody;
|
|
pTextBody.reset(new oox::drawingml::TextBody());
|
|
|
|
if (rAttribs.hasAttribute(XML_anchor))
|
|
{
|
|
drawing::TextVerticalAdjust eAdjust
|
|
= drawingml::GetTextVerticalAdjust(rAttribs.getToken(XML_anchor, XML_t));
|
|
pTextBody->getTextProperties().meVA = eAdjust;
|
|
}
|
|
|
|
sal_Int32 aInsets[] = { XML_lIns, XML_tIns, XML_rIns, XML_bIns };
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
if (rAttribs.hasAttribute(XML_lIns))
|
|
{
|
|
std::optional<OUString> oValue = rAttribs.getString(aInsets[i]);
|
|
if (oValue.has_value())
|
|
pTextBody->getTextProperties().moInsets[i]
|
|
= oox::drawingml::GetCoordinate(oValue.value());
|
|
else
|
|
// Defaults from the spec: left/right: 91440 EMU, top/bottom: 45720 EMU
|
|
pTextBody->getTextProperties().moInsets[i]
|
|
= (aInsets[i] == XML_lIns || aInsets[i] == XML_rIns) ? 254 : 127;
|
|
}
|
|
}
|
|
|
|
mpShapePtr->setTextBody(pTextBody);
|
|
}
|
|
break;
|
|
case XML_noAutofit:
|
|
case XML_spAutoFit:
|
|
{
|
|
uno::Reference<lang::XServiceInfo> xServiceInfo(mxShape, uno::UNO_QUERY);
|
|
// We can't use oox::drawingml::TextBodyPropertiesContext here, as this
|
|
// is a child context of bodyPr, so the shape is already sent: we need
|
|
// to alter the XShape directly.
|
|
uno::Reference<beans::XPropertySet> xPropertySet(mxShape, uno::UNO_QUERY);
|
|
if (xPropertySet.is())
|
|
{
|
|
if (xServiceInfo->supportsService("com.sun.star.text.TextFrame"))
|
|
xPropertySet->setPropertyValue(
|
|
"FrameIsAutomaticHeight",
|
|
uno::Any(getBaseToken(nElementToken) == XML_spAutoFit));
|
|
else
|
|
xPropertySet->setPropertyValue(
|
|
"TextAutoGrowHeight",
|
|
uno::Any(getBaseToken(nElementToken) == XML_spAutoFit));
|
|
}
|
|
}
|
|
break;
|
|
case XML_prstTxWarp:
|
|
if (rAttribs.hasAttribute(XML_prst))
|
|
{
|
|
uno::Reference<beans::XPropertySet> xPropertySet(mxShape, uno::UNO_QUERY);
|
|
if (xPropertySet.is())
|
|
{
|
|
std::optional<OUString> presetShapeName = rAttribs.getString(XML_prst);
|
|
const OUString& preset = presetShapeName.value();
|
|
comphelper::SequenceAsHashMap aCustomShapeGeometry(
|
|
xPropertySet->getPropertyValue("CustomShapeGeometry"));
|
|
aCustomShapeGeometry["PresetTextWarp"] <<= preset;
|
|
xPropertySet->setPropertyValue(
|
|
"CustomShapeGeometry",
|
|
uno::Any(aCustomShapeGeometry.getAsConstPropertyValueList()));
|
|
}
|
|
}
|
|
return new oox::drawingml::PresetTextShapeContext(
|
|
*this, rAttribs, *(getShape()->getCustomShapeProperties()));
|
|
case XML_txbx:
|
|
{
|
|
mpShapePtr->getCustomShapeProperties()->setShapeTypeOverride(true);
|
|
mpShapePtr->setTextBox(true);
|
|
//in case if the textbox is linked, save the attributes
|
|
//for further processing.
|
|
if (rAttribs.hasAttribute(XML_id))
|
|
{
|
|
std::optional<OUString> id = rAttribs.getString(XML_id);
|
|
if (id.has_value())
|
|
{
|
|
oox::drawingml::LinkedTxbxAttr linkedTxtBoxAttr;
|
|
linkedTxtBoxAttr.id = id.value().toInt32();
|
|
mpShapePtr->setTxbxHasLinkedTxtBox(true);
|
|
mpShapePtr->setLinkedTxbxAttributes(linkedTxtBoxAttr);
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
break;
|
|
case XML_linkedTxbx:
|
|
{
|
|
//in case if the textbox is linked, save the attributes
|
|
//for further processing.
|
|
mpShapePtr->getCustomShapeProperties()->setShapeTypeOverride(true);
|
|
mpShapePtr->setTextBox(true);
|
|
std::optional<OUString> id = rAttribs.getString(XML_id);
|
|
std::optional<OUString> seq = rAttribs.getString(XML_seq);
|
|
if (id.has_value() && seq.has_value())
|
|
{
|
|
oox::drawingml::LinkedTxbxAttr linkedTxtBoxAttr;
|
|
linkedTxtBoxAttr.id = id.value().toInt32();
|
|
linkedTxtBoxAttr.seq = seq.value().toInt32();
|
|
mpShapePtr->setTxbxHasLinkedTxtBox(true);
|
|
mpShapePtr->setLinkedTxbxAttributes(linkedTxtBoxAttr);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return ShapeContext::onCreateContext(nElementToken, rAttribs);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void WpsContext::onEndElement()
|
|
{
|
|
// Convert shape to Fontwork shape if necessary and meaningful.
|
|
// Only at end of bodyPr all needed info is available.
|
|
|
|
if (getBaseToken(getCurrentElement()) != XML_bodyPr)
|
|
return;
|
|
|
|
// Make sure all needed parts are available
|
|
auto* pCustomShape
|
|
= dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(mxShape));
|
|
if (!pCustomShape || !mpShapePtr || !mxShape.is())
|
|
return;
|
|
uno::Reference<beans::XPropertySet> xShapePropertySet(mxShape, uno::UNO_QUERY);
|
|
if (!xShapePropertySet.is())
|
|
return;
|
|
// This is the text in the frame, associated with the shape
|
|
uno::Reference<text::XText> xText(mxShape, uno::UNO_QUERY);
|
|
if (!xText.is())
|
|
return;
|
|
|
|
OUString sMSPresetType;
|
|
comphelper::SequenceAsHashMap aCustomShapeGeometry(
|
|
xShapePropertySet->getPropertyValue("CustomShapeGeometry"));
|
|
aCustomShapeGeometry["PresetTextWarp"] >>= sMSPresetType;
|
|
if (sMSPresetType.isEmpty() || sMSPresetType == u"textNoShape")
|
|
return;
|
|
|
|
// Word can combine its "abc Transform" with a lot of shape types. LibreOffice can only render
|
|
// the old kind WordArt, which is based on a rectangle. In case of non rectangular shape we keep
|
|
// the shape and do not convert the text to Fontwork.
|
|
OUString sType;
|
|
aCustomShapeGeometry["Type"] >>= sType;
|
|
if (sType != u"ooxml-rect")
|
|
return;
|
|
|
|
// Copy properties from frame text to have them available after the frame is removed.
|
|
std::vector<beans::PropertyValue> aTextPropVec;
|
|
if (!lcl_getTextPropsFromFrameText(xText, aTextPropVec))
|
|
return;
|
|
comphelper::SequenceAsHashMap aTextPropMap(comphelper::containerToSequence(aTextPropVec));
|
|
|
|
// Copy text content from frame to shape. Since Fontwork uses simple text anyway, we can use
|
|
// a string.
|
|
OUString sFrameContent(xText->getString());
|
|
pCustomShape->NbcSetText(sFrameContent);
|
|
|
|
// Setting the property "TextBox" to false includes removing the attached frame from the shape.
|
|
xShapePropertySet->setPropertyValue("TextBox", uno::Any(false));
|
|
|
|
// Set the shape into text path mode, so that the text is drawn as Fontwork. Word renders a legacy
|
|
// "text on path" without the legacy stretching, therefore use false for bFromWordArt.
|
|
mpShapePtr->getCustomShapeProperties()->setShapeTypeOverride(true);
|
|
FontworkHelpers::putCustomShapeIntoTextPathMode(mxShape, getShape()->getCustomShapeProperties(),
|
|
sMSPresetType, /*bFromWordArt*/ false);
|
|
|
|
// Apply the text props to the fontwork shape
|
|
lcl_setTextPropsToShape(xShapePropertySet, aTextPropVec); // includes e.g. FontName
|
|
lcl_setTextAnchorFromTextProps(xShapePropertySet, aTextPropMap);
|
|
|
|
// Fontwork in LO uses fill and stroke of the shape and cannot style text portions individually.
|
|
// "abc Transform" in Word uses fill and outline of the characters.
|
|
// We need to copy the properties from a run to the shape.
|
|
oox::drawingml::ShapePropertyMap aStrokeShapeProps(getFilter().getModelObjectHelper());
|
|
oox::drawingml::LineProperties aCreatedLineProperties
|
|
= lcl_generateLinePropertiesFromTextProps(aTextPropMap);
|
|
aCreatedLineProperties.pushToPropMap(aStrokeShapeProps, getFilter().getGraphicHelper());
|
|
lcl_applyShapePropsToShape(xShapePropertySet, aStrokeShapeProps);
|
|
|
|
oox::drawingml::ShapePropertyMap aFillShapeProps(getFilter().getModelObjectHelper());
|
|
oox::drawingml::FillProperties aCreatedFillProperties
|
|
= lcl_generateFillPropertiesFromTextProps(aTextPropMap);
|
|
aCreatedFillProperties.pushToPropMap(aFillShapeProps, getFilter().getGraphicHelper(),
|
|
/*nShapeRotation*/ 0,
|
|
/*nPhClr*/ API_RGB_TRANSPARENT,
|
|
/*aShapeSize*/ css::awt::Size(0, 0), /*nPhClrTheme*/ -1,
|
|
pCustomShape->IsMirroredX(), pCustomShape->IsMirroredY(),
|
|
/*bIsCustomShape*/ true);
|
|
lcl_applyShapePropsToShape(xShapePropertySet, aFillShapeProps);
|
|
|
|
// Copying the text content from frame to shape as string has lost the styles. Apply the used text
|
|
// properties back to all runs in the text.
|
|
uno::Reference<text::XText> xNewText(pCustomShape->getUnoShape(), uno::UNO_QUERY);
|
|
if (xNewText.is())
|
|
lcl_applyUsedTextPropsToAllTextRuns(xNewText, aTextPropVec);
|
|
|
|
// Fontwork stretches the text to the given path. So adapt shape size to text is nonsensical.
|
|
xShapePropertySet->setPropertyValue("TextAutoGrowHeight", uno::Any(false));
|
|
xShapePropertySet->setPropertyValue("TextAutoGrowWidth", uno::Any(false));
|
|
}
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|