LibreOffice Module oox (master)  1
diagramlayoutatoms.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  * Licensed to the Apache Software Foundation (ASF) under one or more
12  * contributor license agreements. See the NOTICE file distributed
13  * with this work for additional information regarding copyright
14  * ownership. The ASF licenses this file to you under the Apache
15  * License, Version 2.0 (the "License"); you may not use this file
16  * except in compliance with the License. You may obtain a copy of
17  * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include "diagramlayoutatoms.hxx"
21 
22 #include <set>
23 
25 
27 #include <sal/log.hxx>
28 
29 #include <o3tl/unit_conversion.hxx>
31 #include <oox/token/properties.hxx>
34 #include <drawingml/textbody.hxx>
36 #include <drawingml/textrun.hxx>
38 #include <com/sun/star/drawing/TextFitToSizeType.hpp>
39 
40 using namespace ::com::sun::star;
41 using namespace ::com::sun::star::uno;
42 using namespace ::com::sun::star::xml::sax;
43 using namespace ::oox::core;
44 
45 namespace
46 {
48 oox::OptValue<sal_Int32> findProperty(const oox::drawingml::LayoutPropertyMap& rProperties,
49  const OUString& rInternalName, sal_Int32 nProperty)
50 {
52 
53  auto it = rProperties.find(rInternalName);
54  if (it != rProperties.end())
55  {
56  const oox::drawingml::LayoutProperty& rProperty = it->second;
57  auto itProperty = rProperty.find(nProperty);
58  if (itProperty != rProperty.end())
59  oRet = itProperty->second;
60  }
61 
62  return oRet;
63 }
64 
69 bool isFontUnit(sal_Int32 nUnit)
70 {
71  return nUnit == oox::XML_primFontSz || nUnit == oox::XML_secFontSz;
72 }
73 
75 sal_Int32 getPropertyFromConstraint(sal_Int32 nConstraint)
76 {
77  switch (nConstraint)
78  {
79  case oox::XML_lMarg:
80  return oox::PROP_TextLeftDistance;
81  case oox::XML_rMarg:
82  return oox::PROP_TextRightDistance;
83  case oox::XML_tMarg:
84  return oox::PROP_TextUpperDistance;
85  case oox::XML_bMarg:
86  return oox::PROP_TextLowerDistance;
87  }
88 
89  return 0;
90 }
91 
96 bool containsDataNodeType(const oox::drawingml::ShapePtr& pShape, sal_Int32 nType)
97 {
98  if (pShape->getDataNodeType() == nType)
99  return true;
100 
101  for (const auto& pChild : pShape->getChildren())
102  {
103  if (containsDataNodeType(pChild, nType))
104  return true;
105  }
106 
107  return false;
108 }
109 }
110 
111 namespace oox::drawingml {
112 void SnakeAlg::layoutShapeChildren(const AlgAtom& rAlg, const ShapePtr& rShape,
113  const std::vector<Constraint>& rConstraints)
114 {
115  if (rShape->getChildren().empty() || rShape->getSize().Width == 0
116  || rShape->getSize().Height == 0)
117  return;
118 
119  // Parse constraints.
120  double fChildAspectRatio = rShape->getChildren()[0]->getAspectRatio();
121  double fShapeHeight = rShape->getSize().Height;
122  double fShapeWidth = rShape->getSize().Width;
123  // Check if we have a child aspect ratio. If so, need to shrink one dimension to
124  // achieve that ratio.
125  if (fChildAspectRatio && fShapeHeight && fChildAspectRatio < (fShapeWidth / fShapeHeight))
126  {
127  fShapeWidth = fShapeHeight * fChildAspectRatio;
128  }
129 
130  double fSpaceFromConstraint = 1.0;
131  LayoutPropertyMap aPropertiesByName;
132  std::map<sal_Int32, LayoutProperty> aPropertiesByType;
133  LayoutProperty& rParent = aPropertiesByName[""];
134  rParent[XML_w] = fShapeWidth;
135  rParent[XML_h] = fShapeHeight;
136  for (const auto& rConstr : rConstraints)
137  {
138  if (rConstr.mnRefType == XML_w || rConstr.mnRefType == XML_h)
139  {
140  if (rConstr.mnType == XML_sp && rConstr.msForName.isEmpty())
141  fSpaceFromConstraint = rConstr.mfFactor;
142  }
143 
144  auto itRefForName = aPropertiesByName.find(rConstr.msRefForName);
145  if (itRefForName == aPropertiesByName.end())
146  {
147  continue;
148  }
149 
150  auto it = itRefForName->second.find(rConstr.mnRefType);
151  if (it == itRefForName->second.end())
152  {
153  continue;
154  }
155 
156  if (rConstr.mfValue != 0.0)
157  {
158  continue;
159  }
160 
161  sal_Int32 nValue = it->second * rConstr.mfFactor;
162 
163  if (rConstr.mnPointType == XML_none)
164  {
165  aPropertiesByName[rConstr.msForName][rConstr.mnType] = nValue;
166  }
167  else
168  {
169  aPropertiesByType[rConstr.mnPointType][rConstr.mnType] = nValue;
170  }
171  }
172 
173  std::vector<sal_Int32> aShapeWidths(rShape->getChildren().size());
174  for (size_t i = 0; i < rShape->getChildren().size(); ++i)
175  {
176  ShapePtr pChild = rShape->getChildren()[i];
177  if (!pChild->getDataNodeType())
178  {
179  // TODO handle the case when the requirement applies by name, not by point type.
180  aShapeWidths[i] = fShapeWidth;
181  continue;
182  }
183 
184  auto itNodeType = aPropertiesByType.find(pChild->getDataNodeType());
185  if (itNodeType == aPropertiesByType.end())
186  {
187  aShapeWidths[i] = fShapeWidth;
188  continue;
189  }
190 
191  auto it = itNodeType->second.find(XML_w);
192  if (it == itNodeType->second.end())
193  {
194  aShapeWidths[i] = fShapeWidth;
195  continue;
196  }
197 
198  aShapeWidths[i] = it->second;
199  }
200 
201  bool bSpaceFromConstraints = fSpaceFromConstraint != 1.0;
202 
203  const AlgAtom::ParamMap& rMap = rAlg.getMap();
204  const sal_Int32 nDir = rMap.count(XML_grDir) ? rMap.find(XML_grDir)->second : XML_tL;
205  sal_Int32 nIncX = 1;
206  sal_Int32 nIncY = 1;
207  bool bHorizontal = true;
208  switch (nDir)
209  {
210  case XML_tL:
211  nIncX = 1;
212  nIncY = 1;
213  break;
214  case XML_tR:
215  nIncX = -1;
216  nIncY = 1;
217  break;
218  case XML_bL:
219  nIncX = 1;
220  nIncY = -1;
221  bHorizontal = false;
222  break;
223  case XML_bR:
224  nIncX = -1;
225  nIncY = -1;
226  bHorizontal = false;
227  break;
228  }
229 
230  sal_Int32 nCount = rShape->getChildren().size();
231  // Defaults in case not provided by constraints.
232  double fSpace = bSpaceFromConstraints ? fSpaceFromConstraint : 0.3;
233  double fAspectRatio = 0.54; // diagram should not spill outside, earlier it was 0.6
234 
235  sal_Int32 nCol = 1;
236  sal_Int32 nRow = 1;
237  sal_Int32 nMaxRowWidth = 0;
238  if (nCount <= fChildAspectRatio)
239  // Child aspect ratio request (width/height) is N, and we have at most N shapes.
240  // This means we don't need multiple columns.
241  nRow = nCount;
242  else
243  {
244  for (; nRow < nCount; nRow++)
245  {
246  nCol = std::ceil(static_cast<double>(nCount) / nRow);
247  sal_Int32 nRowWidth = 0;
248  for (sal_Int32 i = 0; i < nCol; ++i)
249  {
250  if (i >= nCount)
251  {
252  break;
253  }
254 
255  nRowWidth += aShapeWidths[i];
256  }
257  double fTotalShapesHeight = fShapeHeight * nRow;
258  if (nRowWidth && fTotalShapesHeight / nRowWidth >= fAspectRatio)
259  {
260  if (nRowWidth > nMaxRowWidth)
261  {
262  nMaxRowWidth = nRowWidth;
263  }
264  break;
265  }
266  }
267  }
268 
269  SAL_INFO("oox.drawingml", "Snake layout grid: " << nCol << "x" << nRow);
270 
271  sal_Int32 nWidth = rShape->getSize().Width / (nCol + (nCol - 1) * fSpace);
272  awt::Size aChildSize(nWidth, nWidth * fAspectRatio);
273  if (nCol == 1 && nRow > 1)
274  {
275  // We have a single column, so count the height based on the parent height, not
276  // based on width.
277  // Space occurs inside children; also double amount of space is needed outside (on
278  // both sides), if the factor comes from a constraint.
279  sal_Int32 nNumSpaces = -1;
280  if (bSpaceFromConstraints)
281  nNumSpaces += 4;
282  sal_Int32 nHeight = rShape->getSize().Height / (nRow + (nRow + nNumSpaces) * fSpace);
283 
284  if (fChildAspectRatio > 1)
285  {
286  // Shrink width if the aspect ratio requires it.
287  nWidth = std::min(rShape->getSize().Width,
288  static_cast<sal_Int32>(nHeight * fChildAspectRatio));
289  aChildSize = awt::Size(nWidth, nHeight);
290  }
291 
292  bHorizontal = false;
293  }
294 
295  awt::Point aCurrPos(0, 0);
296  if (nIncX == -1)
297  aCurrPos.X = rShape->getSize().Width - aChildSize.Width;
298  if (nIncY == -1)
299  aCurrPos.Y = rShape->getSize().Height - aChildSize.Height;
300  else if (bSpaceFromConstraints)
301  {
302  if (!bHorizontal)
303  {
304  // Initial vertical offset to have upper spacing (outside, so double amount).
305  aCurrPos.Y = aChildSize.Height * fSpace * 2;
306  }
307  }
308 
309  sal_Int32 nStartX = aCurrPos.X;
310  sal_Int32 nColIdx = 0, index = 0;
311 
312  const sal_Int32 aContDir
313  = rMap.count(XML_contDir) ? rMap.find(XML_contDir)->second : XML_sameDir;
314 
315  switch (aContDir)
316  {
317  case XML_sameDir:
318  {
319  sal_Int32 nRowHeight = 0;
320  for (auto& aCurrShape : rShape->getChildren())
321  {
322  aCurrShape->setPosition(aCurrPos);
323  awt::Size aCurrSize(aChildSize);
324  // aShapeWidths items are a portion of nMaxRowWidth. We want the same ratio,
325  // based on the original parent width, ignoring the aspect ratio request.
326  bool bWidthsFromConstraints
327  = nCount >= 2 && rShape->getChildren()[1]->getDataNodeType() == XML_sibTrans;
328  if (bWidthsFromConstraints && nMaxRowWidth)
329  {
330  double fWidthFactor = static_cast<double>(aShapeWidths[index]) / nMaxRowWidth;
331  // We can only work from constraints if spacing is represented by a real
332  // child shape.
333  aCurrSize.Width = rShape->getSize().Width * fWidthFactor;
334  }
335  if (fChildAspectRatio)
336  {
337  aCurrSize.Height = aCurrSize.Width / fChildAspectRatio;
338 
339  // Child shapes are not allowed to leave their parent.
340  aCurrSize.Height = std::min<sal_Int32>(
341  aCurrSize.Height, rShape->getSize().Height / (nRow + (nRow - 1) * fSpace));
342  }
343  if (aCurrSize.Height > nRowHeight)
344  {
345  nRowHeight = aCurrSize.Height;
346  }
347  aCurrShape->setSize(aCurrSize);
348  aCurrShape->setChildSize(aCurrSize);
349 
350  index++; // counts index of child, helpful for positioning.
351 
352  if (index % nCol == 0 || ((index / nCol) + 1) != nRow)
353  aCurrPos.X += nIncX * (aCurrSize.Width + fSpace * aCurrSize.Width);
354 
355  if (++nColIdx == nCol) // condition for next row
356  {
357  // if last row, then position children according to number of shapes.
358  if ((index + 1) % nCol != 0 && (index + 1) >= 3
359  && ((index + 1) / nCol + 1) == nRow && nCount != nRow * nCol)
360  {
361  // position first child of last row
362  if (bWidthsFromConstraints)
363  {
364  aCurrPos.X = nStartX;
365  }
366  else
367  {
368  // Can assume that all child shape has the same width.
369  aCurrPos.X
370  = nStartX
371  + (nIncX * (aCurrSize.Width + fSpace * aCurrSize.Width)) / 2;
372  }
373  }
374  else
375  // if not last row, positions first child of that row
376  aCurrPos.X = nStartX;
377  aCurrPos.Y += nIncY * (nRowHeight + fSpace * nRowHeight);
378  nColIdx = 0;
379  nRowHeight = 0;
380  }
381 
382  // positions children in the last row.
383  if (index % nCol != 0 && index >= 3 && ((index / nCol) + 1) == nRow)
384  aCurrPos.X += (nIncX * (aCurrSize.Width + fSpace * aCurrSize.Width));
385  }
386  break;
387  }
388  case XML_revDir:
389  for (auto& aCurrShape : rShape->getChildren())
390  {
391  aCurrShape->setPosition(aCurrPos);
392  aCurrShape->setSize(aChildSize);
393  aCurrShape->setChildSize(aChildSize);
394 
395  index++; // counts index of child, helpful for positioning.
396 
397  /*
398  index%col -> tests node is at last column
399  ((index/nCol)+1)!=nRow) -> tests node is at last row or not
400  ((index/nCol)+1)%2!=0 -> tests node is at row which is multiple of 2, important for revDir
401  num!=nRow*nCol -> tests how last row nodes should be spread.
402  */
403 
404  if ((index % nCol == 0 || ((index / nCol) + 1) != nRow)
405  && ((index / nCol) + 1) % 2 != 0)
406  aCurrPos.X += (aChildSize.Width + fSpace * aChildSize.Width);
407  else if (index % nCol != 0
408  && ((index / nCol) + 1) != nRow) // child other than placed at last column
409  aCurrPos.X -= (aChildSize.Width + fSpace * aChildSize.Width);
410 
411  if (++nColIdx == nCol) // condition for next row
412  {
413  // if last row, then position children according to number of shapes.
414  if ((index + 1) % nCol != 0 && (index + 1) >= 4
415  && ((index + 1) / nCol + 1) == nRow && nCount != nRow * nCol
416  && ((index / nCol) + 1) % 2 == 0)
417  // position first child of last row
418  aCurrPos.X -= aChildSize.Width * 3 / 2;
419  else if ((index + 1) % nCol != 0 && (index + 1) >= 4
420  && ((index + 1) / nCol + 1) == nRow && nCount != nRow * nCol
421  && ((index / nCol) + 1) % 2 != 0)
422  aCurrPos.X = nStartX
423  + (nIncX * (aChildSize.Width + fSpace * aChildSize.Width)) / 2;
424  else if (((index / nCol) + 1) % 2 != 0)
425  aCurrPos.X = nStartX;
426 
427  aCurrPos.Y += nIncY * (aChildSize.Height + fSpace * aChildSize.Height);
428  nColIdx = 0;
429  }
430 
431  // positions children in the last row.
432  if (index % nCol != 0 && index >= 3 && ((index / nCol) + 1) == nRow
433  && ((index / nCol) + 1) % 2 == 0)
434  //if row%2=0 then start from left else
435  aCurrPos.X -= (nIncX * (aChildSize.Width + fSpace * aChildSize.Width));
436  else if (index % nCol != 0 && index >= 3 && ((index / nCol) + 1) == nRow
437  && ((index / nCol) + 1) % 2 != 0)
438  // start from right
439  aCurrPos.X += (nIncX * (aChildSize.Width + fSpace * aChildSize.Width));
440  }
441  break;
442  }
443 }
444 
446 {
447  if (rShape->getChildren().empty() || rShape->getSize().Width == 0
448  || rShape->getSize().Height == 0)
449  return;
450 
451  // const sal_Int32 nDir = maMap.count(XML_linDir) ? maMap.find(XML_linDir)->second : XML_fromT;
452  // const sal_Int32 npyraAcctPos = maMap.count(XML_pyraAcctPos) ? maMap.find(XML_pyraAcctPos)->second : XML_bef;
453  // const sal_Int32 ntxDir = maMap.count(XML_txDir) ? maMap.find(XML_txDir)->second : XML_fromT;
454  // const sal_Int32 npyraLvlNode = maMap.count(XML_pyraLvlNode) ? maMap.find(XML_pyraLvlNode)->second : XML_level;
455  // uncomment when use in code.
456 
457  sal_Int32 nCount = rShape->getChildren().size();
458  double fAspectRatio = 0.32;
459 
460  awt::Size aChildSize = rShape->getSize();
461  aChildSize.Width /= nCount;
462  aChildSize.Height /= nCount;
463 
464  awt::Point aCurrPos(0, 0);
465  aCurrPos.X = fAspectRatio * aChildSize.Width * (nCount - 1);
466  aCurrPos.Y = fAspectRatio * aChildSize.Height;
467 
468  for (auto& aCurrShape : rShape->getChildren())
469  {
470  aCurrShape->setPosition(aCurrPos);
471  if (nCount > 1)
472  {
473  aCurrPos.X -= aChildSize.Height / (nCount - 1);
474  }
475  aChildSize.Width += aChildSize.Height;
476  aCurrShape->setSize(aChildSize);
477  aCurrShape->setChildSize(aChildSize);
478  aCurrPos.Y += (aChildSize.Height);
479  }
480 }
481 
482 bool CompositeAlg::inferFromLayoutProperty(const LayoutProperty& rMap, sal_Int32 nRefType,
483  sal_Int32& rValue)
484 {
485  switch (nRefType)
486  {
487  case XML_r:
488  {
489  auto it = rMap.find(XML_l);
490  if (it == rMap.end())
491  {
492  return false;
493  }
494  sal_Int32 nLeft = it->second;
495  it = rMap.find(XML_w);
496  if (it == rMap.end())
497  {
498  return false;
499  }
500  rValue = nLeft + it->second;
501  return true;
502  }
503  default:
504  break;
505  }
506 
507  return false;
508 }
509 
511  LayoutPropertyMap& rProperties)
512 {
513  // TODO handle the case when we have ptType="...", not forName="...".
514  if (rConstraint.msForName.isEmpty())
515  {
516  return;
517  }
518 
519  const LayoutPropertyMap::const_iterator aRef = rProperties.find(rConstraint.msRefForName);
520  if (aRef == rProperties.end())
521  return;
522 
523  const LayoutProperty::const_iterator aRefType = aRef->second.find(rConstraint.mnRefType);
524  sal_Int32 nInferredValue = 0;
525  if (aRefType != aRef->second.end())
526  {
527  // Reference is found directly.
528  rProperties[rConstraint.msForName][rConstraint.mnType]
529  = aRefType->second * rConstraint.mfFactor;
530  }
531  else if (inferFromLayoutProperty(aRef->second, rConstraint.mnRefType, nInferredValue))
532  {
533  // Reference can be inferred.
534  rProperties[rConstraint.msForName][rConstraint.mnType]
535  = nInferredValue * rConstraint.mfFactor;
536  }
537  else
538  {
539  // Reference not found, assume a fixed value.
540  // Values are never in EMU, while oox::drawingml::Shape position and size are always in
541  // EMU.
542  const double fValue = o3tl::convert(rConstraint.mfValue,
543  isFontUnit(rConstraint.mnRefType) ? o3tl::Length::pt
546  rProperties[rConstraint.msForName][rConstraint.mnType] = fValue;
547  }
548 }
549 
551  const std::vector<Constraint>& rConstraints)
552 {
554  LayoutProperty& rParent = aProperties[""];
555 
556  sal_Int32 nParentXOffset = 0;
557 
558  // Track min/max vertical positions, so we can center everything at the end, if needed.
559  sal_Int32 nVertMin = std::numeric_limits<sal_Int32>::max();
560  sal_Int32 nVertMax = 0;
561 
562  if (rAlg.getAspectRatio() != 1.0)
563  {
564  rParent[XML_w] = rShape->getSize().Width;
565  rParent[XML_h] = rShape->getSize().Height;
566  rParent[XML_l] = 0;
567  rParent[XML_t] = 0;
568  rParent[XML_r] = rShape->getSize().Width;
569  rParent[XML_b] = rShape->getSize().Height;
570  }
571  else
572  {
573  // Shrink width to be only as large as height.
574  rParent[XML_w] = std::min(rShape->getSize().Width, rShape->getSize().Height);
575  rParent[XML_h] = rShape->getSize().Height;
576  if (rParent[XML_w] < rShape->getSize().Width)
577  nParentXOffset = (rShape->getSize().Width - rParent[XML_w]) / 2;
578  rParent[XML_l] = nParentXOffset;
579  rParent[XML_t] = 0;
580  rParent[XML_r] = rShape->getSize().Width - rParent[XML_l];
581  rParent[XML_b] = rShape->getSize().Height;
582  }
583 
584  for (const auto& rConstr : rConstraints)
585  {
586  // Apply direct constraints for all layout nodes.
587  applyConstraintToLayout(rConstr, aProperties);
588  }
589 
590  for (auto& aCurrShape : rShape->getChildren())
591  {
592  // Apply constraints from the current layout node for this child shape.
593  // Previous child shapes may have changed aProperties.
594  for (const auto& rConstr : rConstraints)
595  {
596  if (rConstr.msForName != aCurrShape->getInternalName())
597  {
598  continue;
599  }
600 
601  applyConstraintToLayout(rConstr, aProperties);
602  }
603 
604  // Apply constraints from the child layout node for this child shape.
605  // This builds on top of the own parent state + the state of previous shapes in the
606  // same composite algorithm.
607  const LayoutNode& rLayoutNode = rAlg.getLayoutNode();
608  for (const auto& pDirectChild : rLayoutNode.getChildren())
609  {
610  auto pLayoutNode = dynamic_cast<LayoutNode*>(pDirectChild.get());
611  if (!pLayoutNode)
612  {
613  continue;
614  }
615 
616  if (pLayoutNode->getName() != aCurrShape->getInternalName())
617  {
618  continue;
619  }
620 
621  for (const auto& pChild : pLayoutNode->getChildren())
622  {
623  auto pConstraintAtom = dynamic_cast<ConstraintAtom*>(pChild.get());
624  if (!pConstraintAtom)
625  {
626  continue;
627  }
628 
629  const Constraint& rConstraint = pConstraintAtom->getConstraint();
630  if (!rConstraint.msForName.isEmpty())
631  {
632  continue;
633  }
634 
635  if (!rConstraint.msRefForName.isEmpty())
636  {
637  continue;
638  }
639 
640  // Either an absolute value or a factor of a property.
641  if (rConstraint.mfValue == 0.0 && rConstraint.mnRefType == XML_none)
642  {
643  continue;
644  }
645 
646  Constraint aConstraint(rConstraint);
647  aConstraint.msForName = pLayoutNode->getName();
648  aConstraint.msRefForName = pLayoutNode->getName();
649 
650  applyConstraintToLayout(aConstraint, aProperties);
651  }
652  }
653 
654  awt::Size aSize = rShape->getSize();
655  awt::Point aPos(0, 0);
656 
657  const LayoutPropertyMap::const_iterator aPropIt
658  = aProperties.find(aCurrShape->getInternalName());
659  if (aPropIt != aProperties.end())
660  {
661  const LayoutProperty& rProp = aPropIt->second;
662  LayoutProperty::const_iterator it, it2;
663 
664  if ((it = rProp.find(XML_w)) != rProp.end())
665  aSize.Width = std::min(it->second, rShape->getSize().Width);
666  if ((it = rProp.find(XML_h)) != rProp.end())
667  aSize.Height = std::min(it->second, rShape->getSize().Height);
668 
669  if ((it = rProp.find(XML_l)) != rProp.end())
670  aPos.X = it->second;
671  else if ((it = rProp.find(XML_ctrX)) != rProp.end())
672  aPos.X = it->second - aSize.Width / 2;
673  else if ((it = rProp.find(XML_r)) != rProp.end())
674  aPos.X = it->second - aSize.Width;
675 
676  if ((it = rProp.find(XML_t)) != rProp.end())
677  aPos.Y = it->second;
678  else if ((it = rProp.find(XML_ctrY)) != rProp.end())
679  aPos.Y = it->second - aSize.Height / 2;
680  else if ((it = rProp.find(XML_b)) != rProp.end())
681  aPos.Y = it->second - aSize.Height;
682 
683  if ((it = rProp.find(XML_l)) != rProp.end() && (it2 = rProp.find(XML_r)) != rProp.end())
684  aSize.Width = it2->second - it->second;
685  if ((it = rProp.find(XML_t)) != rProp.end() && (it2 = rProp.find(XML_b)) != rProp.end())
686  aSize.Height = it2->second - it->second;
687 
688  aPos.X += nParentXOffset;
689  aSize.Width = std::min(aSize.Width, rShape->getSize().Width - aPos.X);
690  aSize.Height = std::min(aSize.Height, rShape->getSize().Height - aPos.Y);
691  }
692  else
693  SAL_WARN("oox.drawingml", "composite layout properties not found for shape "
694  << aCurrShape->getInternalName());
695 
696  aCurrShape->setSize(aSize);
697  aCurrShape->setChildSize(aSize);
698  aCurrShape->setPosition(aPos);
699 
700  nVertMin = std::min(aPos.Y, nVertMin);
701  nVertMax = std::max(aPos.Y + aSize.Height, nVertMax);
702 
703  NamedShapePairs& rDiagramFontHeights
704  = rAlg.getLayoutNode().getDiagram().getShape()->getDiagramFontHeights();
705  auto it = rDiagramFontHeights.find(aCurrShape->getInternalName());
706  if (it != rDiagramFontHeights.end())
707  {
708  // Internal name matches: put drawingml::Shape to the relevant group, for
709  // synchronized font height handling.
710  it->second.insert({ aCurrShape, {} });
711  }
712  }
713 
714  // See if all vertical space is used or we have to center the content.
715  if (!(nVertMin >= 0 && nVertMin <= nVertMax && nVertMax <= rParent[XML_h]))
716  return;
717 
718  sal_Int32 nDiff = rParent[XML_h] - (nVertMax - nVertMin);
719  if (nDiff > 0)
720  {
721  for (auto& aCurrShape : rShape->getChildren())
722  {
723  awt::Point aPosition = aCurrShape->getPosition();
724  aPosition.Y += nDiff / 2;
725  aCurrShape->setPosition(aPosition);
726  }
727  }
728 }
729 
731  : mnCnt( -1 )
732  , mbHideLastTrans( true )
733  , mnPtType( 0 )
734  , mnSt( 0 )
735  , mnStep( 1 )
736 {
737 }
738 
740 {
741  AttributeList attr( xAttr );
742  maAxis = attr.getTokenList(XML_axis);
743  mnCnt = attr.getInteger( XML_cnt, -1 );
744  mbHideLastTrans = attr.getBool( XML_hideLastTrans, true );
745  mnSt = attr.getInteger( XML_st, 0 );
746  mnStep = attr.getInteger( XML_step, 1 );
747 
748  // better to keep first token instead of error when multiple values
749  std::vector<sal_Int32> aPtTypes = attr.getTokenList(XML_ptType);
750  mnPtType = aPtTypes.empty() ? XML_all : aPtTypes.front();
751 }
752 
754  : mnFunc( 0 )
755  , mnArg( 0 )
756  , mnOp( 0 )
757  , mnVal( 0 )
758 {
759 }
760 
762 {
763  mnFunc = xAttr->getOptionalValueToken( XML_func, 0 );
764  mnArg = xAttr->getOptionalValueToken( XML_arg, XML_none );
765  mnOp = xAttr->getOptionalValueToken( XML_op, 0 );
766  msVal = xAttr->getOptionalValue( XML_val );
767  mnVal = xAttr->getOptionalValueToken( XML_val, 0 );
768 }
769 
770 void LayoutAtom::dump(int level)
771 {
772  SAL_INFO("oox.drawingml", "level = " << level << " - " << msName << " of type " << typeid(*this).name() );
773  for (const auto& pAtom : getChildren())
774  pAtom->dump(level + 1);
775 }
776 
778  LayoutAtom(rLayoutNode)
779 {
780  maIter.loadFromXAttr(xAttributes);
781 }
782 
784 {
785  rVisitor.visit(*this);
786 }
787 
789 {
790  if (!msRef.isEmpty())
791  {
792  const LayoutAtomMap& rLayoutAtomMap = getLayoutNode().getDiagram().getLayout()->getLayoutAtomMap();
793  LayoutAtomMap::const_iterator pRefAtom = rLayoutAtomMap.find(msRef);
794  if (pRefAtom != rLayoutAtomMap.end())
795  return pRefAtom->second;
796  else
797  SAL_WARN("oox.drawingml", "ForEach reference \"" << msRef << "\" not found");
798  }
799  return LayoutAtomPtr();
800 }
801 
803 {
804  rVisitor.visit(*this);
805 }
806 
807 ConditionAtom::ConditionAtom(LayoutNode& rLayoutNode, bool isElse, const Reference< XFastAttributeList >& xAttributes) :
808  LayoutAtom(rLayoutNode),
809  mIsElse(isElse)
810 {
811  maIter.loadFromXAttr( xAttributes );
812  maCond.loadFromXAttr( xAttributes );
813 }
814 
815 bool ConditionAtom::compareResult(sal_Int32 nOperator, sal_Int32 nFirst, sal_Int32 nSecond)
816 {
817  switch (nOperator)
818  {
819  case XML_equ: return nFirst == nSecond;
820  case XML_gt: return nFirst > nSecond;
821  case XML_gte: return nFirst >= nSecond;
822  case XML_lt: return nFirst < nSecond;
823  case XML_lte: return nFirst <= nSecond;
824  case XML_neq: return nFirst != nSecond;
825  default:
826  SAL_WARN("oox.drawingml", "unsupported operator: " << nOperator);
827  return false;
828  }
829 }
830 
831 namespace
832 {
837 OUString navigate(LayoutNode& rLayoutNode, sal_Int32 nType, std::u16string_view rFrom,
838  bool bSourceToDestination)
839 {
840  for (const auto& rConnection : rLayoutNode.getDiagram().getData()->getConnections())
841  {
842  if (rConnection.mnType != nType)
843  continue;
844 
845  if (bSourceToDestination)
846  {
847  if (rConnection.msSourceId == rFrom)
848  return rConnection.msDestId;
849  }
850  else
851  {
852  if (rConnection.msDestId == rFrom)
853  return rConnection.msSourceId;
854  }
855  }
856 
857  return OUString();
858 }
859 
860 sal_Int32 calcMaxDepth(std::u16string_view rNodeName, const dgm::Connections& rConnections)
861 {
862  sal_Int32 nMaxLength = 0;
863  for (auto const& aCxn : rConnections)
864  if (aCxn.mnType == XML_parOf && aCxn.msSourceId == rNodeName)
865  nMaxLength = std::max(nMaxLength, calcMaxDepth(aCxn.msDestId, rConnections) + 1);
866 
867  return nMaxLength;
868 }
869 }
870 
871 sal_Int32 ConditionAtom::getNodeCount(const dgm::Point* pPresPoint) const
872 {
873  sal_Int32 nCount = 0;
874  OUString sNodeId = pPresPoint->msPresentationAssociationId;
875 
876  // HACK: special case - count children of first child
877  if (maIter.maAxis.size() == 2 && maIter.maAxis[0] == XML_ch && maIter.maAxis[1] == XML_ch)
878  sNodeId = navigate(mrLayoutNode, XML_parOf, sNodeId, /*bSourceToDestination*/ true);
879 
880  if (!sNodeId.isEmpty())
881  {
882  for (const auto& aCxn : mrLayoutNode.getDiagram().getData()->getConnections())
883  if (aCxn.mnType == XML_parOf && aCxn.msSourceId == sNodeId)
884  nCount++;
885  }
886 
887  return nCount;
888 }
889 
890 bool ConditionAtom::getDecision(const dgm::Point* pPresPoint) const
891 {
892  if (mIsElse)
893  return true;
894  if (!pPresPoint)
895  return false;
896 
897  switch (maCond.mnFunc)
898  {
899  case XML_var:
900  {
901  if (maCond.mnArg == XML_dir)
902  return compareResult(maCond.mnOp, pPresPoint->mnDirection, maCond.mnVal);
903  else if (maCond.mnArg == XML_hierBranch)
904  {
905  sal_Int32 nHierarchyBranch = pPresPoint->moHierarchyBranch.get(XML_std);
906  if (!pPresPoint->moHierarchyBranch.has())
907  {
908  // If <dgm:hierBranch> is missing in the current presentation
909  // point, ask the parent.
910  OUString aParent = navigate(mrLayoutNode, XML_presParOf, pPresPoint->msModelId,
911  /*bSourceToDestination*/ false);
912  DiagramData::PointNameMap& rPointNameMap
913  = mrLayoutNode.getDiagram().getData()->getPointNameMap();
914  auto it = rPointNameMap.find(aParent);
915  if (it != rPointNameMap.end())
916  {
917  const dgm::Point* pParent = it->second;
918  if (pParent->moHierarchyBranch.has())
919  nHierarchyBranch = pParent->moHierarchyBranch.get();
920  }
921  }
922  return compareResult(maCond.mnOp, nHierarchyBranch, maCond.mnVal);
923  }
924  break;
925  }
926 
927  case XML_cnt:
928  return compareResult(maCond.mnOp, getNodeCount(pPresPoint), maCond.msVal.toInt32());
929 
930  case XML_maxDepth:
931  {
932  sal_Int32 nMaxDepth = calcMaxDepth(pPresPoint->msPresentationAssociationId, mrLayoutNode.getDiagram().getData()->getConnections());
933  return compareResult(maCond.mnOp, nMaxDepth, maCond.msVal.toInt32());
934  }
935 
936  case XML_depth:
937  case XML_pos:
938  case XML_revPos:
939  case XML_posEven:
940  case XML_posOdd:
941  // TODO
942  default:
943  SAL_WARN("oox.drawingml", "unknown function " << maCond.mnFunc);
944  break;
945  }
946 
947  return true;
948 }
949 
951 {
952  rVisitor.visit(*this);
953 }
954 
956 {
957  rVisitor.visit(*this);
958 }
959 
961 {
962  rVisitor.visit(*this);
963 }
964 
965 void ConstraintAtom::parseConstraint(std::vector<Constraint>& rConstraints,
966  bool bRequireForName) const
967 {
968  // Allowlist for cases where empty forName is handled.
969  if (bRequireForName)
970  {
971  switch (maConstraint.mnType)
972  {
973  case XML_sp:
974  case XML_lMarg:
975  case XML_rMarg:
976  case XML_tMarg:
977  case XML_bMarg:
978  bRequireForName = false;
979  break;
980  }
981  switch (maConstraint.mnPointType)
982  {
983  case XML_sibTrans:
984  bRequireForName = false;
985  break;
986  }
987  }
988 
989  if (bRequireForName && maConstraint.msForName.isEmpty())
990  return;
991 
992  // accepting only basic equality constraints
993  if ((maConstraint.mnOperator == XML_none || maConstraint.mnOperator == XML_equ)
994  && maConstraint.mnType != XML_none)
995  {
996  rConstraints.push_back(maConstraint);
997  }
998 }
999 
1000 void RuleAtom::parseRule(std::vector<Rule>& rRules) const
1001 {
1002  if (!maRule.msForName.isEmpty())
1003  {
1004  rRules.push_back(maRule);
1005  }
1006 }
1007 
1009 {
1010  rVisitor.visit(*this);
1011 }
1012 
1014 {
1015  sal_Int32 nConnRout = 0;
1016  sal_Int32 nBegSty = 0;
1017  sal_Int32 nEndSty = 0;
1018  if (maMap.count(oox::XML_connRout))
1019  nConnRout = maMap.find(oox::XML_connRout)->second;
1020  if (maMap.count(oox::XML_begSty))
1021  nBegSty = maMap.find(oox::XML_begSty)->second;
1022  if (maMap.count(oox::XML_endSty))
1023  nEndSty = maMap.find(oox::XML_endSty)->second;
1024 
1025  if (nConnRout == oox::XML_bend)
1026  return 0; // was oox::XML_bentConnector3 - connectors are hidden in org chart as they don't work anyway
1027  if (nBegSty == oox::XML_arr && nEndSty == oox::XML_arr)
1028  return oox::XML_leftRightArrow;
1029  if (nBegSty == oox::XML_arr)
1030  return oox::XML_leftArrow;
1031  if (nEndSty == oox::XML_arr)
1032  return oox::XML_rightArrow;
1033 
1034  return oox::XML_rightArrow;
1035 }
1036 
1038 {
1039  if (rShape->getChildren().empty())
1040  return (rShape->getSubType() != XML_conn) ? 1 : 0;
1041 
1042  sal_Int32 nDir = XML_fromL;
1043  if (mnType == XML_hierRoot)
1044  nDir = XML_fromT;
1045  else if (maMap.count(XML_linDir))
1046  nDir = maMap.find(XML_linDir)->second;
1047 
1048  const sal_Int32 nSecDir = maMap.count(XML_secLinDir) ? maMap.find(XML_secLinDir)->second : 0;
1049 
1050  sal_Int32 nCount = 0;
1051  if (nDir == XML_fromT || nDir == XML_fromB)
1052  {
1053  for (const ShapePtr& pChild : rShape->getChildren())
1054  nCount += pChild->getVerticalShapesCount();
1055  }
1056  else if ((nDir == XML_fromL || nDir == XML_fromR) && nSecDir == XML_fromT)
1057  {
1058  for (const ShapePtr& pChild : rShape->getChildren())
1059  nCount += pChild->getVerticalShapesCount();
1060  nCount = (nCount + 1) / 2;
1061  }
1062  else
1063  {
1064  for (const ShapePtr& pChild : rShape->getChildren())
1065  nCount = std::max(nCount, pChild->getVerticalShapesCount());
1066  }
1067 
1068  return nCount;
1069 }
1070 
1071 namespace
1072 {
1074 bool HasCustomText(const ShapePtr& rShape, LayoutNode& rLayoutNode)
1075 {
1076  const PresPointShapeMap& rPresPointShapeMap
1077  = rLayoutNode.getDiagram().getLayout()->getPresPointShapeMap();
1078  const DiagramData::StringMap& rPresOfNameMap
1079  = rLayoutNode.getDiagram().getData()->getPresOfNameMap();
1080  const DiagramData::PointNameMap& rPointNameMap
1081  = rLayoutNode.getDiagram().getData()->getPointNameMap();
1082  // Get the first presentation node of the shape.
1083  const dgm::Point* pPresNode = nullptr;
1084  for (const auto& rPair : rPresPointShapeMap)
1085  {
1086  if (rPair.second == rShape)
1087  {
1088  pPresNode = rPair.first;
1089  break;
1090  }
1091  }
1092  // Get the first data node of the presentation node.
1093  dgm::Point* pDataNode = nullptr;
1094  if (pPresNode)
1095  {
1096  auto itPresToData = rPresOfNameMap.find(pPresNode->msModelId);
1097  if (itPresToData != rPresOfNameMap.end())
1098  {
1099  for (const auto& rPair : itPresToData->second)
1100  {
1101  const DiagramData::SourceIdAndDepth& rItem = rPair.second;
1102  auto it = rPointNameMap.find(rItem.msSourceId);
1103  if (it != rPointNameMap.end())
1104  {
1105  pDataNode = it->second;
1106  break;
1107  }
1108  }
1109  }
1110  }
1111 
1112  // If we have a data node, see if its text is customized or not.
1113  if (pDataNode)
1114  {
1115  return pDataNode->mbCustomText;
1116  }
1117 
1118  return false;
1119 }
1120 }
1121 
1122 void AlgAtom::layoutShape(const ShapePtr& rShape, const std::vector<Constraint>& rConstraints,
1123  const std::vector<Rule>& rRules)
1124 {
1125  if (mnType != XML_lin)
1126  {
1127  // TODO Handle spacing from constraints for non-lin algorithms as well.
1128  rShape->getChildren().erase(
1129  std::remove_if(rShape->getChildren().begin(), rShape->getChildren().end(),
1130  [](const ShapePtr& aChild) {
1131  return aChild->getServiceName() == "com.sun.star.drawing.GroupShape"
1132  && aChild->getChildren().empty();
1133  }),
1134  rShape->getChildren().end());
1135  }
1136 
1137  switch(mnType)
1138  {
1139  case XML_composite:
1140  {
1141  CompositeAlg::layoutShapeChildren(*this, rShape, rConstraints);
1142  break;
1143  }
1144 
1145  case XML_conn:
1146  {
1147  if (rShape->getSubType() == XML_conn)
1148  {
1149  // There is no shape type "conn", replace it by an arrow based
1150  // on the direction of the parent linear layout.
1151  sal_Int32 nType = getConnectorType();
1152 
1153  rShape->setSubType(nType);
1154  rShape->getCustomShapeProperties()->setShapePresetType(nType);
1155  }
1156 
1157  // Parse constraints to adjust the size.
1158  std::vector<Constraint> aDirectConstraints;
1159  const LayoutNode& rLayoutNode = getLayoutNode();
1160  for (const auto& pChild : rLayoutNode.getChildren())
1161  {
1162  auto pConstraintAtom = dynamic_cast<ConstraintAtom*>(pChild.get());
1163  if (pConstraintAtom)
1164  pConstraintAtom->parseConstraint(aDirectConstraints, /*bRequireForName=*/false);
1165  }
1166 
1168  LayoutProperty& rParent = aProperties[""];
1169  rParent[XML_w] = rShape->getSize().Width;
1170  rParent[XML_h] = rShape->getSize().Height;
1171  rParent[XML_l] = 0;
1172  rParent[XML_t] = 0;
1173  rParent[XML_r] = rShape->getSize().Width;
1174  rParent[XML_b] = rShape->getSize().Height;
1175  for (const auto& rConstr : aDirectConstraints)
1176  {
1177  const LayoutPropertyMap::const_iterator aRef
1178  = aProperties.find(rConstr.msRefForName);
1179  if (aRef != aProperties.end())
1180  {
1181  const LayoutProperty::const_iterator aRefType
1182  = aRef->second.find(rConstr.mnRefType);
1183  if (aRefType != aRef->second.end())
1184  aProperties[rConstr.msForName][rConstr.mnType]
1185  = aRefType->second * rConstr.mfFactor;
1186  }
1187  }
1188  awt::Size aSize;
1189  aSize.Width = rParent[XML_w];
1190  aSize.Height = rParent[XML_h];
1191  // keep center position
1192  awt::Point aPos = rShape->getPosition();
1193  aPos.X += (rShape->getSize().Width - aSize.Width) / 2;
1194  aPos.Y += (rShape->getSize().Height - aSize.Height) / 2;
1195  rShape->setPosition(aPos);
1196  rShape->setSize(aSize);
1197  break;
1198  }
1199 
1200  case XML_cycle:
1201  {
1202  if (rShape->getChildren().empty())
1203  break;
1204 
1205  const sal_Int32 nStartAngle = maMap.count(XML_stAng) ? maMap.find(XML_stAng)->second : 0;
1206  const sal_Int32 nSpanAngle = maMap.count(XML_spanAng) ? maMap.find(XML_spanAng)->second : 360;
1207  const sal_Int32 nRotationPath = maMap.count(XML_rotPath) ? maMap.find(XML_rotPath)->second : XML_none;
1208  const sal_Int32 nctrShpMap = maMap.count(XML_ctrShpMap) ? maMap.find(XML_ctrShpMap)->second : XML_none;
1209  const awt::Size aCenter(rShape->getSize().Width / 2, rShape->getSize().Height / 2);
1210  const awt::Size aChildSize(rShape->getSize().Width / 4, rShape->getSize().Height / 4);
1211  const awt::Size aConnectorSize(rShape->getSize().Width / 12, rShape->getSize().Height / 12);
1212  const sal_Int32 nRadius = std::min(
1213  (rShape->getSize().Width - aChildSize.Width) / 2,
1214  (rShape->getSize().Height - aChildSize.Height) / 2);
1215 
1216  std::vector<oox::drawingml::ShapePtr> aCycleChildren = rShape->getChildren();
1217 
1218  if (nctrShpMap == XML_fNode)
1219  {
1220  // first node placed in center, others around
1221  oox::drawingml::ShapePtr pCenterShape = aCycleChildren.front();
1222  aCycleChildren.erase(aCycleChildren.begin());
1223  const awt::Point aCurrPos(aCenter.Width - aChildSize.Width / 2,
1224  aCenter.Height - aChildSize.Height / 2);
1225  pCenterShape->setPosition(aCurrPos);
1226  pCenterShape->setSize(aChildSize);
1227  pCenterShape->setChildSize(aChildSize);
1228  }
1229 
1230  const sal_Int32 nShapes = aCycleChildren.size();
1231  if (nShapes)
1232  {
1233  const sal_Int32 nConnectorRadius = nRadius * cos(basegfx::deg2rad(nSpanAngle / nShapes));
1234  const sal_Int32 nConnectorAngle = nSpanAngle > 0 ? 0 : 180;
1235 
1236  sal_Int32 idx = 0;
1237  for (auto & aCurrShape : aCycleChildren)
1238  {
1239  const double fAngle = static_cast<double>(idx)*nSpanAngle/nShapes + nStartAngle;
1240  awt::Size aCurrSize = aChildSize;
1241  sal_Int32 nCurrRadius = nRadius;
1242  if (aCurrShape->getSubType() == XML_conn)
1243  {
1244  aCurrSize = aConnectorSize;
1245  nCurrRadius = nConnectorRadius;
1246  }
1247  const awt::Point aCurrPos(
1248  aCenter.Width + nCurrRadius*sin(basegfx::deg2rad(fAngle)) - aCurrSize.Width/2,
1249  aCenter.Height - nCurrRadius*cos(basegfx::deg2rad(fAngle)) - aCurrSize.Height/2);
1250 
1251  aCurrShape->setPosition(aCurrPos);
1252  aCurrShape->setSize(aCurrSize);
1253  aCurrShape->setChildSize(aCurrSize);
1254 
1255  if (nRotationPath == XML_alongPath)
1256  aCurrShape->setRotation(fAngle * PER_DEGREE);
1257 
1258  // connectors should be handled in conn, but we don't have
1259  // reference to previous and next child, so it's easier here
1260  if (aCurrShape->getSubType() == XML_conn)
1261  aCurrShape->setRotation((nConnectorAngle + fAngle) * PER_DEGREE);
1262 
1263  idx++;
1264  }
1265  }
1266  break;
1267  }
1268 
1269  case XML_hierChild:
1270  case XML_hierRoot:
1271  {
1272  if (rShape->getChildren().empty() || rShape->getSize().Width == 0 || rShape->getSize().Height == 0)
1273  break;
1274 
1275  // hierRoot is the manager -> employees vertical linear path,
1276  // hierChild is the first employee -> last employee horizontal
1277  // linear path.
1278  sal_Int32 nDir = XML_fromL;
1279  if (mnType == XML_hierRoot)
1280  nDir = XML_fromT;
1281  else if (maMap.count(XML_linDir))
1282  nDir = maMap.find(XML_linDir)->second;
1283 
1284  const sal_Int32 nSecDir = maMap.count(XML_secLinDir) ? maMap.find(XML_secLinDir)->second : 0;
1285 
1286  sal_Int32 nCount = rShape->getChildren().size();
1287 
1288  if (mnType == XML_hierChild)
1289  {
1290  // Connectors should not influence the size of non-connect shapes.
1291  nCount = std::count_if(
1292  rShape->getChildren().begin(), rShape->getChildren().end(),
1293  [](const ShapePtr& pShape) { return pShape->getSubType() != XML_conn; });
1294  }
1295 
1296  const double fSpaceWidth = 0.1;
1297  const double fSpaceHeight = 0.3;
1298 
1299  if (mnType == XML_hierRoot && nCount == 3)
1300  {
1301  // Order assistant nodes above employee nodes.
1302  std::vector<ShapePtr>& rChildren = rShape->getChildren();
1303  if (!containsDataNodeType(rChildren[1], XML_asst)
1304  && containsDataNodeType(rChildren[2], XML_asst))
1305  std::swap(rChildren[1], rChildren[2]);
1306  }
1307 
1308  sal_Int32 nHorizontalShapesCount = 1;
1309  if (nSecDir == XML_fromT)
1310  nHorizontalShapesCount = 2;
1311  else if (nDir == XML_fromL || nDir == XML_fromR)
1312  nHorizontalShapesCount = nCount;
1313 
1314  awt::Size aChildSize = rShape->getSize();
1315  aChildSize.Height /= (rShape->getVerticalShapesCount() + (rShape->getVerticalShapesCount() - 1) * fSpaceHeight);
1316  aChildSize.Width /= (nHorizontalShapesCount + (nHorizontalShapesCount - 1) * fSpaceWidth);
1317 
1318  awt::Size aConnectorSize = aChildSize;
1319  aConnectorSize.Width = 1;
1320 
1321  awt::Point aChildPos(0, 0);
1322 
1323  // indent children to show they are descendants, not siblings
1324  if (mnType == XML_hierChild && nHorizontalShapesCount == 1)
1325  {
1326  const double fChildIndent = 0.1;
1327  aChildPos.X = aChildSize.Width * fChildIndent;
1328  aChildSize.Width *= (1 - 2 * fChildIndent);
1329  }
1330 
1331  sal_Int32 nIdx = 0;
1332  sal_Int32 nRowHeight = 0;
1333  for (auto& pChild : rShape->getChildren())
1334  {
1335  pChild->setPosition(aChildPos);
1336 
1337  if (mnType == XML_hierChild && pChild->getSubType() == XML_conn)
1338  {
1339  // Connectors should not influence the position of
1340  // non-connect shapes.
1341  pChild->setSize(aConnectorSize);
1342  pChild->setChildSize(aConnectorSize);
1343  continue;
1344  }
1345 
1346  awt::Size aCurrSize = aChildSize;
1347  aCurrSize.Height *= pChild->getVerticalShapesCount() + (pChild->getVerticalShapesCount() - 1) * fSpaceHeight;
1348 
1349  pChild->setSize(aCurrSize);
1350  pChild->setChildSize(aCurrSize);
1351 
1352  if (nDir == XML_fromT || nDir == XML_fromB)
1353  aChildPos.Y += aCurrSize.Height + aChildSize.Height * fSpaceHeight;
1354  else
1355  aChildPos.X += aCurrSize.Width + aCurrSize.Width * fSpaceWidth;
1356 
1357  nRowHeight = std::max(nRowHeight, aCurrSize.Height);
1358 
1359  if (nSecDir == XML_fromT && nIdx % 2 == 1)
1360  {
1361  aChildPos.X = 0;
1362  aChildPos.Y += nRowHeight + aChildSize.Height * fSpaceHeight;
1363  nRowHeight = 0;
1364  }
1365 
1366  nIdx++;
1367  }
1368 
1369  break;
1370  }
1371 
1372  case XML_lin:
1373  {
1374  // spread children evenly across one axis, stretch across second
1375 
1376  if (rShape->getChildren().empty() || rShape->getSize().Width == 0 || rShape->getSize().Height == 0)
1377  break;
1378 
1379  const sal_Int32 nDir = maMap.count(XML_linDir) ? maMap.find(XML_linDir)->second : XML_fromL;
1380  const sal_Int32 nIncX = nDir==XML_fromL ? 1 : (nDir==XML_fromR ? -1 : 0);
1381  const sal_Int32 nIncY = nDir==XML_fromT ? 1 : (nDir==XML_fromB ? -1 : 0);
1382 
1383  double fCount = rShape->getChildren().size();
1384  sal_Int32 nConnectorAngle = 0;
1385  switch (nDir)
1386  {
1387  case XML_fromL: nConnectorAngle = 0; break;
1388  case XML_fromR: nConnectorAngle = 180; break;
1389  case XML_fromT: nConnectorAngle = 270; break;
1390  case XML_fromB: nConnectorAngle = 90; break;
1391  }
1392 
1393  awt::Size aSpaceSize;
1394 
1395  // Find out which constraint is relevant for which (internal) name.
1397  for (const auto& rConstraint : rConstraints)
1398  {
1399  if (rConstraint.msForName.isEmpty())
1400  continue;
1401 
1402  LayoutProperty& rProperty = aProperties[rConstraint.msForName];
1403  if (rConstraint.mnType == XML_w)
1404  {
1405  rProperty[XML_w] = rShape->getSize().Width * rConstraint.mfFactor;
1406  if (rProperty[XML_w] > rShape->getSize().Width)
1407  {
1408  rProperty[XML_w] = rShape->getSize().Width;
1409  }
1410  }
1411  if (rConstraint.mnType == XML_h)
1412  {
1413  rProperty[XML_h] = rShape->getSize().Height * rConstraint.mfFactor;
1414  if (rProperty[XML_h] > rShape->getSize().Height)
1415  {
1416  rProperty[XML_h] = rShape->getSize().Height;
1417  }
1418  }
1419 
1420  if (rConstraint.mnType == XML_primFontSz && rConstraint.mnFor == XML_des
1421  && rConstraint.mnOperator == XML_equ)
1422  {
1423  NamedShapePairs& rDiagramFontHeights
1424  = getLayoutNode().getDiagram().getShape()->getDiagramFontHeights();
1425  auto it = rDiagramFontHeights.find(rConstraint.msForName);
1426  if (it == rDiagramFontHeights.end())
1427  {
1428  // Start tracking all shapes with this internal name: they'll have the same
1429  // font height.
1430  rDiagramFontHeights[rConstraint.msForName] = {};
1431  }
1432  }
1433 
1434  // TODO: get values from differently named constraints as well
1435  if (rConstraint.msForName == "sp" || rConstraint.msForName == "space" || rConstraint.msForName == "sibTrans")
1436  {
1437  if (rConstraint.mnType == XML_w)
1438  aSpaceSize.Width = rShape->getSize().Width * rConstraint.mfFactor;
1439  if (rConstraint.mnType == XML_h)
1440  aSpaceSize.Height = rShape->getSize().Height * rConstraint.mfFactor;
1441  }
1442  }
1443 
1444  // first approximation of children size
1445  std::set<OUString> aChildrenToShrink;
1446  for (const auto& rRule : rRules)
1447  {
1448  // Consider rules: when scaling down, only change children where the rule allows
1449  // doing so.
1450  aChildrenToShrink.insert(rRule.msForName);
1451  }
1452 
1453  if (nDir == XML_fromT || nDir == XML_fromB)
1454  {
1455  // TODO consider rules for vertical linear layout as well.
1456  aChildrenToShrink.clear();
1457  }
1458 
1459  if (!aChildrenToShrink.empty())
1460  {
1461  // Have scaling info from rules: then only count scaled children.
1462  // Also count children which are a fraction of a scaled child.
1463  std::set<OUString> aChildrenToShrinkDeps;
1464  for (auto& aCurrShape : rShape->getChildren())
1465  {
1466  if (aChildrenToShrink.find(aCurrShape->getInternalName())
1467  == aChildrenToShrink.end())
1468  {
1469  if (fCount > 1.0)
1470  {
1471  fCount -= 1.0;
1472 
1473  bool bIsDependency = false;
1474  double fFactor = 0;
1475  for (const auto& rConstraint : rConstraints)
1476  {
1477  if (rConstraint.msForName != aCurrShape->getInternalName())
1478  {
1479  continue;
1480  }
1481 
1482  if ((nDir == XML_fromL || nDir == XML_fromR) && rConstraint.mnType != XML_w)
1483  {
1484  continue;
1485  }
1486  if ((nDir == XML_fromL || nDir == XML_fromR) && rConstraint.mnType == XML_w)
1487  {
1488  fFactor = rConstraint.mfFactor;
1489  }
1490 
1491  if ((nDir == XML_fromT || nDir == XML_fromB) && rConstraint.mnType != XML_h)
1492  {
1493  continue;
1494  }
1495  if ((nDir == XML_fromT || nDir == XML_fromB) && rConstraint.mnType == XML_h)
1496  {
1497  fFactor = rConstraint.mfFactor;
1498  }
1499 
1500  if (aChildrenToShrink.find(rConstraint.msRefForName) == aChildrenToShrink.end())
1501  {
1502  continue;
1503  }
1504 
1505  // At this point we have a child with a size which is a factor of an
1506  // other child which will be scaled.
1507  fCount += rConstraint.mfFactor;
1508  aChildrenToShrinkDeps.insert(aCurrShape->getInternalName());
1509  bIsDependency = true;
1510  break;
1511  }
1512 
1513  if (!bIsDependency && aCurrShape->getServiceName() == "com.sun.star.drawing.GroupShape")
1514  {
1515  bool bScaleDownEmptySpacing = false;
1516  if (nDir == XML_fromL || nDir == XML_fromR)
1517  {
1518  oox::OptValue<sal_Int32> oWidth = findProperty(aProperties, aCurrShape->getInternalName(), XML_w);
1519  bScaleDownEmptySpacing = oWidth.has() && oWidth.get() > 0;
1520  }
1521  if (!bScaleDownEmptySpacing && (nDir == XML_fromT || nDir == XML_fromB))
1522  {
1523  oox::OptValue<sal_Int32> oHeight = findProperty(aProperties, aCurrShape->getInternalName(), XML_h);
1524  bScaleDownEmptySpacing = oHeight.has() && oHeight.get() > 0;
1525  }
1526  if (bScaleDownEmptySpacing && aCurrShape->getChildren().empty())
1527  {
1528  fCount += fFactor;
1529  aChildrenToShrinkDeps.insert(aCurrShape->getInternalName());
1530  }
1531  }
1532  }
1533  }
1534  }
1535 
1536  aChildrenToShrink.insert(aChildrenToShrinkDeps.begin(), aChildrenToShrinkDeps.end());
1537 
1538  // No manual spacing: spacings are children as well.
1539  aSpaceSize = awt::Size();
1540  }
1541  else
1542  {
1543  // TODO Handle spacing from constraints without rules as well.
1544  rShape->getChildren().erase(
1545  std::remove_if(rShape->getChildren().begin(), rShape->getChildren().end(),
1546  [](const ShapePtr& aChild) {
1547  return aChild->getServiceName()
1548  == "com.sun.star.drawing.GroupShape"
1549  && aChild->getChildren().empty();
1550  }),
1551  rShape->getChildren().end());
1552  fCount = rShape->getChildren().size();
1553  }
1554  awt::Size aChildSize = rShape->getSize();
1555  if (nDir == XML_fromL || nDir == XML_fromR)
1556  aChildSize.Width /= fCount;
1557  else if (nDir == XML_fromT || nDir == XML_fromB)
1558  aChildSize.Height /= fCount;
1559 
1560  awt::Point aCurrPos(0, 0);
1561  if (nIncX == -1)
1562  aCurrPos.X = rShape->getSize().Width - aChildSize.Width;
1563  if (nIncY == -1)
1564  aCurrPos.Y = rShape->getSize().Height - aChildSize.Height;
1565 
1566  // See if children requested more than 100% space in total: scale
1567  // down in that case.
1568  awt::Size aTotalSize;
1569  for (const auto & aCurrShape : rShape->getChildren())
1570  {
1571  oox::OptValue<sal_Int32> oWidth = findProperty(aProperties, aCurrShape->getInternalName(), XML_w);
1572  oox::OptValue<sal_Int32> oHeight = findProperty(aProperties, aCurrShape->getInternalName(), XML_h);
1573  awt::Size aSize = aChildSize;
1574  if (oWidth.has())
1575  aSize.Width = oWidth.get();
1576  if (oHeight.has())
1577  aSize.Height = oHeight.get();
1578  aTotalSize.Width += aSize.Width;
1579  aTotalSize.Height += aSize.Height;
1580  }
1581 
1582  aTotalSize.Width += (fCount-1) * aSpaceSize.Width;
1583  aTotalSize.Height += (fCount-1) * aSpaceSize.Height;
1584 
1585  double fWidthScale = 1.0;
1586  double fHeightScale = 1.0;
1587  if (nIncX && aTotalSize.Width > rShape->getSize().Width)
1588  fWidthScale = static_cast<double>(rShape->getSize().Width) / aTotalSize.Width;
1589  if (nIncY && aTotalSize.Height > rShape->getSize().Height)
1590  fHeightScale = static_cast<double>(rShape->getSize().Height) / aTotalSize.Height;
1591  aSpaceSize.Width *= fWidthScale;
1592  aSpaceSize.Height *= fHeightScale;
1593 
1594  for (auto& aCurrShape : rShape->getChildren())
1595  {
1596  // Extract properties relevant for this shape from constraints.
1597  oox::OptValue<sal_Int32> oWidth = findProperty(aProperties, aCurrShape->getInternalName(), XML_w);
1598  oox::OptValue<sal_Int32> oHeight = findProperty(aProperties, aCurrShape->getInternalName(), XML_h);
1599 
1600  awt::Size aSize = aChildSize;
1601  if (oWidth.has())
1602  aSize.Width = oWidth.get();
1603  if (oHeight.has())
1604  aSize.Height = oHeight.get();
1605  if (aChildrenToShrink.empty()
1606  || aChildrenToShrink.find(aCurrShape->getInternalName())
1607  != aChildrenToShrink.end())
1608  {
1609  aSize.Width *= fWidthScale;
1610  }
1611  if (aChildrenToShrink.empty()
1612  || aChildrenToShrink.find(aCurrShape->getInternalName())
1613  != aChildrenToShrink.end())
1614  {
1615  aSize.Height *= fHeightScale;
1616  }
1617  aCurrShape->setSize(aSize);
1618  aCurrShape->setChildSize(aSize);
1619 
1620  // center in the other axis - probably some parameter controls it
1621  if (nIncX)
1622  aCurrPos.Y = (rShape->getSize().Height - aSize.Height) / 2;
1623  if (nIncY)
1624  aCurrPos.X = (rShape->getSize().Width - aSize.Width) / 2;
1625  if (aCurrPos.X < 0)
1626  {
1627  aCurrPos.X = 0;
1628  }
1629  if (aCurrPos.Y < 0)
1630  {
1631  aCurrPos.Y = 0;
1632  }
1633 
1634  aCurrShape->setPosition(aCurrPos);
1635 
1636  aCurrPos.X += nIncX * (aSize.Width + aSpaceSize.Width);
1637  aCurrPos.Y += nIncY * (aSize.Height + aSpaceSize.Height);
1638 
1639  // connectors should be handled in conn, but we don't have
1640  // reference to previous and next child, so it's easier here
1641  if (aCurrShape->getSubType() == XML_conn)
1642  aCurrShape->setRotation(nConnectorAngle * PER_DEGREE);
1643  }
1644 
1645  // Newer shapes are behind older ones by default. Reverse this if requested.
1646  sal_Int32 nChildOrder = XML_b;
1647  const LayoutNode* pParentLayoutNode = nullptr;
1648  for (LayoutAtomPtr pAtom = getParent(); pAtom; pAtom = pAtom->getParent())
1649  {
1650  auto pLayoutNode = dynamic_cast<LayoutNode*>(pAtom.get());
1651  if (pLayoutNode)
1652  {
1653  pParentLayoutNode = pLayoutNode;
1654  break;
1655  }
1656  }
1657  if (pParentLayoutNode)
1658  {
1659  nChildOrder = pParentLayoutNode->getChildOrder();
1660  }
1661  if (nChildOrder == XML_t)
1662  {
1663  std::reverse(rShape->getChildren().begin(), rShape->getChildren().end());
1664  }
1665 
1666  break;
1667  }
1668 
1669  case XML_pyra:
1670  {
1672  break;
1673  }
1674 
1675  case XML_snake:
1676  {
1677  SnakeAlg::layoutShapeChildren(*this, rShape, rConstraints);
1678  break;
1679  }
1680 
1681  case XML_sp:
1682  {
1683  // HACK: Handled one level higher. Or rather, planned to
1684  // HACK: text should appear only in tx node; we're assigning it earlier, so let's remove it here
1685  rShape->setTextBody(TextBodyPtr());
1686  break;
1687  }
1688 
1689  case XML_tx:
1690  {
1691  // adjust text alignment
1692 
1693  // Parse constraints, only self margins as a start.
1694  double fFontSize = 0;
1695  for (const auto& rConstr : rConstraints)
1696  {
1697  if (rConstr.mnRefType == XML_w)
1698  {
1699  if (!rConstr.msForName.isEmpty())
1700  continue;
1701 
1702  sal_Int32 nProperty = getPropertyFromConstraint(rConstr.mnType);
1703  if (!nProperty)
1704  continue;
1705 
1706  // PowerPoint takes size as points, but gives margin as MMs.
1707  double fFactor = convertPointToMms(rConstr.mfFactor);
1708 
1709  // DrawingML works in EMUs, UNO API works in MM100s.
1710  sal_Int32 nValue = o3tl::convert(rShape->getSize().Width * fFactor,
1712 
1713  rShape->getShapeProperties().setProperty(nProperty, nValue);
1714  }
1715  if (rConstr.mnType == XML_primFontSz)
1716  fFontSize = rConstr.mfValue;
1717  }
1718 
1719  TextBodyPtr pTextBody = rShape->getTextBody();
1720  if (!pTextBody || pTextBody->isEmpty())
1721  break;
1722 
1723  // adjust text size to fit shape
1724  if (fFontSize != 0)
1725  {
1726  for (auto& aParagraph : pTextBody->getParagraphs())
1727  for (auto& aRun : aParagraph->getRuns())
1728  if (!aRun->getTextCharacterProperties().moHeight.has())
1729  aRun->getTextCharacterProperties().moHeight = fFontSize * 100;
1730  }
1731 
1732  if (!HasCustomText(rShape, getLayoutNode()))
1733  {
1734  // No customized text properties: enable autofit.
1735  pTextBody->getTextProperties().maPropertyMap.setProperty(
1736  PROP_TextFitToSize, drawing::TextFitToSizeType_AUTOFIT);
1737  }
1738 
1739  // ECMA-376-1:2016 21.4.7.5 ST_AutoTextRotation (Auto Text Rotation)
1740  const sal_Int32 nautoTxRot = maMap.count(XML_autoTxRot) ? maMap.find(XML_autoTxRot)->second : XML_upr;
1741  sal_Int32 nShapeRot = rShape->getRotation();
1742  while (nShapeRot < 0)
1743  nShapeRot += 360 * PER_DEGREE;
1744  while (nShapeRot > 360 * PER_DEGREE)
1745  nShapeRot -= 360 * PER_DEGREE;
1746 
1747  switch(nautoTxRot)
1748  {
1749  case XML_upr:
1750  {
1751  int n90x = 0;
1752  if (nShapeRot >= 315 * PER_DEGREE)
1753  /* keep 0 */;
1754  else if (nShapeRot > 225 * PER_DEGREE)
1755  n90x = -3;
1756  else if (nShapeRot >= 135 * PER_DEGREE)
1757  n90x = -2;
1758  else if (nShapeRot > 45 * PER_DEGREE)
1759  n90x = -1;
1760  pTextBody->getTextProperties().moRotation = n90x * 90 * PER_DEGREE;
1761  }
1762  break;
1763  case XML_grav:
1764  {
1765  if (nShapeRot > (90 * PER_DEGREE) && nShapeRot < (270 * PER_DEGREE))
1766  pTextBody->getTextProperties().moRotation = -180 * PER_DEGREE;
1767  }
1768  break;
1769  case XML_none:
1770  break;
1771  }
1772 
1773  const sal_Int32 atxAnchorVert = maMap.count(XML_txAnchorVert) ? maMap.find(XML_txAnchorVert)->second : XML_mid;
1774 
1775  switch(atxAnchorVert)
1776  {
1777  case XML_t:
1778  pTextBody->getTextProperties().meVA = css::drawing::TextVerticalAdjust_TOP;
1779  break;
1780  case XML_b:
1781  pTextBody->getTextProperties().meVA = css::drawing::TextVerticalAdjust_BOTTOM;
1782  break;
1783  case XML_mid:
1784  // text centered vertically by default
1785  default:
1786  pTextBody->getTextProperties().meVA = css::drawing::TextVerticalAdjust_CENTER;
1787  break;
1788  }
1789 
1790  pTextBody->getTextProperties().maPropertyMap.setProperty(PROP_TextVerticalAdjust, pTextBody->getTextProperties().meVA);
1791 
1792  // normalize list level
1793  sal_Int32 nBaseLevel = pTextBody->getParagraphs().front()->getProperties().getLevel();
1794  for (auto & aParagraph : pTextBody->getParagraphs())
1795  {
1796  if (aParagraph->getProperties().getLevel() < nBaseLevel)
1797  nBaseLevel = aParagraph->getProperties().getLevel();
1798  }
1799 
1800  // Start bullets at:
1801  // 1 - top level
1802  // 2 - with children (default)
1803  int nStartBulletsAtLevel = 2;
1804  ParamMap::const_iterator aBulletLvl = maMap.find(XML_stBulletLvl);
1805  if (aBulletLvl != maMap.end())
1806  nStartBulletsAtLevel = aBulletLvl->second;
1807  nStartBulletsAtLevel--;
1808 
1809  bool isBulletList = false;
1810  for (auto & aParagraph : pTextBody->getParagraphs())
1811  {
1812  sal_Int32 nLevel = aParagraph->getProperties().getLevel() - nBaseLevel;
1813  aParagraph->getProperties().setLevel(nLevel);
1814  if (nLevel >= nStartBulletsAtLevel)
1815  {
1816  if (!aParagraph->getProperties().getParaLeftMargin().has_value())
1817  {
1818  sal_Int32 nLeftMargin
1819  = o3tl::convert(285750 * (nLevel - nStartBulletsAtLevel + 1),
1821  aParagraph->getProperties().getParaLeftMargin() = nLeftMargin;
1822  }
1823 
1824  if (!aParagraph->getProperties().getFirstLineIndentation().has_value())
1825  aParagraph->getProperties().getFirstLineIndentation()
1827 
1828  // It is not possible to change the bullet style for text.
1829  aParagraph->getProperties().getBulletList().setBulletChar(u"•");
1830  aParagraph->getProperties().getBulletList().setSuffixNone();
1831  isBulletList = true;
1832  }
1833  }
1834 
1835  // explicit alignment
1836  ParamMap::const_iterator aDir = maMap.find(XML_parTxLTRAlign);
1837  // TODO: XML_parTxRTLAlign
1838  if (aDir != maMap.end())
1839  {
1840  css::style::ParagraphAdjust aAlignment = GetParaAdjust(aDir->second);
1841  for (auto & aParagraph : pTextBody->getParagraphs())
1842  aParagraph->getProperties().setParaAdjust(aAlignment);
1843  }
1844  else if (!isBulletList)
1845  {
1846  // if not list use default alignment - centered
1847  for (auto & aParagraph : pTextBody->getParagraphs())
1848  aParagraph->getProperties().setParaAdjust(css::style::ParagraphAdjust::ParagraphAdjust_CENTER);
1849  }
1850  break;
1851  }
1852 
1853  default:
1854  break;
1855  }
1856 
1857  SAL_INFO(
1858  "oox.drawingml",
1859  "Layouting shape " << rShape->getInternalName() << ", alg type: " << mnType << ", ("
1860  << rShape->getPosition().X << "," << rShape->getPosition().Y << ","
1861  << rShape->getSize().Width << "," << rShape->getSize().Height << ")");
1862 }
1863 
1865 {
1866  rVisitor.visit(*this);
1867 }
1868 
1869 bool LayoutNode::setupShape( const ShapePtr& rShape, const dgm::Point* pPresNode, sal_Int32 nCurrIdx ) const
1870 {
1871  SAL_INFO(
1872  "oox.drawingml",
1873  "Filling content from layout node named \"" << msName
1874  << "\", modelId \"" << pPresNode->msModelId << "\"");
1875 
1876  // have the presentation node - now, need the actual data node:
1877  const DiagramData::StringMap::const_iterator aNodeName = mrDgm.getData()->getPresOfNameMap().find(
1878  pPresNode->msModelId);
1879  if( aNodeName != mrDgm.getData()->getPresOfNameMap().end() )
1880  {
1881  // Calculate the depth of what is effectively the topmost element.
1882  sal_Int32 nMinDepth = std::numeric_limits<sal_Int32>::max();
1883  for (const auto& rPair : aNodeName->second)
1884  {
1885  if (rPair.second.mnDepth < nMinDepth)
1886  nMinDepth = rPair.second.mnDepth;
1887  }
1888 
1889  for (const auto& rPair : aNodeName->second)
1890  {
1891  const DiagramData::SourceIdAndDepth& rItem = rPair.second;
1892  DiagramData::PointNameMap& rMap = mrDgm.getData()->getPointNameMap();
1893  // pPresNode is the presentation node of the aDataNode2 data node.
1894  DiagramData::PointNameMap::const_iterator aDataNode2 = rMap.find(rItem.msSourceId);
1895  if (aDataNode2 == rMap.end())
1896  {
1897  //busted, skip it
1898  continue;
1899  }
1900 
1901  if (!aDataNode2->second->mpShape)
1902  {
1903  //busted, skip it
1904  continue;
1905  }
1906 
1907  rShape->setDataNodeType(aDataNode2->second->mnType);
1908 
1909  if (rItem.mnDepth == 0)
1910  {
1911  // grab shape attr from topmost element(s)
1912  rShape->getShapeProperties() = aDataNode2->second->mpShape->getShapeProperties();
1913  rShape->getLineProperties() = aDataNode2->second->mpShape->getLineProperties();
1914  rShape->getFillProperties() = aDataNode2->second->mpShape->getFillProperties();
1915  rShape->getCustomShapeProperties() = aDataNode2->second->mpShape->getCustomShapeProperties();
1916  rShape->setMasterTextListStyle( aDataNode2->second->mpShape->getMasterTextListStyle() );
1917 
1918  SAL_INFO(
1919  "oox.drawingml",
1920  "Custom shape with preset type "
1921  << (rShape->getCustomShapeProperties()
1922  ->getShapePresetType())
1923  << " added for layout node named \"" << msName
1924  << "\"");
1925  }
1926  else if (rItem.mnDepth == nMinDepth)
1927  {
1928  // If no real topmost element, then take properties from the one that's the closest
1929  // to topmost.
1930  rShape->getLineProperties() = aDataNode2->second->mpShape->getLineProperties();
1931  rShape->getFillProperties() = aDataNode2->second->mpShape->getFillProperties();
1932  }
1933 
1934  // append text with right outline level
1935  if( aDataNode2->second->mpShape->getTextBody() &&
1936  !aDataNode2->second->mpShape->getTextBody()->getParagraphs().empty() &&
1937  !aDataNode2->second->mpShape->getTextBody()->getParagraphs().front()->getRuns().empty() )
1938  {
1939  TextBodyPtr pTextBody=rShape->getTextBody();
1940  if( !pTextBody )
1941  {
1942  pTextBody = std::make_shared<TextBody>();
1943 
1944  // also copy text attrs
1945  pTextBody->getTextListStyle() =
1946  aDataNode2->second->mpShape->getTextBody()->getTextListStyle();
1947  pTextBody->getTextProperties() =
1948  aDataNode2->second->mpShape->getTextBody()->getTextProperties();
1949 
1950  rShape->setTextBody(pTextBody);
1951  }
1952 
1953  const TextParagraphVector& rSourceParagraphs
1954  = aDataNode2->second->mpShape->getTextBody()->getParagraphs();
1955  for (const auto& pSourceParagraph : rSourceParagraphs)
1956  {
1957  TextParagraph& rPara = pTextBody->addParagraph();
1958  if (rItem.mnDepth != -1)
1959  rPara.getProperties().setLevel(rItem.mnDepth);
1960 
1961  for (const auto& pRun : pSourceParagraph->getRuns())
1962  rPara.addRun(pRun);
1963  const TextBodyPtr& rBody = aDataNode2->second->mpShape->getTextBody();
1964  rPara.getProperties().apply(rBody->getParagraphs().front()->getProperties());
1965  }
1966  }
1967  }
1968  }
1969  else
1970  {
1971  SAL_INFO(
1972  "oox.drawingml",
1973  "ShapeCreationVisitor::visit: no data node name found while"
1974  " processing shape type "
1975  << rShape->getCustomShapeProperties()->getShapePresetType()
1976  << " for layout node named \"" << msName << "\"");
1977  if (pPresNode->mpShape)
1978  rShape->getFillProperties().assignUsed(pPresNode->mpShape->getFillProperties());
1979  }
1980 
1981  // TODO(Q1): apply styling & coloring - take presentation
1982  // point's presStyleLbl for both style & color
1983  // if not found use layout node's styleLbl
1984  // however, docs are a bit unclear on this
1985  OUString aStyleLabel = pPresNode->msPresentationLayoutStyleLabel;
1986  if (aStyleLabel.isEmpty())
1987  aStyleLabel = msStyleLabel;
1988  if( !aStyleLabel.isEmpty() )
1989  {
1990  const DiagramQStyleMap::const_iterator aStyle = mrDgm.getStyles().find(aStyleLabel);
1991  if( aStyle != mrDgm.getStyles().end() )
1992  {
1993  const DiagramStyle& rStyle = aStyle->second;
1994  rShape->getShapeStyleRefs()[XML_fillRef] = rStyle.maFillStyle;
1995  rShape->getShapeStyleRefs()[XML_lnRef] = rStyle.maLineStyle;
1996  rShape->getShapeStyleRefs()[XML_effectRef] = rStyle.maEffectStyle;
1997  rShape->getShapeStyleRefs()[XML_fontRef] = rStyle.maTextStyle;
1998  }
1999  else
2000  {
2001  SAL_WARN("oox.drawingml", "Style " << aStyleLabel << " not found");
2002  }
2003 
2004  const DiagramColorMap::const_iterator aColor = mrDgm.getColors().find(aStyleLabel);
2005  if( aColor != mrDgm.getColors().end() )
2006  {
2007  // Take the nth color from the color list in case we are the nth shape in a
2008  // <dgm:forEach> loop.
2009  const DiagramColor& rColor=aColor->second;
2010  if( !rColor.maFillColors.empty() )
2011  rShape->getShapeStyleRefs()[XML_fillRef].maPhClr = DiagramColor::getColorByIndex(rColor.maFillColors, nCurrIdx);
2012  if( !rColor.maLineColors.empty() )
2013  rShape->getShapeStyleRefs()[XML_lnRef].maPhClr = DiagramColor::getColorByIndex(rColor.maLineColors, nCurrIdx);
2014  if( !rColor.maEffectColors.empty() )
2015  rShape->getShapeStyleRefs()[XML_effectRef].maPhClr = DiagramColor::getColorByIndex(rColor.maEffectColors, nCurrIdx);
2016  if( !rColor.maTextFillColors.empty() )
2017  rShape->getShapeStyleRefs()[XML_fontRef].maPhClr = DiagramColor::getColorByIndex(rColor.maTextFillColors, nCurrIdx);
2018  }
2019  }
2020 
2021  // even if no data node found, successful anyway. it's
2022  // contained at the layoutnode
2023  return true;
2024 }
2025 
2027 {
2028  for (LayoutAtomPtr pAtom = getParent(); pAtom; pAtom = pAtom->getParent())
2029  {
2030  auto pLayoutNode = dynamic_cast<LayoutNode*>(pAtom.get());
2031  if (pLayoutNode)
2032  return pLayoutNode;
2033  }
2034 
2035  return nullptr;
2036 }
2037 
2039 {
2040  rVisitor.visit(*this);
2041 }
2042 
2043 }
2044 
2045 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Constraints allow you to specify an ideal (or starting point) size for each shape.
OptValue< bool > getBool(sal_Int32 nAttrToken) const
Returns the boolean value of the specified attribute.
virtual void visit(ConstraintAtom &rAtom)=0
const sal_Int32 PER_DEGREE
OptValue< sal_Int32 > getInteger(sal_Int32 nAttrToken) const
Returns the 32-bit signed integer value of the specified attribute (decimal).
std::map< OUString, ShapePairs > NamedShapePairs
tools::Long const nLeftMargin
static const oox::drawingml::Color & getColorByIndex(const std::vector< oox::drawingml::Color > &rColors, sal_Int32 nIndex)
Definition: diagram.cxx:459
const ParamMap & getMap() const
std::map< const dgm::Point *, ShapePtr > PresPointShapeMap
constexpr Point convert(const Point &rPoint, o3tl::Length eFrom, o3tl::Length eTo)
std::vector< oox::drawingml::Color > maEffectColors
abstract Atom for the layout
void loadFromXAttr(const css::uno::Reference< css::xml::sax::XFastAttributeList > &xAttributes)
static void layoutShapeChildren(const ShapePtr &rShape)
static bool compareResult(sal_Int32 nOperator, sal_Int32 nFirst, sal_Int32 nSecond)
const LayoutNode * getParentLayoutNode() const
PropertiesInfo aProperties
std::map< OUString, LayoutProperty > LayoutPropertyMap
const std::vector< LayoutAtomPtr > & getChildren() const
int nCount
const Type & get() const
Definition: helper.hxx:185
const DiagramLayoutPtr & getLayout() const
OUString msPresentationAssociationId
Definition: datamodel.hxx:106
std::map< OUString, dgm::Point * > PointNameMap
Definition: datamodel.hxx:148
ConditionAtom(LayoutNode &rLayoutNode, bool isElse, const css::uno::Reference< css::xml::sax::XFastAttributeList > &xAttributes)
virtual void accept(LayoutAtomVisitor &) override
visitor acceptance
virtual void accept(LayoutAtomVisitor &) override
visitor acceptance
virtual void accept(LayoutAtomVisitor &) override
visitor acceptance
double convertPointToMms(double fValue)
Converts the passed double value from points to mm.
virtual void accept(LayoutAtomVisitor &) override
visitor acceptance
std::map< OUString, LayoutAtomPtr > LayoutAtomMap
void apply(const TextParagraphProperties &rSourceProps)
bool setupShape(const ShapePtr &rShape, const dgm::Point *pPresNode, sal_Int32 nCurrIdx) const
std::map< sal_Int32, sal_Int32 > LayoutProperty
float u
static void layoutShapeChildren(const AlgAtom &rAlg, const ShapePtr &rShape, const std::vector< Constraint > &rConstraints)
virtual void accept(LayoutAtomVisitor &) override
visitor acceptance
bool getDecision(const dgm::Point *pPresPoint) const
constexpr double deg2rad(double v)
sal_Int32 getConnectorType()
Determines the connector shape type for conn algorithm.
std::shared_ptr< TextBody > TextBodyPtr
tuple index
ForEachAtom(LayoutNode &rLayoutNode, const css::uno::Reference< css::xml::sax::XFastAttributeList > &xAttributes)
Provides access to attribute values of an element.
void loadFromXAttr(const css::uno::Reference< css::xml::sax::XFastAttributeList > &xAttributes)
const DiagramDataPtr & getData() const
const sal_uInt16 idx[]
std::vector< sal_Int32 > maAxis
std::shared_ptr< LayoutAtom > LayoutAtomPtr
sal_Int32 getNodeCount(const dgm::Point *pPresPoint) const
virtual void accept(LayoutAtomVisitor &) override
visitor acceptance
ParagraphAdjust GetParaAdjust(sal_Int32 nAlign)
converts a paragraph align to a ParaAdjust
void parseRule(std::vector< Rule > &rRules) const
void addRun(const TextRunPtr &pRun)
#define SAL_INFO(area, stream)
sal_Int32 getVerticalShapesCount(const ShapePtr &rShape)
if(aStr!=aBuf) UpdateName_Impl(m_xFollowLb.get()
std::vector< oox::drawingml::Color > maFillColors
std::vector< sal_Int32 > getTokenList(sal_Int32 nAttrToken) const
std::map< sal_Int32, sal_Int32 > ParamMap
static bool inferFromLayoutProperty(const LayoutProperty &rMap, sal_Int32 nRefType, sal_Int32 &rValue)
Decides if a certain reference type (e.g.
std::vector< Connection > Connections
Definition: datamodel.hxx:61
LayoutAtomPtr getParent() const
#define SAL_WARN(area, stream)
std::shared_ptr< Shape > ShapePtr
void layoutShape(const ShapePtr &rShape, const std::vector< Constraint > &rConstraints, const std::vector< Rule > &rRules)
TextParagraphProperties & getProperties()
static void applyConstraintToLayout(const Constraint &rConstraint, LayoutPropertyMap &rProperties)
Apply rConstraint to the rProperties shared layout state.
std::map< OUString, std::map< sal_Int32, SourceIdAndDepth > > StringMap
Tracks connections: destination id -> {destination order, details} map.
Definition: datamodel.hxx:159
void parseConstraint(std::vector< Constraint > &rConstraints, bool bRequireForName) const
bool has() const
Definition: helper.hxx:181
virtual void accept(LayoutAtomVisitor &) override
visitor acceptance
std::vector< oox::drawingml::Color > maTextFillColors
OUString msPresentationLayoutStyleLabel
Definition: datamodel.hxx:108
virtual void accept(LayoutAtomVisitor &) override
visitor acceptance
static void layoutShapeChildren(AlgAtom &rAlg, const ShapePtr &rShape, const std::vector< Constraint > &rConstraints)
sal_Int16 nValue
std::vector< oox::drawingml::Color > maLineColors
OptValue< sal_Int32 > moHierarchyBranch
Definition: datamodel.hxx:116