426 lines
18 KiB
C++
426 lines
18 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/.
|
|
*
|
|
* 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 .
|
|
*/
|
|
|
|
#include <cmath>
|
|
|
|
#include <drawingml/transform2dcontext.hxx>
|
|
|
|
#include <basegfx/matrix/b2dhommatrixtools.hxx>
|
|
#include <basegfx/numeric/ftools.hxx>
|
|
#include <basegfx/point/b2dpoint.hxx>
|
|
#include <drawingml/customshapeproperties.hxx>
|
|
#include <drawingml/textbody.hxx>
|
|
#include <oox/drawingml/shape.hxx>
|
|
#include <oox/helper/attributelist.hxx>
|
|
#include <oox/token/namespaces.hxx>
|
|
|
|
#include <com/sun/star/awt/Rectangle.hpp>
|
|
|
|
using namespace ::com::sun::star;
|
|
using ::oox::core::ContextHandlerRef;
|
|
|
|
namespace oox::drawingml {
|
|
|
|
/** context to import a CT_Transform2D */
|
|
Transform2DContext::Transform2DContext( ContextHandler2Helper const & rParent, const AttributeList& rAttribs, Shape& rShape, bool btxXfrm )
|
|
: ContextHandler2( rParent )
|
|
, mrShape( rShape )
|
|
, mbtxXfrm ( btxXfrm )
|
|
{
|
|
if( !btxXfrm )
|
|
{
|
|
mrShape.setRotation( rAttribs.getInteger( XML_rot, 0 ) ); // 60000ths of a degree Positive angles are clockwise; negative angles are counter-clockwise
|
|
mrShape.setFlip( rAttribs.getBool( XML_flipH, false ), rAttribs.getBool( XML_flipV, false ) );
|
|
}
|
|
else
|
|
{
|
|
if (rAttribs.hasAttribute(XML_rot) && mrShape.getTextBody())
|
|
{
|
|
mno_txXfrmRot = rAttribs.getInteger(XML_rot, 0);
|
|
sal_Int32 nTextAreaRot = mrShape.getTextBody()->getTextProperties().moTextAreaRotation.value_or(0);
|
|
mrShape.getTextBody()->getTextProperties().moTextAreaRotation = mno_txXfrmRot.value() + nTextAreaRot;
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
bool ConstructPresetTextRectangle(Shape& rShape, awt::Rectangle& rRect)
|
|
{
|
|
// When we are here, we have neither xShape nor a SdrObject. So need to manually calc the text
|
|
// area rectangle defined in the preset in OOXML standard, but only for those types of shapes
|
|
// where we know, that MS Office SmartArt presets do not use the default text area rectangle.
|
|
const sal_Int32 nType = rShape.getCustomShapeProperties()->getShapePresetType();
|
|
switch (nType)
|
|
{
|
|
case XML_ellipse:
|
|
// The preset text rectangle touches the perimeter of the ellipse at 45deg.
|
|
rRect.X = rShape.getPosition().X + rShape.getSize().Width * ((1.0 - M_SQRT1_2) / 2.0);
|
|
rRect.Y = rShape.getPosition().Y + rShape.getSize().Height * ((1.0 - M_SQRT1_2) / 2.0);
|
|
rRect.Width = rShape.getSize().Width * M_SQRT1_2;
|
|
rRect.Height = rShape.getSize().Height * M_SQRT1_2;
|
|
return true;
|
|
case XML_roundRect:
|
|
case XML_round2SameRect:
|
|
{
|
|
// Second handle of round2SameRect used in preset diagrams has value 0.
|
|
const auto& aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
|
|
double fAdj = aAdjGdList.empty() ? 16667 : aAdjGdList[0].maFormula.toDouble();
|
|
sal_Int32 nWidth = rShape.getSize().Width;
|
|
sal_Int32 nHeight = rShape.getSize().Height;
|
|
if (nWidth == 0 || nHeight == 0)
|
|
return false;
|
|
double fMaxAdj = 50000.0 * nWidth / std::min(nWidth, nHeight);
|
|
fAdj = std::clamp<double>(fAdj, 0, fMaxAdj);
|
|
sal_Int32 nTextLeft = std::min(nWidth, nHeight) * fAdj / 100000.0 * 0.29289;
|
|
sal_Int32 nTextTop = nTextLeft;
|
|
rRect.X = rShape.getPosition().X + nTextLeft;
|
|
rRect.Y = rShape.getPosition().Y + nTextTop;
|
|
rRect.Width = nWidth - 2 * nTextLeft;
|
|
rRect.Height = nHeight - (nType == XML_roundRect ? 2 : 1) * nTextTop;
|
|
return true;
|
|
}
|
|
case XML_trapezoid:
|
|
{
|
|
const auto& aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
|
|
double fAdj = aAdjGdList.empty() ? 25000 : aAdjGdList[0].maFormula.toDouble();
|
|
sal_Int32 nWidth = rShape.getSize().Width;
|
|
sal_Int32 nHeight = rShape.getSize().Height;
|
|
if (nWidth == 0 || nHeight == 0)
|
|
return false;
|
|
double fMaxAdj = 50000.0 * nWidth / std::min(nWidth, nHeight);
|
|
fAdj = std::clamp<double>(fAdj, 0, fMaxAdj);
|
|
sal_Int32 nTextLeft = nWidth / 3.0 * fAdj / fMaxAdj;
|
|
sal_Int32 nTextTop = nHeight / 3.0 * fAdj / fMaxAdj;
|
|
rRect.X = rShape.getPosition().X + nTextLeft;
|
|
rRect.Y = rShape.getPosition().Y + nTextTop;
|
|
rRect.Width = nWidth - 2 * nTextLeft;
|
|
rRect.Height = nHeight - 2 * nTextTop;
|
|
return true;
|
|
}
|
|
case XML_flowChartManualOperation:
|
|
{
|
|
sal_Int32 nWidth = rShape.getSize().Width;
|
|
sal_Int32 nTextLeft = nWidth / 5;
|
|
rRect.X = rShape.getPosition().X + nTextLeft;
|
|
rRect.Y = rShape.getPosition().Y;
|
|
rRect.Width = nWidth - 2 * nTextLeft;
|
|
rRect.Height = rShape.getSize().Height;
|
|
return true;
|
|
}
|
|
case XML_pie:
|
|
case XML_rect:
|
|
case XML_wedgeRectCallout:
|
|
{
|
|
// When tdf#149918 is fixed, pie will need its own case
|
|
rRect.X = rShape.getPosition().X;
|
|
rRect.Y = rShape.getPosition().Y;
|
|
rRect.Width = rShape.getSize().Width;
|
|
rRect.Height = rShape.getSize().Height;
|
|
return true;
|
|
}
|
|
case XML_upArrowCallout:
|
|
case XML_downArrowCallout:
|
|
{
|
|
// The identifiers here reflect the guides name value in presetShapeDefinitions.xml
|
|
sal_Int32 nWidth = rShape.getSize().Width;
|
|
sal_Int32 nHeight = rShape.getSize().Height;
|
|
if (nWidth == 0 || nHeight == 0)
|
|
return false;
|
|
// double adj1 = 25000.0;
|
|
// double adj2 = 25000.0;
|
|
double adj3 = 25000.0; // height of arrow head
|
|
double adj4 = 64977.0; // height of arrow shaft
|
|
const auto& aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
|
|
if (aAdjGdList.size() == 4)
|
|
{
|
|
// adj1 = aAdjGdList[0].maFormula.toDouble();
|
|
// adj2 = aAdjGdList[1].maFormula.toDouble();
|
|
adj3 = aAdjGdList[2].maFormula.toDouble();
|
|
adj4 = aAdjGdList[3].maFormula.toDouble();
|
|
}
|
|
|
|
double maxAdj3 = 100000.0 * nHeight / std::min(nWidth, nHeight);
|
|
adj3 = std::clamp<double>(adj3, 0, maxAdj3);
|
|
double q2 = adj3 * std::min(nWidth, nHeight) / nHeight;
|
|
double maxAdj4 = 100000.0 - q2;
|
|
adj4 = std::clamp<double>(adj4, 0, maxAdj4);
|
|
|
|
rRect.X = rShape.getPosition().X;
|
|
rRect.Y = rShape.getPosition().Y;
|
|
rRect.Width = rShape.getSize().Width;
|
|
rRect.Height = nHeight * adj4 / 100000.0;
|
|
return true;
|
|
}
|
|
case XML_gear6:
|
|
{
|
|
// The identifiers here reflect the guides name value in presetShapeDefinitions.xml
|
|
double w = rShape.getSize().Width;
|
|
double h = rShape.getSize().Height;
|
|
if (w <= 0 || h <= 0)
|
|
return false;
|
|
double a1(15000.0);
|
|
double a2(3526.0);
|
|
const auto& aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
|
|
if (aAdjGdList.size() == 2)
|
|
{
|
|
a1 = aAdjGdList[0].maFormula.toDouble();
|
|
a2 = aAdjGdList[1].maFormula.toDouble();
|
|
a1 = std::clamp<double>(a1, 0, 20000);
|
|
a2 = std::clamp<double>(a2, 0, 5358);
|
|
}
|
|
double th = std::min(w, h) * a1 / 100000.0;
|
|
double l2 = std::min(w, h) * a2 / 100000.0 / 2.0;
|
|
double l3 = th / 2.0 + l2;
|
|
|
|
double rh = h / 2.0 - th;
|
|
double rw = w / 2.0 - th;
|
|
|
|
double maxr = std::min(rw, rh);
|
|
double ha = atan2(l3, maxr);
|
|
|
|
double aA1 = basegfx::deg2rad(330) - ha;
|
|
double ta11 = rw * cos(aA1);
|
|
double ta12 = rh * sin(aA1);
|
|
double bA1 = atan2(ta12, ta11);
|
|
double cta1 = rh * cos(bA1);
|
|
double sta1 = rw * sin(bA1);
|
|
double ma1 = std::hypot(cta1, sta1);
|
|
double na1 = rw * rh / ma1;
|
|
double dxa1 = na1 * cos(bA1);
|
|
double dya1 = na1 * sin(bA1);
|
|
|
|
double xA1 = w / 2.0 + dxa1; // r
|
|
double yA1 = h / 2.0 + dya1; // t
|
|
double yD2 = h - yA1; // b
|
|
double xD5 = w - xA1; // l
|
|
|
|
rRect.X = rShape.getPosition().X + xD5;
|
|
rRect.Y = rShape.getPosition().Y + yA1;
|
|
rRect.Width = xA1 - xD5;
|
|
rRect.Height = yD2 - yA1;
|
|
return true;
|
|
}
|
|
case XML_hexagon:
|
|
{
|
|
const auto& aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
|
|
double fAdj = aAdjGdList.empty() ? 25000 : aAdjGdList[0].maFormula.toDouble();
|
|
sal_Int32 nWidth = rShape.getSize().Width;
|
|
sal_Int32 nHeight = rShape.getSize().Height;
|
|
if (nWidth == 0 || nHeight == 0)
|
|
return false;
|
|
double fMaxAdj = 50000.0 * nWidth / std::min(nWidth, nHeight);
|
|
fAdj = std::clamp<double>(fAdj, 0, fMaxAdj);
|
|
double fFactor = fAdj / fMaxAdj / 6.0 + 1.0 / 12.0;
|
|
sal_Int32 nTextLeft = nWidth * fFactor;
|
|
sal_Int32 nTextTop = nHeight * fFactor;
|
|
rRect.X = rShape.getPosition().X + nTextLeft;
|
|
rRect.Y = rShape.getPosition().Y + nTextTop;
|
|
rRect.Width = nWidth - 2 * nTextLeft;
|
|
rRect.Height = nHeight - 2 * nTextTop;
|
|
return true;
|
|
}
|
|
case XML_round1Rect:
|
|
{
|
|
sal_Int32 nWidth = rShape.getSize().Width;
|
|
sal_Int32 nHeight = rShape.getSize().Height;
|
|
if (nWidth == 0 || nHeight == 0)
|
|
return false;
|
|
const auto& aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
|
|
double fAdj = aAdjGdList.empty() ? 16667.0 : aAdjGdList[0].maFormula.toDouble();
|
|
fAdj = std::clamp<double>(fAdj, 0.0, 50000.0);
|
|
double fDx = std::min(nWidth, nHeight) * fAdj / 100000.0 * 0.29289;
|
|
rRect.X = rShape.getPosition().X;
|
|
rRect.Y = rShape.getPosition().Y;
|
|
rRect.Width = nWidth - fDx;
|
|
rRect.Height = nHeight;
|
|
return true;
|
|
}
|
|
case XML_rightArrow:
|
|
{
|
|
// The identifiers here reflect the guides name value in presetShapeDefinitions.xml
|
|
sal_Int32 nWidth = rShape.getSize().Width;
|
|
sal_Int32 nHeight = rShape.getSize().Height;
|
|
if (nWidth == 0 || nHeight == 0)
|
|
return false;
|
|
double a1(50000.0);
|
|
double a2(50000.0);
|
|
const auto& aAdjGdList = rShape.getCustomShapeProperties()->getAdjustmentGuideList();
|
|
if (aAdjGdList.size() == 2)
|
|
{
|
|
a1 = aAdjGdList[0].maFormula.toDouble();
|
|
a2 = aAdjGdList[1].maFormula.toDouble();
|
|
a1 = std::clamp<double>(a1, 0, 100000);
|
|
}
|
|
double maxAdj2 = 100000.0 * nWidth / std::min(nWidth, nHeight);
|
|
a2 = std::clamp<double>(a2, 0, maxAdj2);
|
|
double dx1 = std::min(nWidth, nHeight) * a2 / 100000.0;
|
|
double x1 = nWidth - dx1;
|
|
double dy1 = nHeight * a1 / 200000.0;
|
|
double y1 = nHeight / 2.0 - dy1; // top
|
|
double y2 = nHeight / 2.0 + dy1; // bottom
|
|
double dx2 = y1 * dx1 / (nHeight / 2.0);
|
|
double x2 = x1 + dx2; // right
|
|
rRect.X = rShape.getPosition().X; // left = 0
|
|
rRect.Y = rShape.getPosition().Y + y1;
|
|
rRect.Width = x2;
|
|
rRect.Height = y2 - y1;
|
|
return true;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
basegfx::B2DPoint getCenter(const awt::Rectangle& rRect)
|
|
{
|
|
return basegfx::B2DPoint(rRect.X + rRect.Width / 2.0, rRect.Y + rRect.Height / 2.0);
|
|
}
|
|
} // end namespace
|
|
|
|
ContextHandlerRef Transform2DContext::onCreateContext( sal_Int32 aElementToken, const AttributeList& rAttribs )
|
|
{
|
|
if (mbtxXfrm)
|
|
{
|
|
// The child elements <a:off> and <a:ext> of a <dsp:txXfrm> element describe the position and
|
|
// size of the text area rectangle. We cannot change the text area rectangle directly, because
|
|
// currently we depend on the geometry definition of the preset. As workaround we change the
|
|
// indents to move and scale the text block. The needed shifts are calculated here as moTextOff
|
|
// and used in TextBodyProperties::pushTextDistances().
|
|
awt::Rectangle aPresetTextRectangle;
|
|
if (!ConstructPresetTextRectangle(mrShape, aPresetTextRectangle))
|
|
return nullptr; // faulty shape or text area calculation not implemented
|
|
|
|
switch (aElementToken)
|
|
{
|
|
case A_TOKEN(off):
|
|
{
|
|
// need <a:ext> too, so only save values here.
|
|
const OUString sXValue = rAttribs.getStringDefaulted(XML_x);
|
|
const OUString sYValue = rAttribs.getStringDefaulted(XML_y);
|
|
if (!sXValue.isEmpty() && !sYValue.isEmpty())
|
|
{
|
|
mno_txXfrmOffX = sXValue.toInt32();
|
|
mno_txXfrmOffY = sYValue.toInt32();
|
|
}
|
|
}
|
|
break;
|
|
case A_TOKEN(ext):
|
|
{
|
|
// Build text frame from txXfrm element
|
|
awt::Rectangle aUnrotatedTxXfrm = aPresetTextRectangle; // dummy initialize
|
|
const OUString sCXValue = rAttribs.getStringDefaulted(XML_cx);
|
|
const OUString sCYValue = rAttribs.getStringDefaulted(XML_cy);
|
|
if (!sCXValue.isEmpty() && !sCYValue.isEmpty())
|
|
{
|
|
aUnrotatedTxXfrm.Width = sCXValue.toInt32();
|
|
aUnrotatedTxXfrm.Height = sCYValue.toInt32();
|
|
}
|
|
if (mno_txXfrmOffX.has_value() && mno_txXfrmOffY.has_value())
|
|
{
|
|
aUnrotatedTxXfrm.X = mno_txXfrmOffX.value();
|
|
aUnrotatedTxXfrm.Y = mno_txXfrmOffY.value();
|
|
}
|
|
|
|
// Has the txXfrm an own rotation beyond compensation of the shape rotation?
|
|
// Happens e.g. in diagram type 'Detailed Process'.
|
|
sal_Int32 nAngleDiff
|
|
= (mrShape.getRotation() + mno_txXfrmRot.value_or(0)) % 21600000;
|
|
if (nAngleDiff != 0)
|
|
{
|
|
// Rectangle aUnrotatedTxXfrm rotates around its center not around text area
|
|
// center from preset. We shift aUnrotatedTxXfrm so that it is at the original
|
|
// position after rotation of text area rectangle from preset.
|
|
basegfx::B2DPoint aXfrmCenter(getCenter(aUnrotatedTxXfrm));
|
|
basegfx::B2DPoint aPresetCenter(getCenter(aPresetTextRectangle));
|
|
|
|
if (!aXfrmCenter.equal(aPresetCenter))
|
|
{
|
|
double fAngleRad = basegfx::deg2rad(nAngleDiff / 60000.0);
|
|
basegfx::B2DHomMatrix aRotMatrix(
|
|
basegfx::utils::createRotateAroundPoint(aPresetCenter, -fAngleRad));
|
|
basegfx::B2DPoint aNewCenter(aRotMatrix * aXfrmCenter);
|
|
aUnrotatedTxXfrm.X += aNewCenter.getX() - aXfrmCenter.getX();
|
|
aUnrotatedTxXfrm.Y += aNewCenter.getY() - aXfrmCenter.getY();
|
|
}
|
|
}
|
|
|
|
if(mrShape.getTextBody())
|
|
{
|
|
// Calculate indent offsets
|
|
sal_Int32 nOffsetLeft = aUnrotatedTxXfrm.X - aPresetTextRectangle.X;
|
|
sal_Int32 nOffsetTop = aUnrotatedTxXfrm.Y - aPresetTextRectangle.Y;
|
|
sal_Int32 nOffsetRight
|
|
= aPresetTextRectangle.Width - aUnrotatedTxXfrm.Width - nOffsetLeft;
|
|
sal_Int32 nOffsetBottom
|
|
= aPresetTextRectangle.Height - aUnrotatedTxXfrm.Height - nOffsetTop;
|
|
|
|
if (nOffsetLeft)
|
|
mrShape.getTextBody()->getTextProperties().moTextOffLeft
|
|
= GetCoordinate(nOffsetLeft);
|
|
if (nOffsetTop)
|
|
mrShape.getTextBody()->getTextProperties().moTextOffUpper
|
|
= GetCoordinate(nOffsetTop);
|
|
if (nOffsetRight)
|
|
mrShape.getTextBody()->getTextProperties().moTextOffRight
|
|
= GetCoordinate(nOffsetRight);
|
|
if (nOffsetBottom)
|
|
mrShape.getTextBody()->getTextProperties().moTextOffLower
|
|
= GetCoordinate(nOffsetBottom);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return nullptr;
|
|
} // end of case mbtxXfrm
|
|
|
|
switch( aElementToken )
|
|
{
|
|
case A_TOKEN( off ): // horz/vert translation
|
|
mrShape.setPosition( awt::Point( rAttribs.getInteger( XML_x, 0 ), rAttribs.getInteger( XML_y, 0 ) ) );
|
|
break;
|
|
case A_TOKEN( ext ): // horz/vert size
|
|
mrShape.setSize( awt::Size( rAttribs.getInteger( XML_cx, 0 ), rAttribs.getInteger( XML_cy, 0 ) ) );
|
|
break;
|
|
case A_TOKEN( chOff ): // horz/vert translation of children
|
|
mrShape.setChildPosition( awt::Point( rAttribs.getInteger( XML_x, 0 ), rAttribs.getInteger( XML_y, 0 ) ) );
|
|
break;
|
|
case A_TOKEN( chExt ): // horz/vert size of children
|
|
{
|
|
sal_Int32 nChExtCx = rAttribs.getInteger(XML_cx, 0);
|
|
|
|
if(nChExtCx == 0)
|
|
nChExtCx = mrShape.getSize().Width;
|
|
|
|
sal_Int32 nChExtCy = rAttribs.getInteger(XML_cy, 0);
|
|
|
|
if(nChExtCy == 0)
|
|
nChExtCy = mrShape.getSize().Height;
|
|
|
|
mrShape.setChildSize(awt::Size(nChExtCx, nChExtCy));
|
|
}
|
|
break;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
} // namespace oox::drawingml
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|