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 <sdr/primitive2d/sdrmeasureprimitive2d.hxx>
21 #include <svx/sdr/primitive2d/sdrdecompositiontools.hxx>
22 #include <basegfx/matrix/b2dhommatrix.hxx>
23 #include <sdr/primitive2d/sdrtextprimitive2d.hxx>
24 #include <svx/sdr/attribute/sdrtextattribute.hxx>
25 #include <basegfx/polygon/b2dpolypolygontools.hxx>
26 #include <basegfx/utils/canvastools.hxx>
27 #include <drawinglayer/primitive2d/groupprimitive2d.hxx>
28 #include <rtl/ref.hxx>
29 #include <svx/sdr/primitive2d/svx_primitivetypes2d.hxx>
30 #include <basegfx/matrix/b2dhommatrixtools.hxx>
31 #include <drawinglayer/primitive2d/hiddengeometryprimitive2d.hxx>
32 
33 
34 using namespace com::sun::star;
35 
36 
37 namespace drawinglayer
38 {
39     namespace primitive2d
40     {
impCreatePart(const attribute::SdrLineAttribute & rLineAttribute,const basegfx::B2DHomMatrix & rObjectMatrix,const basegfx::B2DPoint & rStart,const basegfx::B2DPoint & rEnd,bool bLeftActive,bool bRightActive) const41         Primitive2DReference SdrMeasurePrimitive2D::impCreatePart(
42             const attribute::SdrLineAttribute& rLineAttribute,
43             const basegfx::B2DHomMatrix& rObjectMatrix,
44             const basegfx::B2DPoint& rStart,
45             const basegfx::B2DPoint& rEnd,
46             bool bLeftActive,
47             bool bRightActive) const
48         {
49             const attribute::SdrLineStartEndAttribute& rLineStartEnd = getSdrLSTAttribute().getLineStartEnd();
50             basegfx::B2DPolygon aPolygon;
51 
52             aPolygon.append(rStart);
53             aPolygon.append(rEnd);
54             aPolygon.transform(rObjectMatrix);
55 
56             if(rLineStartEnd.isDefault() || (!bLeftActive && !bRightActive))
57             {
58                 return createPolygonLinePrimitive(
59                     aPolygon,
60                     rLineAttribute,
61                     attribute::SdrLineStartEndAttribute());
62             }
63 
64             if(bLeftActive && bRightActive)
65             {
66                 return createPolygonLinePrimitive(
67                     aPolygon,
68                     rLineAttribute,
69                     rLineStartEnd);
70             }
71 
72             const basegfx::B2DPolyPolygon aEmpty;
73             const attribute::SdrLineStartEndAttribute aLineStartEnd(
74                 bLeftActive ? rLineStartEnd.getStartPolyPolygon() : aEmpty, bRightActive ? rLineStartEnd.getEndPolyPolygon() : aEmpty,
75                 bLeftActive ? rLineStartEnd.getStartWidth() : 0.0, bRightActive ? rLineStartEnd.getEndWidth() : 0.0,
76                 bLeftActive && rLineStartEnd.isStartActive(), bRightActive && rLineStartEnd.isEndActive(),
77                 bLeftActive && rLineStartEnd.isStartCentered(), bRightActive && rLineStartEnd.isEndCentered());
78 
79             return createPolygonLinePrimitive(
80                 aPolygon,
81                 rLineAttribute,
82                 aLineStartEnd);
83         }
84 
create2DDecomposition(Primitive2DContainer & rContainer,const geometry::ViewInformation2D & aViewInformation) const85         void SdrMeasurePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& aViewInformation) const
86         {
87             Primitive2DContainer aRetval;
88             rtl::Reference<SdrBlockTextPrimitive2D> xBlockText;
89             basegfx::B2DRange aTextRange;
90             const basegfx::B2DVector aLine(getEnd() - getStart());
91             const double fDistance(aLine.getLength());
92             const double fAngle(atan2(aLine.getY(), aLine.getX()));
93             bool bAutoUpsideDown(false);
94             const attribute::SdrTextAttribute rTextAttribute = getSdrLSTAttribute().getText();
95             const basegfx::B2DHomMatrix aObjectMatrix(
96                 basegfx::utils::createShearXRotateTranslateB2DHomMatrix(0.0, fAngle, getStart()));
97 
98             // preapare text, but do not add yet; it needs to be aligned to
99             // the line geometry
100             if(!rTextAttribute.isDefault())
101             {
102                 basegfx::B2DHomMatrix aTextMatrix;
103                 double fTestAngle(fAngle);
104 
105                 if(getTextRotation())
106                 {
107                     aTextMatrix.rotate(-F_PI2);
108                     fTestAngle -= (F_PI2);
109 
110                     if(getTextAutoAngle() && fTestAngle < -F_PI)
111                     {
112                         fTestAngle += F_2PI;
113                     }
114                 }
115 
116                 if(getTextAutoAngle())
117                 {
118                     if(fTestAngle > (F_PI / 4.0) || fTestAngle < (-F_PI * (3.0 / 4.0)))
119                     {
120                         bAutoUpsideDown = true;
121                     }
122                 }
123 
124                 // create primitive and get text range
125                 xBlockText = new SdrBlockTextPrimitive2D(
126                     &rTextAttribute.getSdrText(),
127                     rTextAttribute.getOutlinerParaObject(),
128                     aTextMatrix,
129                     SDRTEXTHORZADJUST_CENTER,
130                     SDRTEXTVERTADJUST_CENTER,
131                     rTextAttribute.isScroll(),
132                     false,
133                     false,
134                     false,
135                     false);
136 
137                 aTextRange = xBlockText->getB2DRange(aViewInformation);
138             }
139 
140             // prepare line attribute and result
141             double fTextX;
142             double fTextY;
143             {
144                 const attribute::SdrLineAttribute rLineAttribute(getSdrLSTAttribute().getLine());
145                 bool bArrowsOutside(false);
146                 bool bMainLineSplitted(false);
147                 const attribute::SdrLineStartEndAttribute& rLineStartEnd = getSdrLSTAttribute().getLineStartEnd();
148                 double fStartArrowW(0.0);
149                 double fStartArrowH(0.0);
150                 double fEndArrowW(0.0);
151                 double fEndArrowH(0.0);
152 
153                 if(!rLineStartEnd.isDefault())
154                 {
155                     if(rLineStartEnd.isStartActive())
156                     {
157                         const basegfx::B2DRange aArrowRange(basegfx::utils::getRange(rLineStartEnd.getStartPolyPolygon()));
158                         fStartArrowW = rLineStartEnd.getStartWidth();
159                         fStartArrowH = aArrowRange.getHeight() * fStartArrowW / aArrowRange.getWidth();
160 
161                         if(rLineStartEnd.isStartCentered())
162                         {
163                             fStartArrowH *= 0.5;
164                         }
165                     }
166 
167                     if(rLineStartEnd.isEndActive())
168                     {
169                         const basegfx::B2DRange aArrowRange(basegfx::utils::getRange(rLineStartEnd.getEndPolyPolygon()));
170                         fEndArrowW = rLineStartEnd.getEndWidth();
171                         fEndArrowH = aArrowRange.getHeight() * fEndArrowW / aArrowRange.getWidth();
172 
173                         if(rLineStartEnd.isEndCentered())
174                         {
175                             fEndArrowH *= 0.5;
176                         }
177                     }
178                 }
179 
180                 const double fSpaceNeededByArrows(fStartArrowH + fEndArrowH + ((fStartArrowW + fEndArrowW) * 0.5));
181                 const double fArrowsOutsideLen((fStartArrowH + fEndArrowH + fStartArrowW + fEndArrowW) * 0.5);
182                 const double fHalfLineWidth(rLineAttribute.getWidth() * 0.5);
183 
184                 if(fSpaceNeededByArrows > fDistance)
185                 {
186                     bArrowsOutside = true;
187                 }
188 
189                 MeasureTextPosition eHorizontal(getHorizontal());
190                 MeasureTextPosition eVertical(getVertical());
191 
192                 if(MEASURETEXTPOSITION_AUTOMATIC == eVertical)
193                 {
194                     eVertical = MEASURETEXTPOSITION_NEGATIVE;
195                 }
196 
197                 if(MEASURETEXTPOSITION_CENTERED == eVertical)
198                 {
199                     bMainLineSplitted = true;
200                 }
201 
202                 if(MEASURETEXTPOSITION_AUTOMATIC == eHorizontal)
203                 {
204                     if(aTextRange.getWidth() > fDistance)
205                     {
206                         eHorizontal = MEASURETEXTPOSITION_NEGATIVE;
207                     }
208                     else
209                     {
210                         eHorizontal = MEASURETEXTPOSITION_CENTERED;
211                     }
212 
213                     if(bMainLineSplitted)
214                     {
215                         if(aTextRange.getWidth() + fSpaceNeededByArrows > fDistance)
216                         {
217                             bArrowsOutside = true;
218                         }
219                     }
220                     else
221                     {
222                         const double fSmallArrowNeed(fStartArrowH + fEndArrowH + ((fStartArrowW + fEndArrowW) * 0.125));
223 
224                         if(aTextRange.getWidth() + fSmallArrowNeed > fDistance)
225                         {
226                             bArrowsOutside = true;
227                         }
228                     }
229                 }
230 
231                 if(MEASURETEXTPOSITION_CENTERED != eHorizontal)
232                 {
233                     bArrowsOutside = true;
234                 }
235 
236                 // switch text above/below?
237                 if(getBelow() || (bAutoUpsideDown && !getTextRotation()))
238                 {
239                     if(MEASURETEXTPOSITION_NEGATIVE == eVertical)
240                     {
241                         eVertical = MEASURETEXTPOSITION_POSITIVE;
242                     }
243                     else if(MEASURETEXTPOSITION_POSITIVE == eVertical)
244                     {
245                         eVertical = MEASURETEXTPOSITION_NEGATIVE;
246                     }
247                 }
248 
249                 const double fMainLineOffset(getBelow() ? getDistance() : -getDistance());
250                 const basegfx::B2DPoint aMainLeft(0.0, fMainLineOffset);
251                 const basegfx::B2DPoint aMainRight(fDistance, fMainLineOffset);
252 
253                 // main line
254                 if(bArrowsOutside)
255                 {
256                     double fLenLeft(fArrowsOutsideLen);
257                     double fLenRight(fArrowsOutsideLen);
258 
259                     if(!bMainLineSplitted)
260                     {
261                         if(MEASURETEXTPOSITION_NEGATIVE == eHorizontal)
262                         {
263                             fLenLeft = fStartArrowH + aTextRange.getWidth();
264                         }
265                         else if(MEASURETEXTPOSITION_POSITIVE == eHorizontal)
266                         {
267                             fLenRight = fEndArrowH + aTextRange.getWidth();
268                         }
269                     }
270 
271                     const basegfx::B2DPoint aMainLeftLeft(aMainLeft.getX() - fLenLeft, aMainLeft.getY());
272                     const basegfx::B2DPoint aMainRightRight(aMainRight.getX() + fLenRight, aMainRight.getY());
273 
274                     aRetval.push_back(impCreatePart(rLineAttribute, aObjectMatrix, aMainLeftLeft, aMainLeft, false, true));
275                     aRetval.push_back(impCreatePart(rLineAttribute, aObjectMatrix, aMainRight, aMainRightRight, true, false));
276 
277                     if(!bMainLineSplitted || MEASURETEXTPOSITION_CENTERED != eHorizontal)
278                     {
279                         aRetval.push_back(impCreatePart(rLineAttribute, aObjectMatrix, aMainLeft, aMainRight, false, false));
280                     }
281                 }
282                 else
283                 {
284                     if(bMainLineSplitted)
285                     {
286                         const double fHalfLength((fDistance - (aTextRange.getWidth() + (fStartArrowH + fEndArrowH) * 0.25)) * 0.5);
287                         const basegfx::B2DPoint aMainInnerLeft(aMainLeft.getX() + fHalfLength, aMainLeft.getY());
288                         const basegfx::B2DPoint aMainInnerRight(aMainRight.getX() - fHalfLength, aMainRight.getY());
289 
290                         aRetval.push_back(impCreatePart(rLineAttribute, aObjectMatrix, aMainLeft, aMainInnerLeft, true, false));
291                         aRetval.push_back(impCreatePart(rLineAttribute, aObjectMatrix, aMainInnerRight, aMainRight, false, true));
292                     }
293                     else
294                     {
295                         aRetval.push_back(impCreatePart(rLineAttribute, aObjectMatrix, aMainLeft, aMainRight, true, true));
296                     }
297                 }
298 
299                 // left/right help line value preparation
300                 const double fTopEdge(getBelow() ? getUpper() + getDistance() : -getUpper() - getDistance());
301                 const double fBottomLeft(getBelow() ? getLower() - getLeftDelta() : getLeftDelta() - getLower());
302                 const double fBottomRight(getBelow() ? getLower() - getRightDelta() : getRightDelta() - getLower());
303 
304                 // left help line
305                 const basegfx::B2DPoint aLeftUp(0.0, fTopEdge);
306                 const basegfx::B2DPoint aLeftDown(0.0, fBottomLeft);
307 
308                 aRetval.push_back(impCreatePart(rLineAttribute, aObjectMatrix, aLeftDown, aLeftUp, false, false));
309 
310                 // right help line
311                 const basegfx::B2DPoint aRightUp(fDistance, fTopEdge);
312                 const basegfx::B2DPoint aRightDown(fDistance, fBottomRight);
313 
314                 aRetval.push_back(impCreatePart(rLineAttribute, aObjectMatrix, aRightDown, aRightUp, false, false));
315 
316                 // text horizontal position
317                 if(MEASURETEXTPOSITION_NEGATIVE == eHorizontal)
318                 {
319                     // left
320                     const double fSmall(fArrowsOutsideLen * 0.18);
321                     fTextX = aMainLeft.getX() - (fStartArrowH + aTextRange.getWidth() + fSmall + fHalfLineWidth);
322 
323                     if(bMainLineSplitted)
324                     {
325                         fTextX -= (fArrowsOutsideLen - fStartArrowH);
326                     }
327 
328                     if(!rTextAttribute.isDefault())
329                     {
330                         fTextX -= rTextAttribute.getTextRightDistance();
331                     }
332                 }
333                 else if(MEASURETEXTPOSITION_POSITIVE == eHorizontal)
334                 {
335                     // right
336                     const double fSmall(fArrowsOutsideLen * 0.18);
337                     fTextX = aMainRight.getX() + (fEndArrowH + fSmall + fHalfLineWidth);
338 
339                     if(bMainLineSplitted)
340                     {
341                         fTextX += (fArrowsOutsideLen - fEndArrowH);
342                     }
343 
344                     if(!rTextAttribute.isDefault())
345                     {
346                         fTextX += rTextAttribute.getTextLeftDistance();
347                     }
348                 }
349                 else // MEASURETEXTPOSITION_CENTERED
350                 {
351                     // centered
352                     fTextX = aMainLeft.getX() + ((fDistance - aTextRange.getWidth()) * 0.5);
353 
354                     if(!rTextAttribute.isDefault())
355                     {
356                         fTextX += (rTextAttribute.getTextLeftDistance() - rTextAttribute.getTextRightDistance()) / 2L;
357                     }
358                 }
359 
360                 // text vertical position
361                 if(MEASURETEXTPOSITION_NEGATIVE == eVertical)
362                 {
363                     // top
364                     const double fSmall(fArrowsOutsideLen * 0.10);
365                     fTextY = aMainLeft.getY() - (aTextRange.getHeight() + fSmall + fHalfLineWidth);
366 
367                     if(!rTextAttribute.isDefault())
368                     {
369                         fTextY -= rTextAttribute.getTextLowerDistance();
370                     }
371                 }
372                 else if(MEASURETEXTPOSITION_POSITIVE == eVertical)
373                 {
374                     // bottom
375                     const double fSmall(fArrowsOutsideLen * 0.10);
376                     fTextY = aMainLeft.getY() + (fSmall + fHalfLineWidth);
377 
378                     if(!rTextAttribute.isDefault())
379                     {
380                         fTextY += rTextAttribute.getTextUpperDistance();
381                     }
382                 }
383                 else // MEASURETEXTPOSITION_CENTERED
384                 {
385                     // centered
386                     fTextY = aMainLeft.getY() - (aTextRange.getHeight() * 0.5);
387 
388                     if(!rTextAttribute.isDefault())
389                     {
390                         fTextY += (rTextAttribute.getTextUpperDistance() - rTextAttribute.getTextLowerDistance()) / 2L;
391                     }
392                 }
393             }
394 
395             if(getSdrLSTAttribute().getLine().isDefault())
396             {
397                 // embed line geometry to invisible (100% transparent) line group for HitTest
398                 const Primitive2DReference xHiddenLines(new HiddenGeometryPrimitive2D(aRetval));
399 
400                 aRetval = Primitive2DContainer { xHiddenLines };
401             }
402 
403             if(xBlockText.is())
404             {
405                 // create transformation to text primitive end position
406                 basegfx::B2DHomMatrix aChange;
407 
408                 // handle auto text rotation
409                 if(bAutoUpsideDown)
410                 {
411                     aChange.rotate(F_PI);
412                 }
413 
414                 // move from aTextRange.TopLeft to fTextX, fTextY
415                 aChange.translate(fTextX - aTextRange.getMinX(), fTextY - aTextRange.getMinY());
416 
417                 // apply object matrix
418                 aChange *= aObjectMatrix;
419 
420                 // apply to existing text primitive
421                 std::unique_ptr<SdrTextPrimitive2D> pNewBlockText = xBlockText->createTransformedClone(aChange);
422                 OSL_ENSURE(pNewBlockText, "SdrMeasurePrimitive2D::create2DDecomposition: Could not create transformed clone of text primitive (!)");
423                 xBlockText.clear();
424 
425                 // add to local primitives
426                 aRetval.push_back(Primitive2DReference(pNewBlockText.release()));
427             }
428 
429             // add shadow
430             if(!getSdrLSTAttribute().getShadow().isDefault())
431             {
432                 aRetval = createEmbeddedShadowPrimitive(
433                     aRetval,
434                     getSdrLSTAttribute().getShadow());
435             }
436 
437             rContainer.insert(rContainer.end(), aRetval.begin(), aRetval.end());
438         }
439 
SdrMeasurePrimitive2D(const attribute::SdrLineShadowTextAttribute & rSdrLSTAttribute,const basegfx::B2DPoint & rStart,const basegfx::B2DPoint & rEnd,MeasureTextPosition eHorizontal,MeasureTextPosition eVertical,double fDistance,double fUpper,double fLower,double fLeftDelta,double fRightDelta,bool bBelow,bool bTextRotation,bool bTextAutoAngle)440         SdrMeasurePrimitive2D::SdrMeasurePrimitive2D(
441             const attribute::SdrLineShadowTextAttribute& rSdrLSTAttribute,
442             const basegfx::B2DPoint& rStart,
443             const basegfx::B2DPoint& rEnd,
444             MeasureTextPosition eHorizontal,
445             MeasureTextPosition eVertical,
446             double fDistance,
447             double fUpper,
448             double fLower,
449             double fLeftDelta,
450             double fRightDelta,
451             bool bBelow,
452             bool bTextRotation,
453             bool bTextAutoAngle)
454         :   BufferedDecompositionPrimitive2D(),
455             maSdrLSTAttribute(rSdrLSTAttribute),
456             maStart(rStart),
457             maEnd(rEnd),
458             meHorizontal(eHorizontal),
459             meVertical(eVertical),
460             mfDistance(fDistance),
461             mfUpper(fUpper),
462             mfLower(fLower),
463             mfLeftDelta(fLeftDelta),
464             mfRightDelta(fRightDelta),
465             mbBelow(bBelow),
466             mbTextRotation(bTextRotation),
467             mbTextAutoAngle(bTextAutoAngle)
468         {
469         }
470 
operator ==(const BasePrimitive2D & rPrimitive) const471         bool SdrMeasurePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
472         {
473             if(BufferedDecompositionPrimitive2D::operator==(rPrimitive))
474             {
475                 const SdrMeasurePrimitive2D& rCompare = static_cast<const SdrMeasurePrimitive2D&>(rPrimitive);
476 
477                 return (getStart() == rCompare.getStart()
478                     && getEnd() == rCompare.getEnd()
479                     && getHorizontal() == rCompare.getHorizontal()
480                     && getVertical() == rCompare.getVertical()
481                     && getDistance() == rCompare.getDistance()
482                     && getUpper() == rCompare.getUpper()
483                     && getLower() == rCompare.getLower()
484                     && getLeftDelta() == rCompare.getLeftDelta()
485                     && getRightDelta() == rCompare.getRightDelta()
486                     && getBelow() == rCompare.getBelow()
487                     && getTextRotation() == rCompare.getTextRotation()
488                     && getTextAutoAngle() == rCompare.getTextAutoAngle()
489                     && getSdrLSTAttribute() == rCompare.getSdrLSTAttribute());
490             }
491 
492             return false;
493         }
494 
495         // provide unique ID
496         ImplPrimitive2DIDBlock(SdrMeasurePrimitive2D, PRIMITIVE2D_ID_SDRMEASUREPRIMITIVE2D)
497 
498     } // end of namespace primitive2d
499 } // end of namespace drawinglayer
500 
501 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
502