tdf#51195, tdf#100348 Convert Fontwork to TextWarp on export
LibreOffice has Fontwork as property text-path in enhanced-custom-
geometry. OOXML has the similar TextWarp as property of a textbox. The
patch converts the custom shape to a textbox and sets the attribute
prstTxWarp. Fill and outline of the Fontwork is lost. The import and
export of fill and outline is tracked in tdf#119221 and still needs
to be fixed.
Change-Id: I8ea7b305d7d0a8367d61c1789f22b56d274a311d
Reviewed-on: https://gerrit.libreoffice.org/74057
Tested-by: Jenkins
Reviewed-by: Thorsten Behrens <Thorsten.Behrens@CIB.de>
Reviewed-by: Regina Henschel <rb.henschel@t-online.de>
diff --git a/oox/inc/drawingml/presetgeometrynames.hxx b/oox/inc/drawingml/presetgeometrynames.hxx
index 51721e4..358fc9a 100644
--- a/oox/inc/drawingml/presetgeometrynames.hxx
+++ b/oox/inc/drawingml/presetgeometrynames.hxx
@@ -16,6 +16,7 @@
namespace PresetGeometryTypeNames
{
OOX_DLLPUBLIC OUString GetFontworkType(const OUString& rMsoType);
OOX_DLLPUBLIC OUString GetMsoName(const OUString& rFontworkType);
}
#endif
diff --git a/oox/source/drawingml/presetgeometrynames.cxx b/oox/source/drawingml/presetgeometrynames.cxx
index 272094d..dea972d 100644
--- a/oox/source/drawingml/presetgeometrynames.cxx
+++ b/oox/source/drawingml/presetgeometrynames.cxx
@@ -91,4 +91,25 @@ OUString PresetGeometryTypeNames::GetFontworkType(const OUString& rMsoType)
return OUString(pRetValue, strlen(pRetValue), RTL_TEXTENCODING_ASCII_US);
}
OUString PresetGeometryTypeNames::GetMsoName(const OUString& rFontworkType)
{
static const PresetGeometryHashMap s_HashMapInv = []() {
PresetGeometryHashMap aHInv;
for (const auto& item : pPresetGeometryNameArray)
aHInv[item.pFontworkType] = item.pMsoName;
return aHInv;
}();
const char* pRetValue = "";
int i, nLen = rFontworkType.getLength();
std::unique_ptr<char[]> pBuf(new char[nLen + 1]);
for (i = 0; i < nLen; i++)
pBuf[i] = static_cast<char>(rFontworkType[i]);
pBuf[i] = 0;
PresetGeometryHashMap::const_iterator aHashIter(s_HashMapInv.find(pBuf.get()));
if (aHashIter != s_HashMapInv.end())
pRetValue = (*aHashIter).second;
return OUString(pRetValue, strlen(pRetValue), RTL_TEXTENCODING_ASCII_US);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
diff --git a/oox/source/export/drawingml.cxx b/oox/source/export/drawingml.cxx
index c87cb3a..575878a 100644
--- a/oox/source/export/drawingml.cxx
+++ b/oox/source/export/drawingml.cxx
@@ -2544,6 +2544,7 @@ void DrawingML::WriteText( const Reference< XInterface >& rXIface, const OUStrin
return;
sal_Int32 nTextRotateAngle = 0;
bool bIsFontworkShape(presetWarp.startsWith("text") && (presetWarp != "textNoShape"));
#define DEFLRINS 254
#define DEFTBINS 127
@@ -2582,6 +2583,8 @@ void DrawingML::WriteText( const Reference< XInterface >& rXIface, const OUStrin
}
}
Sequence<drawing::EnhancedCustomShapeAdjustmentValue>aAdjustmentSeq;
if (GetProperty(rXPropSet, "CustomShapeGeometry"))
{
Sequence< PropertyValue > aProps;
@@ -2601,8 +2604,11 @@ void DrawingML::WriteText( const Reference< XInterface >& rXIface, const OUStrin
sWritingMode = "vert270";
bVertical = true;
}
break;
if (!bIsFontworkShape)
break;
}
else if (aProps[i].Name == "AdjustmentValues")
aProps[i].Value >>= aAdjustmentSeq;
}
}
}
@@ -2647,10 +2653,60 @@ void DrawingML::WriteText( const Reference< XInterface >& rXIface, const OUStrin
XML_anchorCtr, bHorizontalCenter ? "1" : nullptr,
XML_vert, sWritingMode,
XML_rot, (nTextRotateAngle != 0) ? oox::drawingml::calcRotationValue( nTextRotateAngle * 100 ).getStr() : nullptr );
if( !presetWarp.isEmpty())
if (bIsFontworkShape)
{
mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, presetWarp.toUtf8());
if (aAdjustmentSeq.getLength() > 0)
{
mpFS->startElementNS(XML_a, XML_prstTxWarp, XML_prst, presetWarp.toUtf8());
mpFS->startElementNS(XML_a, XML_avLst);
for (sal_Int32 i = 0, nElems = aAdjustmentSeq.getLength(); i < nElems; ++i )
{
OString sName = OString("adj") + (( nElems > 1 ) ? OString::number(i + 1) : OString());
double fValue(0.0);
if (aAdjustmentSeq[i].Value.getValueTypeClass() == TypeClass_DOUBLE)
aAdjustmentSeq[i].Value >>= fValue;
else
{
sal_Int32 nNumber(0);
aAdjustmentSeq[i].Value >>= nNumber;
fValue = static_cast<double>(nNumber);
}
// Convert from binary coordinate system with viewBox "0 0 21600 21600" and simple degree
// to DrawingML with coordinate range 0..100000 and angle in 1/60000 degree.
// Reverse to conversion in lcl_createPresetShape in drawingml/shape.cxx on import.
if (presetWarp == "textArchDown" || presetWarp == "textArchUp"
|| presetWarp == "textButton" || presetWarp == "textCircle"
|| ((i == 0) && (presetWarp == "textArchDownPour" || presetWarp == "textArchUpPour"
|| presetWarp == "textButtonPour" || presetWarp == "textCirclePour")))
{
fValue *= 60000.0;
}
else if ((i == 1) && (presetWarp == "textDoubleWave1" || presetWarp == "textWave1"
|| presetWarp == "textWave2" || presetWarp == "textWave4"))
{
fValue = fValue / 0.216 - 50000.0;
}
else if ((i == 1) && (presetWarp == "textArchDownPour" || presetWarp == "textArchUpPour"
|| presetWarp == "textButtonPour" || presetWarp == "textCirclePour"))
{
fValue /= 0.108;
}
else
{
fValue /= 0.216;
}
OString sFmla = OString("val ") + OString::number(std::lround(fValue));
mpFS->singleElementNS(XML_a, XML_gd, XML_name, sName, XML_fmla, sFmla);
}
mpFS->endElementNS( XML_a, XML_avLst );
mpFS->endElementNS(XML_a, XML_prstTxWarp);
}
else
{
mpFS->singleElementNS(XML_a, XML_prstTxWarp, XML_prst, presetWarp.toUtf8());
}
}
if (GetDocumentType() == DOCUMENT_DOCX || GetDocumentType() == DOCUMENT_XLSX)
{
bool bTextAutoGrowHeight = false;
diff --git a/oox/source/export/shapes.cxx b/oox/source/export/shapes.cxx
index d441406..14e5b84 100644
--- a/oox/source/export/shapes.cxx
+++ b/oox/source/export/shapes.cxx
@@ -101,6 +101,7 @@
#include <svx/unoapi.hxx>
#include <oox/export/chartexport.hxx>
#include <oox/mathml/export.hxx>
#include <drawingml/presetgeometrynames.hxx>
using namespace ::css;
using namespace ::css::beans;
@@ -698,13 +699,56 @@ static sal_Int32 lcl_NormalizeAngle( sal_Int32 nAngle )
ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape )
{
// First check, if this is a Fontwork-shape. For DrawingML, such a shape is a
// TextBox shape with body property prstTxWarp.
SAL_INFO("oox.shape", "write custom shape");
Reference< XPropertySet > rXPropSet( xShape, UNO_QUERY );
bool bIsFontworkShape(false);
bool bHasGeometrySeq(false);
Sequence< PropertyValue > aGeometrySeq;
OUString sShapeType;
if (GETA(CustomShapeGeometry))
{
SAL_INFO("oox.shape", "got custom shape geometry");
if (mAny >>= aGeometrySeq)
{
bHasGeometrySeq = true;
SAL_INFO("oox.shape", "got custom shape geometry sequence");
for (int i = 0; i < aGeometrySeq.getLength(); i++)
{
const PropertyValue& rProp = aGeometrySeq[i];
SAL_INFO("oox.shape", "geometry property: " << rProp.Name);
if (rProp.Name == "TextPath")
{
uno::Sequence<beans::PropertyValue> aTextPathSeq;
rProp.Value >>= aTextPathSeq;
for (int k = 0; k < aTextPathSeq.getLength(); k++)
{
const PropertyValue& rTextProp = aTextPathSeq[k];
if (rTextProp.Name == "TextPath")
{
rTextProp.Value >>= bIsFontworkShape;
}
}
}
else if (rProp.Name == "Type")
rProp.Value >>= sShapeType;
}
}
}
if (bIsFontworkShape)
{
// write the correct type to m_presetWarp, WriteTextShape() needs it
// to set TextWarp.
m_presetWarp = PresetGeometryTypeNames::GetMsoName(sShapeType);
ShapeExport::WriteTextShape(xShape); // qualifier to prevent PowerPointShapeExport
return *this;
}
bool bPredefinedHandlesUsed = true;
bool bHasHandles = false;
OUString sShapeType;
ShapeFlag nMirrorFlags = ShapeFlag::NONE;
MSO_SPT eShapeType = EscherPropertyContainer::GetCustomShapeType( xShape, nMirrorFlags, sShapeType );
OSL_ENSURE(nullptr != dynamic_cast< SdrObjCustomShape* >(GetSdrObjectFromXShape(xShape)), "Not a SdrObjCustomShape (!)");
@@ -715,7 +759,7 @@ ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape )
eShapeType));
const char* sPresetShape = msfilter::util::GetOOXMLPresetGeometry(sShapeType.toUtf8().getStr());
SAL_INFO("oox.shape", "custom shape type: " << sShapeType << " ==> " << sPresetShape);
Sequence< PropertyValue > aGeometrySeq;
sal_Int32 nAdjustmentValuesIndex = -1;
awt::Rectangle aViewBox;
uno::Sequence<beans::PropertyValues> aHandles;
@@ -726,12 +770,10 @@ ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape )
// Avoid interference of preset type to the next shape
m_presetWarp = "";
if( GETA( CustomShapeGeometry ) ) {
SAL_INFO("oox.shape", "got custom shape geometry");
if( mAny >>= aGeometrySeq ) {
SAL_INFO("oox.shape", "got custom shape geometry sequence");
for( int i = 0; i < aGeometrySeq.getLength(); i++ ) {
if (bHasGeometrySeq)
{
for (int i = 0; i < aGeometrySeq.getLength(); i++)
{
const PropertyValue& rProp = aGeometrySeq[ i ];
SAL_INFO("oox.shape", "geometry property: " << rProp.Name);
@@ -757,7 +799,6 @@ ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape )
}
else if ( rProp.Name == "ViewBox" )
rProp.Value >>= aViewBox;
}
}
}
@@ -986,9 +1027,7 @@ ShapeExport& ShapeExport::WriteCustomShape( const Reference< XShape >& xShape )
}
if( rXPropSet.is() )
{
// Preset shape with text has no fill
if( m_presetWarp.isEmpty() || !m_presetWarp.startsWith( "text" ) || m_presetWarp == "textNoShape" )
WriteFill( rXPropSet );
WriteFill( rXPropSet );
WriteOutline( rXPropSet );
WriteShapeEffects( rXPropSet );
WriteShape3DEffects( rXPropSet );
@@ -1793,6 +1832,7 @@ ShapeExport& ShapeExport::WriteTableShape( const Reference< XShape >& xShape )
ShapeExport& ShapeExport::WriteTextShape( const Reference< XShape >& xShape )
{
bool bIsFontworkShape(m_presetWarp.startsWith("text") && m_presetWarp != "textNoShape");
FSHelperPtr pFS = GetFS();
Reference<XPropertySet> xShapeProps(xShape, UNO_QUERY);
pFS->startElementNS(mnXmlNamespace, (GetDocumentType() != DOCUMENT_DOCX ? XML_sp : XML_wsp));
@@ -1831,8 +1871,11 @@ ShapeExport& ShapeExport::WriteTextShape( const Reference< XShape >& xShape )
WriteShapeTransformation( xShape, XML_a );
WritePresetShape( "rect" );
uno::Reference<beans::XPropertySet> xPropertySet(xShape, UNO_QUERY);
WriteBlipOrNormalFill(xPropertySet, "Graphic");
WriteOutline(xPropertySet);
if (!bIsFontworkShape) // Fontwork needs fill and outline on char instead.
{
WriteBlipOrNormalFill(xPropertySet, "Graphic");
WriteOutline(xPropertySet);
}
WriteShapeEffects(xPropertySet);
pFS->endElementNS( mnXmlNamespace, XML_spPr );