/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
// Write the focus rectangle. Work with the focus point, and assume // that it extends 50% in all directions. The below // left/top/right/bottom values are percentages, where 0 means the // edge of the tile rectangle and 100% means the center of it.
rtl::Reference<sax_fastparser::FastAttributeList> pAttributeList(
sax_fastparser::FastSerializerHelper::createAttrList());
sal_Int32 nLeftPercent = rBGradient.GetXOffset();
pAttributeList->add(XML_l, OString::number(nLeftPercent * PER_PERCENT));
sal_Int32 nTopPercent = rBGradient.GetYOffset();
pAttributeList->add(XML_t, OString::number(nTopPercent * PER_PERCENT));
sal_Int32 nRightPercent = 100 - rBGradient.GetXOffset();
pAttributeList->add(XML_r, OString::number(nRightPercent * PER_PERCENT));
sal_Int32 nBottomPercent = 100 - rBGradient.GetYOffset();
pAttributeList->add(XML_b, OString::number(nBottomPercent * PER_PERCENT));
pFS->singleElementNS(XML_a, XML_fillToRect, pAttributeList);
bool DrawingML::GetProperty( const Reference< XPropertySet >& rXPropertySet, const OUString& aName )
{ try
{
mAny = rXPropertySet->getPropertyValue(aName); if (mAny.hasValue()) returntrue;
} catch( const Exception& )
{ /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */
} returnfalse;
}
bool DrawingML::GetPropertyAndState( const Reference< XPropertySet >& rXPropertySet, const Reference< XPropertyState >& rXPropertyState, const OUString& aName, PropertyState& eState )
{ try
{
mAny = rXPropertySet->getPropertyValue(aName); if (mAny.hasValue())
{
eState = rXPropertyState->getPropertyState(aName); returntrue;
}
} catch( const Exception& )
{ /* printf ("exception when trying to get value of property: %s\n", aName.toUtf8()); */
} returnfalse;
}
namespace
{ /// Gets hexa value of color on string format.
OString getColorStr(const ::Color nColor)
{ // Transparency is a separate element.
OString sColor = OString::number(sal_uInt32(nColor) & 0x00FFFFFF, 16); if (sColor.getLength() < 6)
{
OStringBuffer sBuf("0"); int remains = 5 - sColor.getLength();
while (remains > 0)
{
sBuf.append("0");
remains--;
}
// OOXML has no separate transparence gradient but uses transparency in the gradient stops. // So we merge transparency and color and use gradient fill in such case.
basegfx::BGradient aTransparenceGradient;
OUString sFillTransparenceGradientName; bool bNeedGradientFill(false);
// we no longer need to 'guess' if FillTransparenceGradient is used by // comparing it's 1st color to COL_BLACK after having tested that the // FillTransparenceGradientName is set if (!bNeedGradientFill)
{ // Our alpha is a gray color value. const sal_uInt8 nRed(aSingleColor.getRed() * 255.0);
// drawingML alpha is a percentage on a 0..100000 scale.
nAlpha = (255 - nRed) * oox::drawingml::MAX_PERCENT / 255;
}
}
// write XML if (bNeedGradientFill)
{ // no longer create copy/PseudoColorGradient, use new API of // WriteGradientFill to express fix fill color
mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
WriteGradientFill(nullptr, nFillColor, &aTransparenceGradient);
mpFS->endElementNS( XML_a, XML_gradFill );
} elseif ( nFillColor != nOriginalColor )
{ // the user has set a different color for the shape if (!WriteSchemeColor(u"FillComplexColor"_ustr, rXPropSet))
{
WriteSolidFill(::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha);
}
} // tdf#91332 LO doesn't export the actual theme.xml in XLSX. elseif ( !sColorFillScheme.isEmpty() && GetDocumentType() != DOCUMENT_XLSX )
{ // the shape had a scheme color and the user didn't change it
WriteSolidFill( sColorFillScheme, aTransformations, nAlpha );
} else
{ // the shape had a custom color and the user didn't change it // tdf#124013
WriteSolidFill( ::Color(ColorTransparency, nFillColor & 0xffffff), nAlpha );
}
}
// use BGradient constructor directly, it will take care of Gradient/Gradient2
basegfx::BGradient aGradient = model::gradient::getFromAny(mAny);
// get InteropGrabBag and search the relevant attributes
basegfx::BGradient aOriginalGradient;
Sequence< PropertyValue > aGradientStops; if ( GetProperty( rXPropSet, u"InteropGrabBag"_ustr ) )
{
Sequence< PropertyValue > aGrabBag;
mAny >>= aGrabBag; for (constauto& rProp : aGrabBag) if( rProp.Name == "GradFillDefinition" )
rProp.Value >>= aGradientStops; elseif( rProp.Name == "OriginalGradFill" )
aOriginalGradient = model::gradient::getFromAny(rProp.Value);
}
// check if an ooxml gradient had been imported and if the user has modified it // Gradient grab-bag depends on theme grab-bag, which is implemented // only for DOCX. if (aOriginalGradient == aGradient && GetDocumentType() == DOCUMENT_DOCX)
{ // If we have no gradient stops that means original gradient were defined by a theme. if( aGradientStops.hasElements() )
{
mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
WriteGrabBagGradientFill(aGradientStops, aGradient);
mpFS->endElementNS( XML_a, XML_gradFill );
}
} else
{
mpFS->startElementNS(XML_a, XML_gradFill, XML_rotWithShape, "0");
if (GetProperty(rXPropSet, u"FillTransparenceGradientName"_ustr)
&& (mAny >>= sFillTransparenceGradientName)
&& !sFillTransparenceGradientName.isEmpty()
&& GetProperty(rXPropSet, u"FillTransparenceGradient"_ustr))
{ // TransparenceGradient is only used when name is not empty
aTransparenceGradient = model::gradient::getFromAny(mAny);
pTransparenceGradient = &aTransparenceGradient;
} elseif (GetProperty(rXPropSet, u"FillTransparence"_ustr))
{ // no longer create PseudoTransparencyGradient, use new API of // WriteGradientFill to express fix transparency
sal_Int32 nTransparency(0);
mAny >>= nTransparency; // nTransparency is [0..100]%
fTransparency = nTransparency * 0.01;
}
// tdf#155852 The gradient might wrongly have StepCount==0, as the draw:gradient-step-count // attribute in ODF does not belong to the gradient definition but is an attribute in // the graphic style of the shape. if (GetProperty(rXPropSet, u"FillGradientStepCount"_ustr))
{
sal_Int16 nStepCount = 0;
mAny >>= nStepCount;
aGradient.SetSteps(nStepCount);
}
if (nullptr != pColorGradient)
{ // extract and correct/process ColorStops
basegfx::utils::prepareColorStops(*pColorGradient, aColorStops, aSingleColor);
// tdf#155827 Convert 'axial' to 'linear' before synchronize and for each gradient separate. if (aColorStops.size() > 0 && awt::GradientStyle_AXIAL == pColorGradient->GetGradientStyle())
aColorStops.doApplyAxial();
} if (nullptr != pTransparenceGradient)
{ // remember basic Gradient definition to use // So we can get the gradient geometry in any case from pGradient. if (nullptr == pGradient)
{
pGradient = pTransparenceGradient;
}
// extract and correct/process AlphaStops
basegfx::utils::prepareColorStops(*pTransparenceGradient, aAlphaStops, aSingleAlpha); if (aAlphaStops.size() > 0
&& awt::GradientStyle_AXIAL == pTransparenceGradient->GetGradientStyle())
{
aAlphaStops.doApplyAxial();
}
}
if (nullptr == pGradient)
{ // an error - see comment in header - is to give neither pColorGradient // nor pTransparenceGradient
assert(false && "pColorGradient or pTransparenceGradient should be set"); return;
}
// Apply steps if used. That increases the number of stops and thus needs to be done before // synchronize. if (pGradient->GetSteps())
{
aColorStops.doApplySteps(pGradient->GetSteps()); // transparency gradients are always automatic, so do not have steps.
}
// synchronize ColorStops and AlphaStops as preparation to export // so also gradients 'coupled' indirectly using the 'FillTransparenceGradient' // method (at import time) will be exported again.
basegfx::utils::synchronizeColorStops(aColorStops, aAlphaStops, aSingleColor, aSingleAlpha);
if (aColorStops.size() != aAlphaStops.size())
{ // this is an error - synchronizeColorStops above *has* to create that // state, see description there (!)
assert(false && "oox::WriteGradientFill: non-synchronized gradients (!)"); return;
}
bool bLinearOrAxial(awt::GradientStyle_LINEAR == pGradient->GetGradientStyle()
|| awt::GradientStyle_AXIAL == pGradient->GetGradientStyle()); if (!bLinearOrAxial)
{ // case awt::GradientStyle_RADIAL: // case awt::GradientStyle_ELLIPTICAL: // case awt::GradientStyle_RECT: // case awt::GradientStyle_SQUARE: // all these types need the gradients to be mirrored
aColorStops.reverseColorStops();
aAlphaStops.reverseColorStops();
}
// If there were one stop, prepareColorStops() method would have cleared aColorStops, same for // aAlphaStops. In case of empty stops vectors synchronizeColorStops() method creates two stops // for each. So at this point we have at least two stops and can fulfill the requirement of // <gsLst> element to have at least two child elements.
if (bLinearOrAxial)
{ // CT_LinearShadeProperties, cases where gradient rotation has to be exported // 'scaled' does not exist in LO, so only 'ang'. const sal_Int16 nAngle(pGradient->GetAngle());
mpFS->singleElementNS(
XML_a, XML_lin, XML_ang,
OString::number(((3600 - static_cast<sal_Int32>(nAngle) + 900) * 6000) % 21600000));
} else
{ // CT_PathShadeProperties, cases where gradient path has to be exported // Concentric fill is not yet implemented, therefore no type 'shape', only 'circle' or 'rect' constbool bCircle(pGradient->GetGradientStyle() == awt::GradientStyle_RADIAL ||
pGradient->GetGradientStyle() == awt::GradientStyle_ELLIPTICAL);
WriteGradientPath(*pGradient, mpFS, bCircle);
}
}
switch( nArrowLength )
{ case ESCHER_LineShortArrow:
len = "sm"; break; default: case ESCHER_LineMediumLenArrow:
len = "med"; break; case ESCHER_LineLongArrow:
len = "lg"; break;
}
switch( eLineEnd )
{ default: case ESCHER_LineNoEnd:
type = "none"; break; case ESCHER_LineArrowEnd:
type = "triangle"; break; case ESCHER_LineArrowStealthEnd:
type = "stealth"; break; case ESCHER_LineArrowDiamondEnd:
type = "diamond"; break; case ESCHER_LineArrowOvalEnd:
type = "oval"; break; case ESCHER_LineArrowOpenEnd:
type = "arrow"; break;
}
switch( nArrowWidth )
{ case ESCHER_LineNarrowArrow:
width = "sm"; break; default: case ESCHER_LineMediumWidthArrow:
width = "med"; break; case ESCHER_LineWideArrow:
width = "lg"; break;
}
[[fallthrough]]; case drawing::LineStyle_SOLID: default: if (GetProperty(rXPropSet, u"LineColor"_ustr))
{
nColor = ::Color(ColorTransparency, mAny.get<sal_uInt32>() & 0xffffff);
bColorSet = true;
} if (GetProperty(rXPropSet, u"LineTransparence"_ustr))
{
nColorAlpha = MAX_PERCENT - (mAny.get<sal_Int16>() * PER_PERCENT);
} if (aLineCap == LineCap_ROUND)
cap = "rnd"; elseif (aLineCap == LineCap_SQUARE)
cap = "sq"; break;
}
// if the line-width was not modified after importing then the original EMU value will be exported to avoid unexpected conversion (rounding) error if (nEmuLineWidth == 0 || static_cast<sal_uInt32>(oox::drawingml::convertEmuToHmm(nEmuLineWidth)) != nLineWidth)
nEmuLineWidth = oox::drawingml::convertHmmToEmu(nLineWidth);
mpFS->startElementNS( XML_a, XML_ln,
XML_cap, cap,
XML_w, sax_fastparser::UseIf(OString::number(nEmuLineWidth),
nLineWidth == 0 || GetDocumentType() == DOCUMENT_XLSX // tdf#119565 LO doesn't export the actual theme.xml in XLSX.
|| (nLineWidth > 1 && nStyleLineWidth != nLineWidth)));
if( bColorSet )
{ if( nColor != nOriginalColor )
{ // the user has set a different color for the line if (!WriteSchemeColor(u"LineComplexColor"_ustr, rXPropSet))
WriteSolidFill(nColor, nColorAlpha);
} elseif( !sColorFillScheme.isEmpty() )
{ // the line had a scheme color and the user didn't change it
WriteSolidFill( aResolvedColorFillScheme, aTransformations );
} else
{
WriteSolidFill( nColor, nColorAlpha );
}
}
if( bDashSet && aStyleLineStyle != drawing::LineStyle_DASH )
{ // Try to detect if it might come from ms preset line style import. // MS Office styles are always relative, both binary and OOXML. // "dot" is always the first dash and "dash" the second one. All OOXML presets linestyles // start with the longer one. Definitions are in OOXML part 1, 20.1.10.49 // The tests are strict, for to not catch styles from standard.sod (as of Aug 2019). bool bIsConverted = false;
bool bIsRelative(aLineDash.Style == DashStyle_RECTRELATIVE || aLineDash.Style == DashStyle_ROUNDRELATIVE); if ( bIsRelative && aLineDash.Dots == 1)
{ // The length were tweaked on import in case of prstDash. Revert it here.
sal_uInt32 nDotLen = aLineDash.DotLen;
sal_uInt32 nDashLen = aLineDash.DashLen;
sal_uInt32 nDistance = aLineDash.Distance; if (aLineCap != LineCap_BUTT && nDistance >= 99)
{
nDistance -= 99;
nDotLen += 99; if (nDashLen > 0)
nDashLen += 99;
} // LO uses length 0 for 100%, if the attribute is missing in ODF. // Other applications might write 100%. Make is unique for the conditions. if (nDotLen == 0)
nDotLen = 100; if (nDashLen == 0 && aLineDash.Dashes > 0)
nDashLen = 100;
bIsConverted = true; if (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dot");
} elseif (nDotLen == 400 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dash");
} elseif (nDotLen == 400 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "dashDot");
} elseif (nDotLen == 800 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 300)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDash");
} elseif (nDotLen == 800 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 300)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDot");
} elseif (nDotLen == 800 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 300)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "lgDashDotDot");
} elseif (nDotLen == 100 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDot");
} elseif (nDotLen == 300 && aLineDash.Dashes == 0 && nDashLen == 0 && nDistance == 100)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDash");
} elseif (nDotLen == 300 && aLineDash.Dashes == 1 && nDashLen == 100 && nDistance == 100)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDot");
} elseif (nDotLen == 300 && aLineDash.Dashes == 2 && nDashLen == 100 && nDistance == 100)
{
mpFS->singleElementNS(XML_a, XML_prstDash, XML_val, "sysDashDotDot");
} else
bIsConverted = false;
} // Do not map our own line styles to OOXML prstDash values, because custDash gives better results. if (!bIsConverted)
{
mpFS->startElementNS(XML_a, XML_custDash); // In case of hairline we would need the current pixel size. Instead use a reasonable // ersatz for it. The value is the same as SMALLEST_DASH_WIDTH in xattr.cxx. // (And it makes sure fLineWidth is not zero in below division.) double fLineWidth = nLineWidth > 0 ? nLineWidth : 26.95; int i; double fSp = bIsRelative ? aLineDash.Distance : aLineDash.Distance * 100.0 / fLineWidth; // LO uses line width, in case Distance is zero. MS Office would use a space of zero length. // So set 100% explicitly. if (aLineDash.Distance <= 0)
fSp = 100.0; // In case of custDash, round caps are included in dash length in MS Office. Square caps are added // to dash length, same as in ODF. Change the length values accordingly. if (aLineCap == LineCap_ROUND && fSp > 99.0)
fSp -= 99.0;
if (aLineDash.Dots > 0)
{ double fD = bIsRelative ? aLineDash.DotLen : aLineDash.DotLen * 100.0 / fLineWidth; // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended. if (aLineDash.DotLen == 0)
fD = 100.0; // Tweak dash length, see above. if (aLineCap == LineCap_ROUND && fSp > 99.0)
fD += 99.0;
for( i = 0; i < aLineDash.Dots; i ++ )
{
mpFS->singleElementNS( XML_a, XML_ds,
XML_d , write1000thOfAPercent(fD),
XML_sp, write1000thOfAPercent(fSp) );
}
} if ( aLineDash.Dashes > 0 )
{ double fD = bIsRelative ? aLineDash.DashLen : aLineDash.DashLen * 100.0 / fLineWidth; // LO sets length to 0, if attribute is missing in ODF. Then a relative length of 100% is intended. if (aLineDash.DashLen == 0)
fD = 100.0; // Tweak dash length, see above. if (aLineCap == LineCap_ROUND && fSp > 99.0)
fD += 99.0;
for( i = 0; i < aLineDash.Dashes; i ++ )
{
mpFS->singleElementNS( XML_a , XML_ds,
XML_d , write1000thOfAPercent(fD),
XML_sp, write1000thOfAPercent(fSp) );
}
}
// tdf#119565 LO doesn't export the actual theme.xml in XLSX. if (aStyleLineJoint == LineJoint_NONE || GetDocumentType() == DOCUMENT_XLSX
|| aStyleLineJoint != eLineJoint)
{ // style-defined line joint does not exist, or is different from the shape's joint switch( eLineJoint )
{ case LineJoint_NONE: case LineJoint_BEVEL:
mpFS->singleElementNS(XML_a, XML_bevel); break; default: case LineJoint_MIDDLE: case LineJoint_MITER:
mpFS->singleElementNS(XML_a, XML_miter); break; case LineJoint_ROUND:
mpFS->singleElementNS(XML_a, XML_round); break;
}
}
}
// #i15508# added BMP type for better exports // export not yet active, so adding for reference (not checked) case GfxLinkType::NativeBmp:
sMediaType = u"image/bmp"_ustr;
aExtension = u"bmp"_ustr; break;
/*Earlier, even in case of unhandled graphic types we were proceeding to write the image, which would eventually write an empty image with a zero size, and return a valid relationID, which is incorrect.
*/ return OUString();
}
namespace
{
BitmapChecksum makeChecksumUniqueForSVG(BitmapChecksum aChecksum)
{ // need to modify the checksum so we know it's for SVG - just invert it return ~aChecksum;
}
// extension
OUString aExtension; const OUString& rURL(pMediaObj->getURL()); int nLastDot = rURL.lastIndexOf('.'); if (nLastDot >= 0)
aExtension = rURL.copy(nLastDot).replace(':', '_'); // Colons are not allowed in Zip entry file names, see OStorageHelper::IsValidZipEntryFileName
GetFS()->startElementNS(XML_p, XML_extLst); // media extensions; google this ID for details
GetFS()->startElementNS(XML_p, XML_ext, XML_uri, "{DAA4B4D4-6D71-4841-9C94-3DE7FCFB9230}");
if (GetProperty(rXPropSet, u"AdjustLuminance"_ustr))
nBright = mAny.get<sal_Int16>(); if (GetProperty(rXPropSet, u"AdjustContrast"_ustr))
nContrast = mAny.get<sal_Int32>(); // Used for shapes with picture fill if (GetProperty(rXPropSet, u"FillTransparence"_ustr))
nTransparence = mAny.get<sal_Int32>(); // Used for pictures if (nTransparence == 0 && GetProperty(rXPropSet, u"Transparency"_ustr))
nTransparence = static_cast<sal_Int32>(mAny.get<sal_Int16>());
if (GetProperty(rXPropSet, u"GraphicColorMode"_ustr))
{
drawing::ColorMode aColorMode;
mAny >>= aColorMode; if (aColorMode == drawing::ColorMode_GREYS)
mpFS->singleElementNS(XML_a, XML_grayscl); elseif (aColorMode == drawing::ColorMode_MONO) //black/white has a 0,5 threshold in LibreOffice
mpFS->singleElementNS(XML_a, XML_biLevel, XML_thresh, OString::number(50000)); elseif (aColorMode == drawing::ColorMode_WATERMARK)
{ //map watermark with mso washout
nBright = 70;
nContrast = -70;
}
}
switch (eBitmapMode)
{ case BitmapMode_REPEAT:
WriteXGraphicTile(rXPropSet, rxGraphic, rSize); break; case BitmapMode_STRETCH:
WriteXGraphicStretch(rXPropSet, rxGraphic); break; case BitmapMode_NO_REPEAT:
WriteXGraphicCustomPosition(rXPropSet, rxGraphic, rSize); break; default: break;
}
}
void DrawingML::WriteBlipOrNormalFill(const Reference<XPropertySet>& xPropSet, const OUString& rURLPropName, const awt::Size& rSize)
{ // check for blip and otherwise fall back to normal fill // we always store normal fill properties but OOXML // uses a choice between our fill props and BlipFill if (GetProperty ( xPropSet, rURLPropName ))
WriteBlipFill( xPropSet, rURLPropName ); else
WriteFill(xPropSet, rSize);
}
css::text::GraphicCrop aGraphicCropStruct;
mAny >>= aGraphicCropStruct;
if(GetProperty(rXPropSet, u"CustomShapeGeometry"_ustr))
{ // tdf#134210 GraphicCrop property is handled in import filter because of LibreOffice has not core // feature. We cropped the bitmap physically and MSO shouldn't crop bitmap one more time. When we // have core feature for graphic cropping in custom shapes, we should uncomment the code anymore.
// GraphicCrop is in mm100, so in case the original size is in pixels, convert it over. if (rMapMode.GetMapUnit() == MapUnit::MapPixel)
aOriginalSize = Application::GetDefaultDevice()->PixelToLogic(aOriginalSize, MapMode(MapUnit::Map100thMM));
void DrawingML::WriteXGraphicStretch(uno::Reference<beans::XPropertySet> const & rXPropSet,
uno::Reference<graphic::XGraphic> const & rxGraphic)
{ if (GetDocumentType() != DOCUMENT_DOCX)
{ // Limiting the area used for stretching is not supported in Impress.
mpFS->singleElementNS(XML_a, XML_stretch); return;
}
mpFS->startElementNS(XML_a, XML_stretch);
bool bCrop = false; if (GetProperty(rXPropSet, u"GraphicCrop"_ustr))
{
css::text::GraphicCrop aGraphicCropStruct;
mAny >>= aGraphicCropStruct;
switch (*o3tl::doAccess<RectanglePoint>(mAny))
{ case RectanglePoint_LEFT_TOP: nR = nWidth; nB = nHeight; break; case RectanglePoint_RIGHT_TOP: nL = nWidth; nB = nHeight; break; case RectanglePoint_LEFT_BOTTOM: nR = nWidth; nT = nHeight; break; case RectanglePoint_RIGHT_BOTTOM: nL = nWidth; nT = nHeight; break; case RectanglePoint_LEFT_MIDDLE: nR = nWidth; nT = nB = nHeight / 2; break; case RectanglePoint_RIGHT_MIDDLE: nL = nWidth; nT = nB = nHeight / 2; break; case RectanglePoint_MIDDLE_TOP: nB = nHeight; nL = nR = nWidth / 2; break; case RectanglePoint_MIDDLE_BOTTOM: nT = nHeight; nL = nR = nWidth / 2; break; case RectanglePoint_MIDDLE_MIDDLE: nL = nR = nWidth / 2; nT = nB = nHeight / 2; break; default: break;
}
}
mpFS->startElementNS(XML_a, XML_stretch);
mpFS->singleElementNS(XML_a, XML_fillRect, XML_l,
sax_fastparser::UseIf(OString::number(nL), nL != 0), XML_t,
sax_fastparser::UseIf(OString::number(nT), nT != 0), XML_r,
sax_fastparser::UseIf(OString::number(nR), nR != 0), XML_b,
sax_fastparser::UseIf(OString::number(nB), nB != 0));
if ( aSize.Width < 0 )
aSize.Width = 1000; if ( aSize.Height < 0 )
aSize.Height = 1000; if (!bSuppressRotation)
{
SdrObject* pShape = SdrObject::getSdrObjectFromXShape(rXShape);
nRotation = pShape ? pShape->GetRotateAngle() : 0_deg100; if ( GetDocumentType() != DOCUMENT_DOCX )
{ int faccos=bFlipV ? -1 : 1; int facsin=bFlipH ? -1 : 1;
aPos.X-=(1-faccos*cos(toRadians(nRotation)))*aSize.Width/2-facsin*sin(toRadians(nRotation))*aSize.Height/2;
aPos.Y-=(1-faccos*cos(toRadians(nRotation)))*aSize.Height/2+facsin*sin(toRadians(nRotation))*aSize.Width/2;
} elseif (m_xParent.is() && nRotation != 0_deg100)
{ // Position for rotated shapes inside group is not set by DocxSdrExport.
basegfx::B2DRange aRect(-aSize.Width / 2.0, -aSize.Height / 2.0, aSize.Width / 2.0,
aSize.Height / 2.0);
basegfx::B2DHomMatrix aRotateMatrix =
basegfx::utils::createRotateB2DHomMatrix(toRadians(nRotation));
aRect.transform(aRotateMatrix);
aPos.X += -aSize.Width / 2.0 - aRect.getMinX();
aPos.Y += -aSize.Height / 2.0 - aRect.getMinY();
}
// The RotateAngle property's value is independent from any flipping, and that's exactly what we need here.
uno::Reference<beans::XPropertySet> xPropertySet(rXShape, uno::UNO_QUERY);
uno::Reference<beans::XPropertySetInfo> xPropertySetInfo = xPropertySet->getPropertySetInfo(); if (xPropertySetInfo->hasPropertyByName(u"RotateAngle"_ustr))
{
sal_Int32 nTmp; if (xPropertySet->getPropertyValue(u"RotateAngle"_ustr) >>= nTmp)
nRotation = Degree100(nTmp);
}
// As long as the support of MS Office 3D-features is rudimentary, we restore the original // values from InteropGrabBag. This affects images and custom shapes. if (xPropertySetInfo->hasPropertyByName(UNO_NAME_MISC_OBJ_INTEROPGRABBAG))
{
uno::Sequence<beans::PropertyValue> aGrabBagProps;
xPropertySet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= aGrabBagProps; auto p3DEffectProps = std::find_if(
std::cbegin(aGrabBagProps), std::cend(aGrabBagProps),
[](const PropertyValue& rProp) { return rProp.Name == "3DEffectProperties"; }); if (p3DEffectProps != std::cend(aGrabBagProps))
{
uno::Sequence<beans::PropertyValue> a3DEffectProps;
p3DEffectProps->Value >>= a3DEffectProps; // We have imported a scene3d. if (rXShape->getShapeType() == "com.sun.star.drawing.CustomShape")
{ auto pMSORotation
= std::find_if(std::cbegin(aGrabBagProps), std::cend(aGrabBagProps),
[](const PropertyValue& rProp) { return rProp.Name == "mso-rotation-angle";
});
sal_Int32 nMSORotation = 0; if (pMSORotation != std::cend(aGrabBagProps))
pMSORotation->Value >>= nMSORotation;
WriteTransformation(
rXShape,
tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)),
nXmlNamespace, bFlipHWrite, bFlipVWrite, nMSORotation); return;
} if (rXShape->getShapeType() == "com.sun.star.drawing.GraphicObjectShape")
{ // tdf#133037: restore original rotate angle of image before output auto pCameraProps = std::find_if(
std::cbegin(a3DEffectProps), std::cend(a3DEffectProps),
[](const PropertyValue& rProp) { return rProp.Name == "Camera"; }); if (pCameraProps != std::cend(a3DEffectProps))
{
uno::Sequence<beans::PropertyValue> aCameraProps;
pCameraProps->Value >>= aCameraProps; auto pZRotationProp = std::find_if(
std::cbegin(aCameraProps), std::cend(aCameraProps),
[](const PropertyValue& rProp) { return rProp.Name == "rotRev"; }); if (pZRotationProp != std::cend(aCameraProps))
{
sal_Int32 nTmp = 0;
pZRotationProp->Value >>= nTmp;
nCameraRotation = NormAngle36000(Degree100(nTmp / -600)); // FixMe tdf#160327. Vertical flip will be false.
}
}
}
}
}
} // end if (!bSuppressRotation)
if ((bComplex && GetProperty(rXPropSet, u"CharWeightComplex"_ustr))
|| GetProperty(rXPropSet, u"CharWeight"_ustr))
{ if ( *o3tl::doAccess<float>(mAny) >= awt::FontWeight::SEMIBOLD )
bold = "1";
}
if ((bComplex && GetProperty(rXPropSet, u"CharPostureComplex"_ustr))
|| GetProperty(rXPropSet, u"CharPosture"_ustr)) switch ( *o3tl::doAccess<awt::FontSlant>(mAny) )
{ case awt::FontSlant_OBLIQUE : case awt::FontSlant_ITALIC :
italic = "1"; break; default: break;
}
if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, u"CharUnderline"_ustr, eState)
&& eState == beans::PropertyState_DIRECT_VALUE)
|| GetProperty(rXPropSet, u"CharUnderline"_ustr))
{ switch ( *o3tl::doAccess<sal_Int16>(mAny) )
{ case awt::FontUnderline::NONE :
underline = "none"; break; case awt::FontUnderline::SINGLE :
underline = "sng"; break; case awt::FontUnderline::DOUBLE :
underline = "dbl"; break; case awt::FontUnderline::DOTTED :
underline = "dotted"; break; case awt::FontUnderline::DASH :
underline = "dash"; break; case awt::FontUnderline::LONGDASH :
underline = "dashLong"; break; case awt::FontUnderline::DASHDOT :
underline = "dotDash"; break; case awt::FontUnderline::DASHDOTDOT :
underline = "dotDotDash"; break; case awt::FontUnderline::WAVE :
underline = "wavy"; break; case awt::FontUnderline::DOUBLEWAVE :
underline = "wavyDbl"; break; case awt::FontUnderline::BOLD :
underline = "heavy"; break; case awt::FontUnderline::BOLDDOTTED :
underline = "dottedHeavy"; break; case awt::FontUnderline::BOLDDASH :
underline = "dashHeavy"; break; case awt::FontUnderline::BOLDLONGDASH :
underline = "dashLongHeavy"; break; case awt::FontUnderline::BOLDDASHDOT :
underline = "dotDashHeavy"; break; case awt::FontUnderline::BOLDDASHDOTDOT :
underline = "dotDotDashHeavy"; break; case awt::FontUnderline::BOLDWAVE :
underline = "wavyHeavy"; break;
}
}
if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, u"CharStrikeout"_ustr, eState)
&& eState == beans::PropertyState_DIRECT_VALUE)
|| GetProperty(rXPropSet, u"CharStrikeout"_ustr))
{ switch ( *o3tl::doAccess<sal_Int16>(mAny) )
{ case awt::FontStrikeout::NONE :
strikeout = "noStrike"; break; case awt::FontStrikeout::SINGLE : // LibO supports further values of character // strikeout, OOXML standard (20.1.10.78, // ST_TextStrikeType) however specifies only // 3 - single, double and none. Approximate // the remaining ones by single strike (better // some strike than none at all). // TODO: figure out how to do this better case awt::FontStrikeout::BOLD : case awt::FontStrikeout::SLASH : case awt::FontStrikeout::X :
strikeout = "sngStrike"; break; case awt::FontStrikeout::DOUBLE :
strikeout = "dblStrike"; break;
}
}
if (bLang)
{
css::lang::Locale aLocale;
mAny >>= aLocale;
LanguageTag aLanguageTag( aLocale); if (!aLanguageTag.isSystemLocale())
usLanguage = aLanguageTag.getBcp47MS();
}
if (GetPropertyAndState(rXPropSet, rXPropState, u"CharEscapement"_ustr, eState)
&& eState == beans::PropertyState_DIRECT_VALUE)
mAny >>= nCharEscapement;
if (GetPropertyAndState(rXPropSet, rXPropState, u"CharEscapementHeight"_ustr, eState)
&& eState == beans::PropertyState_DIRECT_VALUE)
mAny >>= nCharEscapementHeight;
if (DFLT_ESC_AUTO_SUPER == nCharEscapement)
{ // Raised by the differences between the ascenders (ascent = baseline to top of highest letter). // The ascent is generally about 80% of the total font height. // That is why DFLT_ESC_PROP (58) leads to 33% (DFLT_ESC_SUPER)
nCharEscapement = .8 * (100 - nCharEscapementHeight);
} elseif (DFLT_ESC_AUTO_SUB == nCharEscapement)
{ // Lowered by the differences between the descenders (descent = baseline to bottom of lowest letter). // The descent is generally about 20% of the total font height. // That is why DFLT_ESC_PROP (58) leads to 8% (DFLT_ESC_SUB)
nCharEscapement = .2 * -(100 - nCharEscapementHeight);
}
if (GetProperty(rXPropSet, u"CharCaseMap"_ustr))
{ switch ( *o3tl::doAccess<sal_Int16>(mAny) )
{ case CaseMap::UPPERCASE :
cap = "all"; break; case CaseMap::SMALLCAPS :
cap = "small"; break;
}
}
mpFS->startElementNS( XML_a, nElement,
XML_b, bold,
XML_i, italic,
XML_lang, sax_fastparser::UseIf(usLanguage, !usLanguage.isEmpty()),
XML_sz, OString::number(nSize), // For Condensed character spacing spc value is negative.
XML_spc, sax_fastparser::UseIf(OString::number(nCharKerning), nCharKerning != 0),
XML_strike, strikeout,
XML_u, underline,
XML_baseline, sax_fastparser::UseIf(OString::number(nCharEscapement*1000), nCharEscapement != 0),
XML_cap, cap );
// Fontwork-shapes in LO have text outline and fill from shape stroke and shape fill // PowerPoint has this as run properties if (IsFontworkShape(rXShapePropSet))
{
WriteOutline(rXShapePropSet);
WriteBlipOrNormalFill(rXShapePropSet, u"Graphic"_ustr);
WriteShapeEffects(rXShapePropSet);
} else
{ // mso doesn't like text color to be placed after typeface if ((bCheckDirect && GetPropertyAndState(rXPropSet, rXPropState, u"CharColor"_ustr, eState)
&& eState == beans::PropertyState_DIRECT_VALUE)
|| GetProperty(rXPropSet, u"CharColor"_ustr))
{
::Color color( ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny) );
SAL_INFO("oox.shape", "run color: " << sal_uInt32(color) << " auto: " << sal_uInt32(COL_AUTO));
// WriteSolidFill() handles MAX_PERCENT as "no transparency".
sal_Int32 nTransparency = MAX_PERCENT; if (rXPropSet->getPropertySetInfo()->hasPropertyByName(u"CharTransparence"_ustr))
{
rXPropSet->getPropertyValue(u"CharTransparence"_ustr) >>= nTransparency; // UNO scale is 0..100, OOXML scale is 0..100000; also UNO tracks transparency, OOXML // tracks opacity.
nTransparency = MAX_PERCENT - (nTransparency * PER_PERCENT);
}
bool bContoured = false; if (GetProperty(rXPropSet, u"CharContoured"_ustr))
bContoured = *o3tl::doAccess<bool>(mAny);
// tdf#127696 If the CharContoured is true, then the text color is white and the outline color is the CharColor. if (bContoured)
{
mpFS->startElementNS(XML_a, XML_ln); if (color == COL_AUTO)
{
mbIsBackgroundDark ? WriteSolidFill(COL_WHITE) : WriteSolidFill(COL_BLACK);
} else
{
color.SetAlpha(255); if (!WriteSchemeColor(u"CharComplexColor"_ustr, rXPropSet))
WriteSolidFill(color, nTransparency);
}
mpFS->endElementNS(XML_a, XML_ln);
WriteSolidFill(COL_WHITE);
} // tdf#104219 In LibreOffice and MS Office, there are two types of colors: // Automatic and Fixed. OOXML is setting automatic color, by not providing color. elseif( color != COL_AUTO )
{
color.SetAlpha(255); // TODO: special handle embossed/engraved if (!WriteSchemeColor(u"CharComplexColor"_ustr, rXPropSet))
{
WriteSolidFill(color, nTransparency);
}
} elseif (GetDocumentType() == DOCUMENT_PPTX)
{ // Resolve COL_AUTO for PPTX since MS Powerpoint doesn't have automatic colors. bool bIsTextBackgroundDark = mbIsBackgroundDark; if (rXShapePropSet.is() && GetProperty(rXShapePropSet, u"FillStyle"_ustr)
&& mAny.get<FillStyle>() != FillStyle_NONE
&& GetProperty(rXShapePropSet, u"FillColor"_ustr))
{
::Color aShapeFillColor(ColorTransparency, mAny.get<sal_uInt32>());
bIsTextBackgroundDark = aShapeFillColor.IsDark();
}
if (bIsTextBackgroundDark)
WriteSolidFill(COL_WHITE); else
WriteSolidFill(COL_BLACK);
}
// tdf#128096, exporting XML_highlight to docx already works fine, // so make sure this code is only run when exporting to pptx, just in case if (GetDocumentType() == DOCUMENT_PPTX)
{ if (GetProperty(rXPropSet, u"CharBackColor"_ustr))
{
::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny)); if( color != COL_AUTO )
{
mpFS->startElementNS(XML_a, XML_highlight);
WriteColor( color );
mpFS->endElementNS( XML_a, XML_highlight );
}
}
}
if (underline
&& ((bCheckDirect
&& GetPropertyAndState(rXPropSet, rXPropState, u"CharUnderlineColor"_ustr, eState)
&& eState == beans::PropertyState_DIRECT_VALUE)
|| GetProperty(rXPropSet, u"CharUnderlineColor"_ustr)))
{
::Color color(ColorTransparency, *o3tl::doAccess<sal_uInt32>(mAny)); // if color is automatic, then we shouldn't write information about color but to take color from character if( color != COL_AUTO )
{
mpFS->startElementNS(XML_a, XML_uFill);
WriteSolidFill( color );
mpFS->endElementNS( XML_a, XML_uFill );
} else
{
mpFS->singleElementNS(XML_a, XML_uFillTx);
}
}
OUString DrawingML::GetDatetimeTypeFromDateTime(SvxDateFormat eDate, SvxTimeFormat eTime)
{
OUString aDateField; switch (eDate)
{ case SvxDateFormat::StdSmall: case SvxDateFormat::A:
aDateField = "datetime"; break; case SvxDateFormat::B:
aDateField = "datetime1"; // 13/02/1996 break; case SvxDateFormat::C:
aDateField = "datetime5"; break; case SvxDateFormat::D:
aDateField = "datetime3"; // 13 February 1996 break; case SvxDateFormat::StdBig: case SvxDateFormat::E: case SvxDateFormat::F:
aDateField = "datetime2"; break; default: break;
}
OUString aTimeField; switch (eTime)
{ case SvxTimeFormat::Standard: case SvxTimeFormat::HH24_MM_SS: case SvxTimeFormat::HH24_MM_SS_00:
aTimeField = "datetime11"; // 13:49:38 break; case SvxTimeFormat::HH24_MM:
aTimeField = "datetime10"; // 13:49 break; case SvxTimeFormat::HH12_MM: case SvxTimeFormat::HH12_MM_AMPM:
aTimeField = "datetime12"; // 01:49 PM break; case SvxTimeFormat::HH12_MM_SS: case SvxTimeFormat::HH12_MM_SS_AMPM: case SvxTimeFormat::HH12_MM_SS_00: case SvxTimeFormat::HH12_MM_SS_00_AMPM:
aTimeField = "datetime13"; // 01:49:38 PM break; default: break;
}
if (!aDateField.isEmpty() && aTimeField.isEmpty()) return aDateField; elseif (!aTimeField.isEmpty() && aDateField.isEmpty()) return aTimeField; elseif (!aDateField.isEmpty() && !aTimeField.isEmpty())
{ if (aTimeField == "datetime11" || aTimeField == "datetime13") // only datetime format that has Date and HH:MM:SS return u"datetime9"_ustr; // dd/mm/yyyy H:MM:SS else // only datetime format that has Date and HH:MM return u"datetime8"_ustr; // dd/mm/yyyy H:MM
} else return u""_ustr;
}
// Our numbullet dialog has set the wrong textencoding for our "StarSymbol" font, // instead of a Unicode encoding the encoding RTL_TEXTENCODING_SYMBOL was used. // Because there might exist a lot of damaged documents I added this two lines // which fixes the bullet problem for the export. if ( aFontDesc.Name.equalsIgnoreAsciiCase("StarSymbol") )
aFontDesc.CharSet = RTL_TEXTENCODING_MS_1252;
if (GetProperty(rXPropSet, u"ParaLeftMargin"_ustr))
mAny >>= nParaLeftMargin; if (GetProperty(rXPropSet, u"ParaFirstLineIndent"_ustr))
mAny >>= nParaFirstLineIndent;
if (GetProperty(rXPropSet, u"ParaTopMargin"_ustr))
mAny >>= nParaTopMargin; if (GetProperty(rXPropSet, u"ParaBottomMargin"_ustr))
mAny >>= nParaBottomMargin;
Reference<XEnumeration> xEnumeration(xAccess->createEnumeration()); if (!xEnumeration.is()) return;
Reference<XTextRange> rRun;
if (!xEnumeration->hasMoreElements()) return;
Any aAny(xEnumeration->nextElement()); if (aAny >>= rRun)
{ float fFirstCharHeight = rnCharHeight / 1000.;
Reference<XPropertySet> xFirstRunPropSet(rRun, UNO_QUERY);
Reference<XPropertySetInfo> xFirstRunPropSetInfo
= xFirstRunPropSet->getPropertySetInfo();
if (xFirstRunPropSetInfo->hasPropertyByName(u"CharHeight"_ustr))
fFirstCharHeight = xFirstRunPropSet->getPropertyValue(u"CharHeight"_ustr).get<float>();
// top inset looks a bit different compared to ppt export // check if something related doesn't work as expected if (GetProperty(rXPropSet, u"TextLeftDistance"_ustr))
mAny >>= nLeft; if (GetProperty(rXPropSet, u"TextRightDistance"_ustr))
mAny >>= nRight; if (GetProperty(rXPropSet, u"TextUpperDistance"_ustr))
mAny >>= nTop; if (GetProperty(rXPropSet, u"TextLowerDistance"_ustr))
mAny >>= nBottom;
// Transform the text distance values so they are compatible with OOXML insets if (xShape.is())
{
sal_Int32 nTextHeight = xShape->getSize().Height; // Hmm, default
// CustomShape can have text area different from shape rectangle auto* pCustomShape
= dynamic_cast<SdrObjCustomShape*>(SdrObject::getSdrObjectFromXShape(xShape)); if (pCustomShape)
{ const EnhancedCustomShape2d aCustomShape2d(*pCustomShape);
nTextHeight = aCustomShape2d.GetTextRect().getOpenHeight(); if (DOCUMENT_DOCX == meDocumentType)
nTextHeight = convertTwipToMm100(nTextHeight);
}
if (nTop + nBottom >= nTextHeight)
{ // Effective bottom would be above effective top of text area. LO normalizes the // effective text area in such case implicitly for rendering. MS needs indents so that // the result is the normalized effective text area.
std::swap(nTop, nBottom);
nTop = nTextHeight - nTop;
nBottom = nTextHeight - nBottom;
}
}
bool bIsFontworkShape(IsFontworkShape(rXPropSet));
OUString sPresetWarp(PresetGeometryTypeNames::GetMsoName(sShapeType)); // ODF may have user defined TextPath, use "textPlain" as ersatz. if (sPresetWarp.isEmpty())
sPresetWarp = bIsFontworkShape ? std::u16string_view(u"textPlain") : std::u16string_view(u"textNoShape");
// Fontwork shapes in LO ignore insets in rendering, Word interprets them. if (GetDocumentType() == DOCUMENT_DOCX && bIsFontworkShape)
{
nLeft = 0;
nRight = 0;
nTop = 0;
nBottom = 0;
}
if (bUpright)
{
Degree100 nShapeRotateAngleDeg100(0_deg100); if (GetProperty(rXPropSet, u"RotateAngle"_ustr))
nShapeRotateAngleDeg100 = Degree100(mAny.get<sal_Int32>()); // Depending on shape rotation, the import has made 90deg changes to properties // "TextPreRotateAngle" and "TextRotateAngle". Revert it. bool bWasAngleChanged
= (nShapeRotateAngleDeg100 > 4500_deg100 && nShapeRotateAngleDeg100 <= 13500_deg100)
|| (nShapeRotateAngleDeg100 > 22500_deg100
&& nShapeRotateAngleDeg100 <= 31500_deg100); if (bWasAngleChanged)
{
nTextRotateAngleDeg100 = nTextRotateAngleDeg100.value_or(0_deg100) + 9000_deg100;
nTextPreRotateAngle -= 90;
} // If text is no longer upright, user has changed something. Do not write 'upright' then. // This try to detect the case assumes, that the text area rotation was 0 in the original // MS Office document. That is likely because MS Office has no UI to set it and the // predefined SmartArt shapes, which use it, do not use 'upright'.
Degree100 nAngleSum = nShapeRotateAngleDeg100 + nTextRotateAngleDeg100.value_or(0_deg100); if (abs(NormAngle18000(nAngleSum)) < 100_deg100) // consider inaccuracy from rounding
{
nTextRotateAngleDeg100.reset(); // 'upright' does not overrule text area rotation.
} else
{ // User changes. Keep current angles.
isUpright.reset(); if (bWasAngleChanged)
{
nTextPreRotateAngle += 90;
nTextRotateAngleDeg100 = nTextRotateAngleDeg100.value_or(0_deg100) - 9000_deg100;
}
}
}
// ToDo: Unsure about this. Need to investigate shapes from diagram import, especially diagrams // with vertical text directions. if (nTextPreRotateAngle != 0 && !sWritingMode)
{ if (nTextPreRotateAngle == -90 || nTextPreRotateAngle == 270)
sWritingMode = "vert"; elseif (nTextPreRotateAngle == -270 || nTextPreRotateAngle == 90)
sWritingMode = "vert270"; elseif (nTextPreRotateAngle == -180 || nTextPreRotateAngle == 180)
{ #ifdefined __GNUC__ && !defined __clang__ && __GNUC__ == 12 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" #endif
nTextRotateAngleDeg100
= NormAngle18000(nTextRotateAngleDeg100.value_or(0_deg100) + 18000_deg100); #ifdefined __GNUC__ && !defined __clang__ && __GNUC__ == 12 #pragma GCC diagnostic pop #endif // ToDo: Examine insets. They might need rotation too. Check diagrams (SmartArt).
} else
SAL_WARN("oox", "unsuitable value for TextPreRotateAngle:" << nTextPreRotateAngle);
} elseif (nTextPreRotateAngle != 0 && sWritingMode && sWritingMode.value() == "eaVert")
{ // ToDo: eaVert plus 270deg clockwise rotation has to be written with vert="horz" // plus attribute 'normalEastAsianFlow="1"' on the <wps:wsp> element.
} // else nothing to do
// Our WritingMode introduces text pre rotation which includes padding, MSO vert does not include // padding. Therefore set padding so, that is looks the same in MSO as in LO. if (sWritingMode)
{ if (sWritingMode.value() == "vert" || sWritingMode.value() == "eaVert")
{
sal_Int32 nHelp = nLeft;
nLeft = nBottom;
nBottom = nRight;
nRight = nTop;
nTop = nHelp;
} elseif (sWritingMode.value() == "vert270")
{
sal_Int32 nHelp = nLeft;
nLeft = nTop;
nTop = nRight;
nRight = nBottom;
nBottom = nHelp;
} elseif (sWritingMode.value() == "mongolianVert")
{ // ToDo: Examine padding
}
}
// Prepare attributes 'anchor' and 'anchorCtr' // LibreOffice has 12 value sets, MS Office only 6. We map them so, that it reverses the // 6 mappings from import, and we assign the others approximately.
TextVerticalAdjust eVerticalAlignment(TextVerticalAdjust_TOP); if (GetProperty(rXPropSet, u"TextVerticalAdjust"_ustr))
mAny >>= eVerticalAlignment;
TextHorizontalAdjust eHorizontalAlignment(TextHorizontalAdjust_CENTER); if (GetProperty(rXPropSet, u"TextHorizontalAdjust"_ustr))
mAny >>= eHorizontalAlignment;
bool bHasWrap = false; bool bWrap = false; // Only custom shapes obey the TextWordWrap option, normal text always wraps. if (dynamic_cast<SvxCustomShape*>(rXIface.get()) && GetProperty(rXPropSet, u"TextWordWrap"_ustr))
{
mAny >>= bWrap;
bHasWrap = true;
}
// tdf#134401: If AUTOGROWWIDTH and AUTOGROWHEIGHT are set, then export it as TextWordWrap if (SvxShapeText* pShpTxt = dynamic_cast<SvxShapeText*>(rXIface.get()))
{ const sdr::properties::BaseProperties& rProperties
= pShpTxt->GetSdrObject()->GetProperties();
if (bBodyPr)
{ constchar* pWrap = (bHasWrap && !bWrap) || bIsFontworkShape ? "none" : nullptr; if (GetDocumentType() == DOCUMENT_DOCX)
{ // In case of DOCX, if we want to have the same effect as // TextShape's automatic word wrapping, then we need to set // wrapping to square.
uno::Reference<lang::XServiceInfo> xServiceInfo(rXIface, uno::UNO_QUERY); if ((xServiceInfo.is() && xServiceInfo->supportsService(u"com.sun.star.drawing.TextShape"_ustr))
|| bIsFontworkShape)
pWrap = "square";
}
sal_Int16 nCols = 0;
sal_Int32 nColSpacing = -1; if (GetProperty(rXPropSet, u"TextColumns"_ustr))
{ if (css::uno::Reference<css::text::XTextColumns> xCols{ mAny, css::uno::UNO_QUERY })
{
nCols = xCols->getColumnCount(); if (css::uno::Reference<css::beans::XPropertySet> xProps{ mAny,
css::uno::UNO_QUERY })
{ if (GetProperty(xProps, u"AutomaticDistance"_ustr))
mAny >>= nColSpacing;
}
}
}
SdrObject* pSdrObject = xShape.is() ? SdrObject::getSdrObjectFromXShape(xShape) : nullptr; const SdrTextObj* pTxtObj = DynCastSdrTextObj( pSdrObject ); if (pTxtObj && mpTextExport)
{
std::vector<beans::PropertyValue> aOldCharFillPropVec; if (bIsFontworkShape)
{ // Users may have set the character fill properties for more convenient editing. // Save the properties before changing them for Fontwork export.
FontworkHelpers::collectCharColorProps(xXText, aOldCharFillPropVec); // Word has properties for abc-transform in the run properties of the text of the shape. // Writer has the Fontwork properties as shape properties. Create the character fill // properties needed for export from the shape fill properties // and apply them to all runs.
std::vector<beans::PropertyValue> aExportCharFillPropVec;
FontworkHelpers::createCharFillPropsFromShape(rXPropSet, aExportCharFillPropVec);
FontworkHelpers::applyPropsToRuns(aExportCharFillPropVec, xXText); // Import has converted some items from CharInteropGrabBag to fill and line // properties of the shape. For export we convert them back because users might have // changed them. And we create them in case we come from an odt document.
std::vector<beans::PropertyValue> aUpdatePropVec;
FontworkHelpers::createCharInteropGrabBagUpdatesFromShapeProps(rXPropSet, aUpdatePropVec);
FontworkHelpers::applyUpdatesToCharInteropGrabBag(aUpdatePropVec, xXText);
}
std::optional<OutlinerParaObject> pParaObj;
/* #i13885# When the object is actively being edited, that text is not set into the objects normal text object, but lives in a separate object.
*/ if (pTxtObj->IsTextEditActive())
{
pParaObj = pTxtObj->CreateEditOutlinerParaObject();
} elseif (pTxtObj->GetOutlinerParaObject())
pParaObj = *pTxtObj->GetOutlinerParaObject();
if (pParaObj)
{ // this is reached only in case some text is attached to the shape
mpTextExport->WriteOutliner(*pParaObj);
}
if (bIsFontworkShape)
FontworkHelpers::applyPropsToRuns(aOldCharFillPropVec, xXText); return;
}
OUString aPath(u"$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/filter/oox-drawingml-adj-names"_ustr);
rtl::Bootstrap::expandMacros(aPath);
SvFileStream aStream(aPath, StreamMode::READ); if (aStream.GetError() != ERRCODE_NONE)
SAL_WARN("oox.shape", "failed to open oox-drawingml-adj-names");
OStringBuffer aLine; while (aStream.ReadLine(aLine))
{
sal_Int32 nIndex = 0; // Each line is in a "key\tvalue" format: read the key, the rest is the value.
OString aKey( o3tl::getToken(aLine, 0, '\t', nIndex) ); if (nIndex >= 0)
{
OString aValue( std::string_view(aLine).substr(nIndex) );
aRet[aKey].push_back(aValue);
} else
{
SAL_WARN("oox.shape", "skipping invalid line: " << std::string_view(aLine));
}
} return aRet;
}
void DrawingML::WritePresetShape( const OString& pShape, MSO_SPT eShapeType, bool bPredefinedHandlesUsed, const PropertyValue& rProp )
{ static std::map< OString, std::vector<OString> > aAdjMap = lcl_getAdjNames(); // If there are predefined adj names for this shape type, look them up now.
std::vector<OString> aAdjustments; auto it = aAdjMap.find(pShape); if (it != aAdjMap.end())
aAdjustments = it->second;
Sequence< drawing::EnhancedCustomShapeAdjustmentValue > aAdjustmentSeq; if ( ( rProp.Value >>= aAdjustmentSeq )
&& eShapeType != mso_sptActionButtonForwardNext // we have adjustments values for these type of shape, but MSO doesn't like them
&& eShapeType != mso_sptActionButtonBackPrevious // so they are now disabled
&& pShape != "rect"//some shape types are commented out in pCustomShapeTypeTranslationTable[] & are being defaulted to rect & rect does not have adjustment values/name.
)
{
SAL_INFO("oox.shape", "adj seq len: " << aAdjustmentSeq.getLength());
sal_Int32 nAdjustmentsWhichNeedsToBeConverted = 0; if ( bPredefinedHandlesUsed )
EscherPropertyContainer::LookForPolarHandles( eShapeType, nAdjustmentsWhichNeedsToBeConverted );
sal_Int32 nValue, nLength = aAdjustmentSeq.getLength(); // aAdjustments will give info about the number of adj values for a particular geometry. For example for hexagon aAdjustments.size() will be 2 and for circular arrow it will be 5 as per lcl_getAdjNames. // Sometimes there are more values than needed, so we ignore the excessive ones. if (aAdjustments.size() <= o3tl::make_unsigned(nLength))
{ for (sal_Int32 i = 0; i < static_cast<sal_Int32>(aAdjustments.size()); i++)
{ if( EscherPropertyContainer::GetAdjustmentValue( aAdjustmentSeq[ i ], i, nAdjustmentsWhichNeedsToBeConverted, nValue ) )
{ // If the document model doesn't have an adjustment name (e.g. shape was created from VML), then take it from the predefined list.
OString aAdjName = aAdjustmentSeq[i].Name.isEmpty()
? aAdjustments[i]
: aAdjustmentSeq[i].Name.toUtf8();
namespace// helpers for DrawingML::WriteCustomGeometry
{
sal_Int32
FindNextCommandEndSubpath(const sal_Int32 nStart, const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments)
{
sal_Int32 i = nStart < 0 ? 0 : nStart; while (i < rSegments.getLength() && rSegments[i].Command != ENDSUBPATH)
i++; return i;
}
bool HasCommandInSubPath(const sal_Int16 nCommand, const sal_Int32 nFirst, const sal_Int32 nLast, const uno::Sequence<drawing::EnhancedCustomShapeSegment>& rSegments)
{ for (sal_Int32 i = nFirst < 0 ? 0 : nFirst; i <= nLast && i < rSegments.getLength(); i++)
{ if (rSegments[i].Command == nCommand) returntrue;
} returnfalse;
}
// Ellipse is given by radii fwR and fhR and center (fCx|fCy). The ray from center through point RayP // intersects the ellipse in point S and this point S has angle fAngleDeg in degrees. void getEllipsePointAndAngleFromRayPoint(double& rfAngleDeg, double& rfSx, double& rfSy, constdouble fWR, constdouble fHR, constdouble fCx, constdouble fCy, constdouble fRayPx, constdouble fRayPy)
{ if (basegfx::fTools::equalZero(fWR) || basegfx::fTools::equalZero(fHR))
{
rfSx = fCx; // needed for getting new 'current point'
rfSy = fCy;
} else
{ // center ellipse at origin, stretch in y-direction to circle, flip to Math orientation // and get angle double fCircleMathAngle = atan2(-fWR / fHR * (fRayPy - fCy), fRayPx - fCx); // use angle for intersection point on circle and stretch back to ellipse double fPointMathEllipse_x = fWR * cos(fCircleMathAngle); double fPointMathEllipse_y = fHR * sin(fCircleMathAngle); // get angle of intersection point on ellipse double fEllipseMathAngle = atan2(fPointMathEllipse_y, fPointMathEllipse_x); // convert from Math to View orientation and shift ellipse back from origin
rfAngleDeg = -basegfx::rad2deg(fEllipseMathAngle);
rfSx = fPointMathEllipse_x + fCx;
rfSy = -fPointMathEllipse_y + fCy;
}
}
if ( nExpectedPairCount > aPairs.getLength() )
{
SAL_WARN("oox.shape", "Segments need " << nExpectedPairCount << " coordinates, but Coordinates have only " << aPairs.getLength() << " pairs."); returnfalse;
}
// A EnhancedCustomShape2d caches the equation results. Therefore we use only one of it for the // entire method. const EnhancedCustomShape2d aCustomShape2d(const_cast<SdrObjCustomShape&>(rSdrObjCustomShape));
// Prepare width and height for <a:path> bool bUseGlobalViewBox(false);
// nViewBoxWidth must be integer otherwise ReplaceGeoWidth in aCustomShape2d.GetParameter() is not // triggered; same for height.
sal_Int32 nViewBoxWidth(0);
sal_Int32 nViewBoxHeight(0); if (!aPathSize.hasElements())
{
bUseGlobalViewBox = true; // If draw:viewBox is missing in draw:enhancedGeometry, then import sets // viewBox="0 0 21600 21600". Missing ViewBox can only occur, if user has manipulated // current file via macro. Author of macro has to fix it. auto pProp = std::find_if(
std::cbegin(*pGeometrySeq), std::cend(*pGeometrySeq),
[](const beans::PropertyValue& rGeomProp) { return rGeomProp.Name == "ViewBox"; }); if (pProp != std::cend(*pGeometrySeq))
{
css::awt::Rectangle aViewBox; if (pProp->Value >>= aViewBox)
{
nViewBoxWidth = aViewBox.Width;
nViewBoxHeight = aViewBox.Height;
css::drawing::EnhancedCustomShapeParameter aECSP;
aECSP.Type = css::drawing::EnhancedCustomShapeParameterType::NORMAL;
aECSP.Value <<= nViewBoxWidth; double fRetValue;
aCustomShape2d.GetParameter(fRetValue, aECSP, true, false);
nViewBoxWidth = basegfx::fround(fRetValue);
aECSP.Value <<= nViewBoxHeight;
aCustomShape2d.GetParameter(fRetValue, aECSP, false, true);
nViewBoxHeight = basegfx::fround(fRetValue);
}
} // Import from oox or documents, which are imported from oox and saved to strict ODF, might // have no subViewSize but viewBox="0 0 0 0". We need to generate width and height in those // cases. Even if that is fixed, we need the substitute for old documents. if ((nViewBoxWidth == 0 && nViewBoxHeight == 0) || pProp == std::cend(*pGeometrySeq))
{ // Generate a substitute based on point coordinates
sal_Int32 nXMin(0);
aPairs[0].First.Value >>= nXMin;
sal_Int32 nXMax = nXMin;
sal_Int32 nYMin(0);
aPairs[0].Second.Value >>= nYMin;
sal_Int32 nYMax = nYMin;
for (constauto& rPair : aPairs)
{
sal_Int32 nX = GetCustomGeometryPointValue(rPair.First, aCustomShape2d,
bReplaceGeoWidth, false);
sal_Int32 nY = GetCustomGeometryPointValue(rPair.Second, aCustomShape2d, false,
bReplaceGeoHeight); if (nX < nXMin)
nXMin = nX; if (nY < nYMin)
nYMin = nY; if (nX > nXMax)
nXMax = nX; if (nY > nYMax)
nYMax = nY;
}
nViewBoxWidth = std::max(nXMax, nXMax - nXMin);
nViewBoxHeight = std::max(nYMax, nYMax - nYMin);
} // ToDo: Other values of left,top than 0,0 are not considered yet. Such would require a // shift of the resulting path coordinates.
}
TextAreaRect aTextAreaRect;
std::vector<Guide> aGuideList; // for now only for <a:rect>
prepareTextArea(aCustomShape2d, aGuideList, aTextAreaRect);
prepareGluePoints(aGuideList, aEquationSeq, aGluePoints, bOOXML, nViewBoxWidth, nViewBoxHeight);
mpFS->startElementNS(XML_a, XML_custGeom);
mpFS->singleElementNS(XML_a, XML_avLst); if (aGuideList.empty())
{
mpFS->singleElementNS(XML_a, XML_gdLst);
} else
{
mpFS->startElementNS(XML_a, XML_gdLst); for (autoconst& elem : aGuideList)
{
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);
if (!aGuideList.empty())
{
mpFS->startElementNS(XML_a, XML_cxnLst); for (auto it = aGuideList.begin(); it != aGuideList.end(); ++it)
{ auto aNextIt = std::next(it); if (aNextIt != aGuideList.end() && it->sName.startsWith("GluePoint")
&& aNextIt->sName.startsWith("GluePoint"))
{
mpFS->startElementNS(XML_a, XML_cxn, XML_ang, "0");
mpFS->singleElementNS(XML_a, XML_pos, XML_x, it->sName, XML_y, aNextIt->sName);
mpFS->endElementNS(XML_a, XML_cxn);
// Iterate over subpaths
sal_Int32 nPairIndex = 0; // index over "Coordinates"
sal_Int32 nPathSizeIndex = 0; // index over "SubViewSize"
sal_Int32 nSubpathStartIndex(0); // index over "Segments"
sal_Int32 nSubPathIndex(0); // serial number of current subpath do
{ bool bOK(true); // catch faulty paths were commands do not correspond to points // get index of next command ENDSUBPATH; if such doesn't exist use index behind last segment
sal_Int32 nNextNcommandIndex = FindNextCommandEndSubpath(nSubpathStartIndex, aSegments);
// Prepare attributes for a:path start element // NOFILL or one of the LIGHTEN commands
std::optional<OString> sFill; if (HasCommandInSubPath(NOFILL, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
sFill = "none"; elseif (HasCommandInSubPath(DARKEN, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
sFill = "darken"; elseif (HasCommandInSubPath(DARKENLESS, nSubpathStartIndex, nNextNcommandIndex - 1,
aSegments))
sFill = "darkenLess"; elseif (HasCommandInSubPath(LIGHTEN, nSubpathStartIndex, nNextNcommandIndex - 1,
aSegments))
sFill = "lighten"; elseif (HasCommandInSubPath(LIGHTENLESS, nSubpathStartIndex, nNextNcommandIndex - 1,
aSegments))
sFill = "lightenLess"; else
{ // shading info might be in object type, e.g. "Octagon Bevel".
sal_Int32 nLuminanceChange(aCustomShape2d.GetLuminanceChange(nSubPathIndex)); if (nLuminanceChange <= -40)
sFill = "darken"; elseif (nLuminanceChange <= -10)
sFill = "darkenLess"; elseif (nLuminanceChange >= 40)
sFill = "lighten"; elseif (nLuminanceChange >= 10)
sFill = "lightenLess";
} // NOSTROKE
std::optional<OString> sStroke; if (HasCommandInSubPath(NOSTROKE, nSubpathStartIndex, nNextNcommandIndex - 1, aSegments))
sStroke = "0";
// Arcs drawn by commands ELLIPTICALQUADRANTX and ELLIPTICALQUADRANTY depend on the position // of the target point in regard to the current point. Therefore we need to track the // current point. A current point is not defined in the beginning. double fCurrentX(0.0); double fCurrentY(0.0); bool bCurrentValid(false); // Actually write the subpath for (sal_Int32 nSegmentIndex = nSubpathStartIndex; nSegmentIndex < nNextNcommandIndex;
++nSegmentIndex)
{ constauto& rSegment(aSegments[nSegmentIndex]); if (rSegment.Command == CLOSESUBPATH)
{
mpFS->singleElementNS(XML_a, XML_close); // command Z has no parameter // ODF 1.4 specifies, that the start of the subpath becomes the current point. // But that is not implemented yet. Currently LO keeps the last current point.
} for (sal_Int32 k = 0; k < rSegment.Count && bOK; ++k)
{
bOK = WriteCustomGeometrySegment(rSegment.Command, k, aPairs, nPairIndex, fCurrentX,
fCurrentY, bCurrentValid, aCustomShape2d,
bReplaceGeoWidth, bReplaceGeoHeight);
}
} // end loop over all commands of subpath // finish this subpath in any case
mpFS->endElementNS(XML_a, XML_path);
if (!bOK) break; // exit loop if not enough values in aPairs
// step forward to next subpath
nSubpathStartIndex = nNextNcommandIndex + 1;
nPathSizeIndex++;
nSubPathIndex++;
} while (nSubpathStartIndex < aSegments.getLength());
mpFS->endElementNS(XML_a, XML_pathLst);
mpFS->endElementNS(XML_a, XML_custGeom); returntrue; // We have written custGeom even if path is poorly structured.
}
mpFS->startElementNS(XML_a, XML_moveTo);
WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
bReplaceGeoHeight);
mpFS->endElementNS(XML_a, XML_moveTo);
rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false,
bReplaceGeoHeight);
rbCurrentValid = true;
rnPairIndex++; break;
} case LINETO:
{ if (rnPairIndex >= rPairs.getLength()) returnfalse; // LINETO without valid current point is a faulty path. LO is tolerant and makes a // moveTo instead. Do the same on export. MS OFFICE requires a current point for lnTo, // otherwise it shows nothing of the shape. if (rbCurrentValid)
{
mpFS->startElementNS(XML_a, XML_lnTo);
WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
bReplaceGeoHeight);
mpFS->endElementNS(XML_a, XML_lnTo);
} else
{
mpFS->startElementNS(XML_a, XML_moveTo);
WriteCustomGeometryPoint(rPairs[rnPairIndex], rCustomShape2d, bReplaceGeoWidth,
bReplaceGeoHeight);
mpFS->endElementNS(XML_a, XML_moveTo);
}
rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex].First, bReplaceGeoWidth, false);
rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex].Second, false,
bReplaceGeoHeight);
rbCurrentValid = true;
rnPairIndex++; break;
} case CURVETO:
{ if (rnPairIndex + 2 >= rPairs.getLength()) returnfalse;
mpFS->startElementNS(XML_a, XML_cubicBezTo); for (sal_uInt8 i = 0; i <= 2; ++i)
{
WriteCustomGeometryPoint(rPairs[rnPairIndex + i], rCustomShape2d, bReplaceGeoWidth,
bReplaceGeoHeight);
}
mpFS->endElementNS(XML_a, XML_cubicBezTo);
rCustomShape2d.GetParameter(rfCurrentX, rPairs[rnPairIndex + 2].First, bReplaceGeoWidth, false);
rCustomShape2d.GetParameter(rfCurrentY, rPairs[rnPairIndex + 2].Second, false,
bReplaceGeoHeight);
rbCurrentValid = true;
rnPairIndex += 3; break;
} case ANGLEELLIPSETO: case ANGLEELLIPSE:
{ if (rnPairIndex + 2 >= rPairs.getLength()) returnfalse;
void DrawingML::WriteEmptyCustomGeometry()
{ // This method is used for export to docx in case WriteCustomGeometry fails.
mpFS->startElementNS(XML_a, XML_custGeom);
mpFS->singleElementNS(XML_a, XML_avLst);
mpFS->singleElementNS(XML_a, XML_gdLst);
mpFS->singleElementNS(XML_a, XML_ahLst);
mpFS->singleElementNS(XML_a, XML_rect, XML_l, "0", XML_t, "0", XML_r, "r", XML_b, "b");
mpFS->singleElementNS(XML_a, XML_pathLst);
mpFS->endElementNS(XML_a, XML_custGeom);
}
// version for SdrPathObj void DrawingML::WritePolyPolygon(const css::uno::Reference<css::drawing::XShape>& rXShape, constbool bClosed)
{
tools::PolyPolygon aPolyPolygon = EscherPropertyContainer::GetPolyPolygon(rXShape); // In case of Writer, the parent element is <wps:spPr>, and there the // <a:custGeom> element is not optional. if (aPolyPolygon.Count() < 1 && GetDocumentType() != DOCUMENT_DOCX) return;
// Only closed SdrPathObj can be filled
std::optional<OString> sFill; if (!bClosed)
sFill = "none"; // for possible values see ST_PathFillMode in OOXML standard
// Put all polygons of rPolyPolygon in the same path element // to subtract the overlapped areas.
mpFS->startElementNS(XML_a, XML_path, XML_fill, sFill, XML_w, OString::number(aSize.Width),
XML_h, OString::number(aSize.Height));
for (sal_uInt16 i = 0; i < aPolyPolygon.Count(); i++)
{ const tools::Polygon& aPoly = aPolyPolygon[i];
if (aPoly.GetSize() > 0)
{
mpFS->startElementNS(XML_a, XML_moveTo);
void DrawingML::WriteShapeStyle( const Reference< XPropertySet >& xPropSet )
{ // check existence of the grab bag if ( !GetProperty( xPropSet, u"InteropGrabBag"_ustr ) ) return;
// extract the relevant properties from the grab bag
Sequence< PropertyValue > aGrabBag;
Sequence< PropertyValue > aFillRefProperties, aLnRefProperties, aEffectRefProperties;
mAny >>= aGrabBag; for (constauto& rProp : aGrabBag)
{ if( rProp.Name == "StyleFillRef" )
rProp.Value >>= aFillRefProperties; elseif( rProp.Name == "StyleLnRef" )
rProp.Value >>= aLnRefProperties; elseif( rProp.Name == "StyleEffectRef" )
rProp.Value >>= aEffectRefProperties;
}
// tdf#132201: the order of effects is important. Effects order (CT_EffectList in ECMA-376): // blur -> fillOverlay -> glow -> innerShdw -> outerShdw -> prstShdw -> reflection -> softEdge
if( !aEffects.hasElements() )
{ bool bHasShadow = false; if( GetProperty( rXPropSet, u"Shadow"_ustr ) )
mAny >>= bHasShadow; bool bHasEffects = bHasShadow; if (!bHasEffects && GetProperty(rXPropSet, u"GlowEffectRadius"_ustr))
{
sal_Int32 rad = 0;
mAny >>= rad;
bHasEffects = rad > 0;
} if (!bHasEffects && GetProperty(rXPropSet, u"SoftEdgeRadius"_ustr))
{
sal_Int32 rad = 0;
mAny >>= rad;
bHasEffects = rad > 0;
}
void DrawingML::Write3DEffects( const Reference< XPropertySet >& xPropSet, bool bIsText )
{ // check existence of the grab bag if( !GetProperty( xPropSet, u"InteropGrabBag"_ustr ) ) return;
// extract the relevant properties from the grab bag
Sequence< PropertyValue > aGrabBag, aEffectProps, aLightRigProps, aShape3DProps;
mAny >>= aGrabBag;
// retrieve the doms from the GrabBag
uno::Sequence<beans::PropertyValue> propList;
xPropSet->getPropertyValue(UNO_NAME_MISC_OBJ_INTEROPGRABBAG) >>= propList; for (constauto& rProp : propList)
{
OUString propName = rProp.Name; if (propName == "OOXData")
rProp.Value >>= dataDom; elseif (propName == "OOXLayout")
rProp.Value >>= layoutDom; elseif (propName == "OOXStyle")
rProp.Value >>= styleDom; elseif (propName == "OOXColor")
rProp.Value >>= colorDom; elseif (propName == "OOXDrawing")
{
rProp.Value >>= diagramDrawing;
diagramDrawing[0]
>>= drawingDom; // if there is OOXDrawing property then set drawingDom here only.
} elseif (propName == "OOXDiagramDataRels")
rProp.Value >>= xDataRelSeq;
}
// check that we have the 4 mandatory XDocuments // if not, there was an error importing and we won't output anything if (!dataDom.is() || !layoutDom.is() || !styleDom.is() || !colorDom.is()) return;
// generate a unique id
rtl::Reference<sax_fastparser::FastAttributeList> pDocPrAttrList
= sax_fastparser::FastSerializerHelper::createAttrList();
pDocPrAttrList->add(XML_id, OString::number(nDiagramId));
OString sName = "Diagram" + OString::number(nDiagramId);
pDocPrAttrList->add(XML_name, sName);
if (GetDocumentType() == DOCUMENT_DOCX)
{
mpFS->singleElementNS(XML_wp, XML_docPr, pDocPrAttrList);
mpFS->singleElementNS(XML_wp, XML_cNvGraphicFramePr);
// store size and position of background shape instead of group shape // as some shapes may be outside
css::uno::Reference<css::drawing::XShapes> xShapes(rXShape, uno::UNO_QUERY); if (xShapes.is() && xShapes->hasElements())
{
css::uno::Reference<css::drawing::XShape> xShapeBg(xShapes->getByIndex(0),
uno::UNO_QUERY);
awt::Point aPos = xShapeBg->getPosition();
awt::Size aSize = xShapeBg->getSize();
WriteTransformation(
xShapeBg, tools::Rectangle(Point(aPos.X, aPos.Y), Size(aSize.Width, aSize.Height)),
XML_p, false, false, 0, false);
}
// the data dom contains a reference to the drawing relation. We need to update it with the new generated // relation value before writing the dom to a file
// There must be one element only so get it
uno::Reference<xml::dom::XNode> node = nodeList->item(0);
// Get the list of attributes of the node
uno::Reference<xml::dom::XNamedNodeMap> nodeMap = node->getAttributes();
// Get the node with the relId attribute and set its new value
uno::Reference<xml::dom::XNode> relIdNode = nodeMap->getNamedItem(u"relId"_ustr);
relIdNode->setNodeValue(drawingRelId);
}
StreamDataSequence dataSeq;
diagramDataRelTuple[1] >>= dataSeq;
uno::Reference<io::XInputStream> dataImagebin( new ::comphelper::SequenceInputStream(dataSeq));
//nDiagramId is used to make the name unique irrespective of the number of smart arts.
OUString sFragment = OUString::Concat("media/") + sGrabBagProperyName
+ OUString::number(nDiagramId) + "_"
+ OUString::number(j) + sExtension;
SdrObject* pObj = SdrObject::getSdrObjectFromXShape(rXShape); if (pObj)
{
Degree100 nRotation = pObj->GetRotateAngle(); if (nRotation)
{
sal_Int16 nHalfWidth = aSize.Width / 2;
sal_Int16 nHalfHeight = aSize.Height / 2; // aTopLeft needs correction for rotated customshapes if (pObj->GetObjIdentifier() == SdrObjKind::CustomShape)
{ // Center of bounding box of the rotated shape constauto aSnapRectCenter(pObj->GetSnapRect().Center());
aTopLeft.X = aSnapRectCenter.X() - nHalfWidth;
aTopLeft.Y = aSnapRectCenter.Y() - nHalfHeight;
}
// MSO changes the anchor positions at these angles and that does an extra 90 degrees // rotation on our shapes, so we output it in such position that MSO // can draw this shape correctly. if ((nRotation >= 4500_deg100 && nRotation < 13500_deg100) || (nRotation >= 22500_deg100 && nRotation < 31500_deg100))
{
aTopLeft.X = aTopLeft.X - nHalfHeight + nHalfWidth;
aTopLeft.Y = aTopLeft.Y - nHalfWidth + nHalfHeight;
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.227Angebot
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-05-06)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.