office-gobmx/oox/source/drawingml/diagram/diagram.cxx

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

445 lines
15 KiB
C++
Raw Normal View History

/* -*- 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 .
*/
2008-01-17 01:06:10 -06:00
#include <oox/drawingml/diagram/diagram.hxx>
#include "diagram.hxx"
#include <com/sun/star/awt/Point.hpp>
#include <com/sun/star/awt/Size.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/drawing/XShape.hpp>
#include <com/sun/star/drawing/XShapes.hpp>
#include <com/sun/star/xml/dom/XDocument.hpp>
#include <com/sun/star/xml/sax/XFastSAXSerializable.hpp>
#include <sal/log.hxx>
2013-09-03 03:13:22 -05:00
#include <editeng/unoprnms.hxx>
#include <drawingml/fillproperties.hxx>
#include <drawingml/customshapeproperties.hxx>
#include <o3tl/unit_conversion.hxx>
#include <oox/token/namespaces.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <svx/svdpage.hxx>
#include <oox/ppt/pptimport.hxx>
#include <comphelper/xmltools.hxx>
2008-01-17 01:06:10 -06:00
#include "diagramlayoutatoms.hxx"
#include "layoutatomvisitors.hxx"
#include "diagramfragmenthandler.hxx"
using namespace ::com::sun::star;
2008-01-17 01:06:10 -06:00
namespace oox::drawingml {
2008-01-17 01:06:10 -06:00
static void sortChildrenByZOrder(const ShapePtr& pShape)
{
std::vector<ShapePtr>& rChildren = pShape->getChildren();
// Offset the children from their default z-order stacking, if necessary.
for (size_t i = 0; i < rChildren.size(); ++i)
rChildren[i]->setZOrder(i);
for (size_t i = 0; i < rChildren.size(); ++i)
{
const ShapePtr& pChild = rChildren[i];
sal_Int32 nZOrderOff = pChild->getZOrderOff();
if (nZOrderOff <= 0)
continue;
// Increase my ZOrder by nZOrderOff.
pChild->setZOrder(pChild->getZOrder() + nZOrderOff);
pChild->setZOrderOff(0);
for (sal_Int32 j = 0; j < nZOrderOff; ++j)
{
size_t nIndex = i + j + 1;
if (nIndex >= rChildren.size())
break;
// Decrease the ZOrder of the next nZOrderOff elements by one.
const ShapePtr& pNext = rChildren[nIndex];
pNext->setZOrder(pNext->getZOrder() - 1);
}
}
// Now that the ZOrders are adjusted, sort the children.
std::sort(rChildren.begin(), rChildren.end(),
[](const ShapePtr& a, const ShapePtr& b) { return a->getZOrder() < b->getZOrder(); });
// Apply also for children.
for (const auto& rChild : rChildren)
sortChildrenByZOrder(rChild);
}
/// Removes empty group shapes, now that their spacing influenced the layout.
static void removeUnneededGroupShapes(const ShapePtr& pShape)
{
std::vector<ShapePtr>& rChildren = pShape->getChildren();
rChildren.erase(std::remove_if(rChildren.begin(), rChildren.end(),
[](const ShapePtr& aChild) {
return aChild->getServiceName()
== "com.sun.star.drawing.GroupShape"
&& aChild->getChildren().empty();
}),
rChildren.end());
for (const auto& pChild : rChildren)
{
removeUnneededGroupShapes(pChild);
}
}
void Diagram::addTo( const ShapePtr & pParentShape )
2008-01-17 01:06:10 -06:00
{
if (pParentShape->getSize().Width == 0 || pParentShape->getSize().Height == 0)
SAL_WARN("oox.drawingml", "Diagram cannot be correctly laid out. Size: "
<< pParentShape->getSize().Width << "x" << pParentShape->getSize().Height);
pParentShape->setChildSize(pParentShape->getSize());
const svx::diagram::Point* pRootPoint = mpData->getRootPoint();
if (mpLayout->getNode() && pRootPoint)
{
// create Shape hierarchy
ShapeCreationVisitor aCreationVisitor(*this, pRootPoint, pParentShape);
mpLayout->getNode()->setExistingShape(pParentShape);
mpLayout->getNode()->accept(aCreationVisitor);
// layout shapes - now all shapes are created
ShapeLayoutingVisitor aLayoutingVisitor(*this, pRootPoint);
mpLayout->getNode()->accept(aLayoutingVisitor);
sortChildrenByZOrder(pParentShape);
removeUnneededGroupShapes(pParentShape);
}
ShapePtr pBackground = std::make_shared<Shape>("com.sun.star.drawing.CustomShape");
pBackground->setSubType(XML_rect);
pBackground->getCustomShapeProperties()->setShapePresetType(XML_rect);
pBackground->setSize(pParentShape->getSize());
pBackground->getFillProperties() = *mpData->getBackgroundShapeFillProperties();
pBackground->setLocked(true);
// create and set ModelID for BackgroundShape to allow later association
getData()->setBackgroundShapeModelID(OStringToOUString(comphelper::xml::generateGUIDString(), RTL_TEXTENCODING_UTF8));
pBackground->setDiagramDataModelID(getData()->getBackgroundShapeModelID());
auto& aChildren = pParentShape->getChildren();
aChildren.insert(aChildren.begin(), pBackground);
2013-09-03 03:13:22 -05:00
}
Diagram::Diagram()
: maDiagramFontHeights()
{
}
2013-09-03 03:13:22 -05:00
uno::Sequence<beans::PropertyValue> Diagram::getDomsAsPropertyValues() const
{
sal_Int32 length = maMainDomMap.size();
if (maDataRelsMap.hasElements())
++length;
2013-09-03 03:13:22 -05:00
uno::Sequence<beans::PropertyValue> aValue(length);
beans::PropertyValue* pValue = aValue.getArray();
for (auto const& mainDom : maMainDomMap)
2013-09-03 03:13:22 -05:00
{
pValue->Name = mainDom.first;
pValue->Value <<= mainDom.second;
2013-09-03 03:13:22 -05:00
++pValue;
}
if (maDataRelsMap.hasElements())
{
pValue->Name = "OOXDiagramDataRels";
pValue->Value <<= maDataRelsMap;
++pValue;
}
2013-09-03 03:13:22 -05:00
return aValue;
2008-01-17 01:06:10 -06:00
}
using ShapePairs
= std::map<std::shared_ptr<drawingml::Shape>, css::uno::Reference<css::drawing::XShape>>;
void Diagram::syncDiagramFontHeights()
{
// Each name represents a group of shapes, for which the font height should have the same
// scaling.
for (const auto& rNameAndPairs : maDiagramFontHeights)
{
// Find out the minimum scale within this group.
const ShapePairs& rShapePairs = rNameAndPairs.second;
sal_Int16 nMinScale = 100;
for (const auto& rShapePair : rShapePairs)
{
uno::Reference<beans::XPropertySet> xPropertySet(rShapePair.second, uno::UNO_QUERY);
if (xPropertySet.is())
{
sal_Int16 nTextFitToSizeScale = 0;
xPropertySet->getPropertyValue("TextFitToSizeScale") >>= nTextFitToSizeScale;
if (nTextFitToSizeScale > 0 && nTextFitToSizeScale < nMinScale)
{
nMinScale = nTextFitToSizeScale;
}
}
}
// Set that minimum scale for all members of the group.
if (nMinScale < 100)
{
for (const auto& rShapePair : rShapePairs)
{
uno::Reference<beans::XPropertySet> xPropertySet(rShapePair.second, uno::UNO_QUERY);
if (xPropertySet.is())
{
xPropertySet->setPropertyValue("TextFitToSizeScale", uno::Any(nMinScale));
}
}
}
}
// no longer needed after processing
maDiagramFontHeights.clear();
}
static uno::Reference<xml::dom::XDocument> loadFragment(
core::XmlFilterBase& rFilter,
2013-09-03 03:13:22 -05:00
const OUString& rFragmentPath )
{
// load diagramming fragments into DOM representation, that later
// gets serialized back to SAX events and parsed
2013-09-03 03:13:22 -05:00
return rFilter.importFragment( rFragmentPath );
}
static uno::Reference<xml::dom::XDocument> loadFragment(
2013-09-03 03:13:22 -05:00
core::XmlFilterBase& rFilter,
const rtl::Reference< core::FragmentHandler >& rxHandler )
{
return loadFragment( rFilter, rxHandler->getFragmentPath() );
}
static void importFragment( core::XmlFilterBase& rFilter,
const uno::Reference<xml::dom::XDocument>& rXDom,
const OUString& rDocName,
2013-09-03 03:13:22 -05:00
const DiagramPtr& pDiagram,
const rtl::Reference< core::FragmentHandler >& rxHandler )
{
2013-09-03 03:13:22 -05:00
DiagramDomMap& rMainDomMap = pDiagram->getDomMap();
rMainDomMap[rDocName] = rXDom;
2013-09-03 03:13:22 -05:00
uno::Reference<xml::sax::XFastSAXSerializable> xSerializer(
rXDom, uno::UNO_QUERY_THROW);
// now serialize DOM tree into internal data structures
rFilter.importFragment( rxHandler, xSerializer );
}
namespace
{
/**
* A fragment handler that just counts the number of <dsp:sp> elements in a
* fragment.
*/
class DiagramShapeCounter : public oox::core::FragmentHandler2
{
public:
DiagramShapeCounter(oox::core::XmlFilterBase& rFilter, const OUString& rFragmentPath,
sal_Int32& nCounter);
oox::core::ContextHandlerRef onCreateContext(sal_Int32 nElement,
const AttributeList& rAttribs) override;
private:
sal_Int32& m_nCounter;
};
DiagramShapeCounter::DiagramShapeCounter(oox::core::XmlFilterBase& rFilter,
const OUString& rFragmentPath, sal_Int32& nCounter)
: FragmentHandler2(rFilter, rFragmentPath)
, m_nCounter(nCounter)
{
}
oox::core::ContextHandlerRef DiagramShapeCounter::onCreateContext(sal_Int32 nElement,
const AttributeList& /*rAttribs*/)
{
switch (nElement)
{
case DSP_TOKEN(drawing):
case DSP_TOKEN(spTree):
return this;
case DSP_TOKEN(sp):
++m_nCounter;
break;
default:
break;
}
return nullptr;
}
}
void loadDiagram( ShapePtr const & pShape,
core::XmlFilterBase& rFilter,
const OUString& rDataModelPath,
const OUString& rLayoutPath,
const OUString& rQStylePath,
const OUString& rColorStylePath,
const oox::core::Relations& rRelations )
{
DiagramPtr pDiagram = std::make_shared<Diagram>();
OoxDiagramDataPtr pData = std::make_shared<DiagramData>();
pDiagram->setData( pData );
DiagramLayoutPtr pLayout = std::make_shared<DiagramLayout>(*pDiagram);
pDiagram->setLayout( pLayout );
// set DiagramFontHeights at filter
rFilter.setDiagramFontHeights(&pDiagram->getDiagramFontHeights());
// data
if( !rDataModelPath.isEmpty() )
{
2013-09-03 03:13:22 -05:00
rtl::Reference< core::FragmentHandler > xRefDataModel(
new DiagramDataFragmentHandler( rFilter, rDataModelPath, pData ));
importFragment(rFilter,
2013-09-03 03:13:22 -05:00
loadFragment(rFilter,xRefDataModel),
"OOXData",
pDiagram,
xRefDataModel);
pDiagram->getDataRelsMap() = pShape->resolveRelationshipsOfTypeFromOfficeDoc( rFilter,
xRefDataModel->getFragmentPath(), u"image" );
// Pass the info to pShape
for (auto const& extDrawing : pData->getExtDrawings())
{
OUString aFragmentPath = rRelations.getFragmentPathFromRelId(extDrawing);
// Ignore RelIds which don't resolve to a fragment path.
if (aFragmentPath.isEmpty())
continue;
sal_Int32 nCounter = 0;
rtl::Reference<core::FragmentHandler> xCounter(
new DiagramShapeCounter(rFilter, aFragmentPath, nCounter));
rFilter.importFragment(xCounter);
// Ignore ext drawings which don't actually have any shapes.
if (nCounter == 0)
continue;
pShape->addExtDrawingRelId(extDrawing);
}
}
// extLst is present, lets bet on that and ignore the rest of the data from here
if( pShape->getExtDrawings().empty() )
{
// layout
if( !rLayoutPath.isEmpty() )
{
2013-09-03 03:13:22 -05:00
rtl::Reference< core::FragmentHandler > xRefLayout(
new DiagramLayoutFragmentHandler( rFilter, rLayoutPath, pLayout ));
2013-09-03 03:13:22 -05:00
importFragment(rFilter,
2013-09-03 03:13:22 -05:00
loadFragment(rFilter,xRefLayout),
"OOXLayout",
pDiagram,
xRefLayout);
}
// style
if( !rQStylePath.isEmpty() )
{
2013-09-03 03:13:22 -05:00
rtl::Reference< core::FragmentHandler > xRefQStyle(
new DiagramQStylesFragmentHandler( rFilter, rQStylePath, pDiagram->getStyles() ));
2013-09-03 03:13:22 -05:00
importFragment(rFilter,
2013-09-03 03:13:22 -05:00
loadFragment(rFilter,xRefQStyle),
"OOXStyle",
pDiagram,
xRefQStyle);
}
}
else
{
2013-09-03 03:13:22 -05:00
// We still want to add the XDocuments to the DiagramDomMap
DiagramDomMap& rMainDomMap = pDiagram->getDomMap();
rMainDomMap[OUString("OOXLayout")] = loadFragment(rFilter,rLayoutPath);
rMainDomMap[OUString("OOXStyle")] = loadFragment(rFilter,rQStylePath);
}
// colors
if( !rColorStylePath.isEmpty() )
{
rtl::Reference< core::FragmentHandler > xRefColorStyle(
new ColorFragmentHandler( rFilter, rColorStylePath, pDiagram->getColors() ));
importFragment(rFilter,
loadFragment(rFilter,xRefColorStyle),
"OOXColor",
pDiagram,
xRefColorStyle);
}
if( !pData->getExtDrawings().empty() )
{
const DiagramColorMap::const_iterator aColor = pDiagram->getColors().find("node0");
if( aColor != pDiagram->getColors().end() && !aColor->second.maTextFillColors.empty())
{
// TODO(F1): well, actually, there might be *several* color
// definitions in it, after all it's called list.
pShape->setFontRefColorForNodes(DiagramColor::getColorByIndex(aColor->second.maTextFillColors, -1));
}
}
// collect data, init maps
Advanced Diagram support: Preparations to make oox-Shape temporary When we come from the LO-side of things and need to (re)create the Diagram, no oox-Shapes can exist by definition. That forces to see oox-Shapes as temporary helper objects. Since some ModelData is at the oox-Shape after importing a Diagram that data needs to be identified and added to the Diagram ModelData. On the other path, when (re)creating a Diagram from the ModelData, oox-Shapes are needed to use the currently existing mechanism in the import-Filter to create XShapes froom the oox-Shapes. In that path, the Diagram ModelData needs to be applied to the newly created oox-Shape. For now I have identified e.g. the Text as such data value. In the future there may be more ModelData of that type identified and may need to be added. To support that, I have added methods to do that. It may even be feasible in debug builds to add code to compare the imported oox-Shape with a temporarily (re)created one to allow easier identification of yet missing secured/copied ModelData. To make that mechanism obligatory I have added code in DiagramData::build() that deletes all oox-Shapes which were created at Diagam import. This is necessary. That way, the (re)creation is used for Diagram re-creation and can be already used and tested. Deleting all oox-Shapes in build() immediately triggered an error in a UnitTest: testPictureStrip. This shows that here some ModelData in form of BitmapData is not secured - proof of concept and what I described above. To get forward, I adapted to not delete oox-Shapes when Diagram import is executed. Thus, the UnitTests work, and the ReCreate can be used to trigger that behaviour. As an end result this has to be re-activated. It allows now to search and add the missing model data from the test file used in that UnitTest. Change-Id: I6bc39a0898c7be1b4c656875f6d7508c36c9bea8 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/132127 Tested-by: Jenkins Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com>
2022-03-25 10:34:30 -05:00
// for Diagram import, do - for now - NOT clear all oox::drawingml::Shape
pData->buildDiagramDataModel(false);
// diagram loaded. now lump together & attach to shape
pDiagram->addTo(pShape);
pShape->setDiagramDoms(pDiagram->getDomsAsPropertyValues());
// Get the oox::Theme definition and - if available - move/secure the
// original ImportData directly to the Diagram ModelData
std::shared_ptr<::oox::drawingml::Theme> aTheme(rFilter.getCurrentThemePtr());
if(aTheme)
pData->setThemeDocument(aTheme->getFragment()); //getTempFile());
// Prepare support for the advanced DiagramHelper using Diagram & Theme data
pShape->prepareDiagramHelper(pDiagram, rFilter.getCurrentThemePtr());
}
const oox::drawingml::Color&
DiagramColor::getColorByIndex(const std::vector<oox::drawingml::Color>& rColors, sal_Int32 nIndex)
{
assert(!rColors.empty());
if (nIndex == -1)
{
return rColors[rColors.size() - 1];
}
return rColors[nIndex % rColors.size()];
}
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */