Coverage Report

Created: 2026-09-28 10:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svx/source/sdr/contact/viewcontactofsdrobjcustomshape.cxx
Line
Count
Source
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/contact/viewcontactofsdrobjcustomshape.hxx>
21
#include <svx/svdoashp.hxx>
22
#include <svx/sdooitm.hxx>
23
#include <sdr/primitive2d/sdrattributecreator.hxx>
24
#include <sdr/primitive2d/sdrcustomshapeprimitive2d.hxx>
25
#include <basegfx/matrix/b2dhommatrixtools.hxx>
26
#include <vcl/canvastools.hxx>
27
28
29
namespace sdr::contact
30
{
31
        ViewContactOfSdrObjCustomShape::ViewContactOfSdrObjCustomShape(SdrObjCustomShape& rCustomShape)
32
148k
        :   ViewContactOfTextObj(rCustomShape)
33
148k
        {
34
148k
        }
35
36
        ViewContactOfSdrObjCustomShape::~ViewContactOfSdrObjCustomShape()
37
148k
        {
38
148k
        }
39
40
        basegfx::B2DRange ViewContactOfSdrObjCustomShape::getCorrectedTextBoundRect() const
41
655
        {
42
655
            const tools::Rectangle aObjectBound(GetCustomShapeObj().GetGeoRect());
43
655
            tools::Rectangle aTextBound(aObjectBound);
44
655
            GetCustomShapeObj().GetTextBounds(aTextBound);
45
655
            basegfx::B2DRange aTextRange = vcl::unotools::b2DRectangleFromRectangle(aTextBound);
46
655
            const basegfx::B2DRange aObjectRange = vcl::unotools::b2DRectangleFromRectangle(aObjectBound);
47
48
            // no need to correct if no extra text range
49
655
            if(aTextRange != aObjectRange)
50
470
            {
51
470
                const GeoStat& rGeoStat(GetCustomShapeObj().GetGeoStat());
52
53
                // only correct when rotation and/or shear is used
54
470
                if(rGeoStat.m_nShearAngle || rGeoStat.m_nRotationAngle )
55
42
                {
56
                    // text range needs to be corrected by
57
                    // aObjectRange.getCenter() - aRotObjectRange.getCenter() since it's
58
                    // defined differently by using rotation around object center. Start
59
                    // with positive part
60
42
                    basegfx::B2DVector aTranslation(aObjectRange.getCenter());
61
62
                    // get rotated and sheared object's range
63
42
                    basegfx::B2DRange aRotObjectRange(aObjectRange);
64
42
                    basegfx::B2DHomMatrix aRotMatrix;
65
66
42
                    aRotMatrix.translate(-aObjectRange.getMinimum().getX(), -aObjectRange.getMinimum().getY());
67
68
42
                    if(rGeoStat.m_nShearAngle)
69
0
                    {
70
0
                        aRotMatrix.shearX(-rGeoStat.mfTanShearAngle);
71
0
                    }
72
73
42
                    if(rGeoStat.m_nRotationAngle)
74
42
                    {
75
42
                        aRotMatrix.rotate(toRadians(36000_deg100 - rGeoStat.m_nRotationAngle));
76
42
                    }
77
78
42
                    aRotMatrix.translate(aObjectRange.getMinimum().getX(), aObjectRange.getMinimum().getY());
79
42
                    aRotObjectRange.transform(aRotMatrix);
80
81
                    // add negative translation part
82
42
                    aTranslation -= aRotObjectRange.getCenter();
83
84
                    // create new range
85
42
                    aTextRange = basegfx::B2DRange(
86
42
                        aTextRange.getMinX() + aTranslation.getX(), aTextRange.getMinY() + aTranslation.getY(),
87
42
                        aTextRange.getMaxX() + aTranslation.getX(), aTextRange.getMaxY() + aTranslation.getY());
88
42
                }
89
90
                // NbcMirror() of SdrTextObj (from which SdrObjCustomShape is derived), adds a
91
                // 180deg rotation around the shape center to GeoStat.nRotationAngle. So remove here the
92
                // 180° rotation, which was added by GetTextBounds().
93
470
                if(GetCustomShapeObj().IsMirroredY())
94
0
                {
95
0
                    basegfx::B2DHomMatrix aRotMatrix(basegfx::utils::createRotateAroundPoint(
96
0
                        aObjectRange.getCenterX(), aObjectRange.getCenterY(), M_PI));
97
0
                    aTextRange.transform(aRotMatrix);
98
0
                }
99
470
            }
100
101
655
            return aTextRange;
102
655
        }
103
104
        void ViewContactOfSdrObjCustomShape::createViewIndependentPrimitive2DSequence(drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) const
105
1.28k
        {
106
1.28k
            const SfxItemSet& rItemSet = GetCustomShapeObj().GetMergedItemSet();
107
108
            // #i98072# Get shadow and text; eventually suppress the text if it's
109
            // a TextPath FontworkGallery object
110
1.28k
            const drawinglayer::attribute::SdrEffectsTextAttribute aAttribute(
111
1.28k
                drawinglayer::primitive2d::createNewSdrEffectsTextAttribute(
112
1.28k
                    rItemSet,
113
1.28k
                    GetCustomShapeObj().getText(0),
114
1.28k
                    GetCustomShapeObj().IsTextPath()));
115
1.28k
            drawinglayer::primitive2d::Primitive2DContainer xGroup;
116
1.28k
            bool bHasText(!aAttribute.getText().isDefault());
117
118
            // create Primitive2DContainer from sub-geometry
119
1.28k
            const SdrObject* pSdrObjRepresentation = GetCustomShapeObj().GetSdrObjectFromCustomShape();
120
1.28k
            bool b3DShape(false);
121
122
1.28k
            if(pSdrObjRepresentation)
123
228
            {
124
                // tdf#118498 The processing of SdrObjListIter for SdrIterMode::DeepNoGroups
125
                // did change for 3D-Objects, it now correctly enters and iterates the
126
                // SdrObjects in the E3dScene (same as for SdrObjGroup). This is more correct
127
                // as the old version which just checked for dynamic_cast<const SdrObjGroup*>
128
                // and *only* entered these, ignoring E3dScene as grouping-object.
129
                // But how to fix that? Taking back the SdrObjListIter change would be easy, but
130
                // not correct. After checking ViewContactOfE3dScene and ViewContactOfGroup
131
                // I see that both traverse their children by themselves (on VC-Level,
132
                // see createViewIndependentPrimitive2DSequence implementations and usage of
133
                // GetObjectCount()). Thus in principle iterating here (esp. 'deep') seems to
134
                // be wrong anyways, it might have even created wrong and double geometries
135
                // (only with complex CustomShapes with multiple representation SdrObjects and
136
                // only visible when transparency involved, but runtime-expensive).
137
                // Thus: Just do not iterate, will check behaviour deeply.
138
228
                b3DShape = (nullptr != DynCastE3dObject(pSdrObjRepresentation));
139
228
                pSdrObjRepresentation->GetViewContact().getViewIndependentPrimitive2DContainer(xGroup);
140
228
            }
141
142
1.28k
            if(bHasText || !xGroup.empty())
143
846
            {
144
                // prepare text box geometry
145
846
                basegfx::B2DHomMatrix aTextBoxMatrix;
146
846
                bool bWordWrap(false);
147
148
                // take unrotated snap rect as default, then get the
149
                // unrotated text box. Rotation needs to be done centered
150
846
                const tools::Rectangle aObjectBound(GetCustomShapeObj().GetGeoRect());
151
846
                const basegfx::B2DRange aObjectRange = vcl::unotools::b2DRectangleFromRectangle(aObjectBound);
152
153
846
                if(bHasText)
154
655
                {
155
                    // #i101684# get the text range unrotated and absolute to the object range
156
655
                    const basegfx::B2DRange aTextRange(getCorrectedTextBoundRect());
157
158
                    // Rotation before scaling
159
655
                    if(!basegfx::fTools::equalZero(GetCustomShapeObj().GetExtraTextRotation(true)))
160
20
                    {
161
20
                        basegfx::B2DVector aTranslation(0.5, 0.5);
162
20
                        aTextBoxMatrix.translate( -aTranslation.getX(), -aTranslation.getY() );
163
20
                        aTextBoxMatrix.rotate(basegfx::deg2rad(
164
20
                            360.0 - GetCustomShapeObj().GetExtraTextRotation(true)));
165
20
                        aTextBoxMatrix.translate( aTranslation.getX(), aTranslation.getY() );
166
20
                    }
167
                    // give text object a size
168
655
                    aTextBoxMatrix.scale(aTextRange.getWidth(), aTextRange.getHeight());
169
170
                    // check if we have a rotation/shear at all to take care of
171
655
                    const double fExtraTextRotation(GetCustomShapeObj().GetExtraTextRotation());
172
655
                    const GeoStat& rGeoStat(GetCustomShapeObj().GetGeoStat());
173
174
655
                    if(rGeoStat.m_nShearAngle || rGeoStat.m_nRotationAngle || !basegfx::fTools::equalZero(fExtraTextRotation))
175
42
                    {
176
42
                        if(aObjectRange != aTextRange)
177
42
                        {
178
                            // move relative to unrotated object range
179
42
                            aTextBoxMatrix.translate(
180
42
                                aTextRange.getMinX() - aObjectRange.getMinimum().getX(),
181
42
                                aTextRange.getMinY() - aObjectRange.getMinimum().getY());
182
42
                        }
183
184
42
                        if(!basegfx::fTools::equalZero(fExtraTextRotation))
185
36
                        {
186
36
                            basegfx::B2DVector aTranslation(
187
36
                                ( aTextRange.getWidth() / 2 ) + ( aTextRange.getMinX() - aObjectRange.getMinimum().getX() ),
188
36
                                ( aTextRange.getHeight() / 2 ) + ( aTextRange.getMinY() - aObjectRange.getMinimum().getY() ) );
189
36
                            aTextBoxMatrix.translate( -aTranslation.getX(), -aTranslation.getY() );
190
36
                            aTextBoxMatrix.rotate(basegfx::deg2rad(360.0 - fExtraTextRotation));
191
36
                            aTextBoxMatrix.translate( aTranslation.getX(), aTranslation.getY() );
192
36
                        }
193
194
42
                        if(rGeoStat.m_nShearAngle)
195
0
                        {
196
0
                            aTextBoxMatrix.shearX(-rGeoStat.mfTanShearAngle);
197
0
                        }
198
199
42
                        if(rGeoStat.m_nRotationAngle)
200
42
                        {
201
42
                            aTextBoxMatrix.rotate(toRadians(36000_deg100 - rGeoStat.m_nRotationAngle));
202
42
                        }
203
204
                        // give text it's target position
205
42
                        aTextBoxMatrix.translate(aObjectRange.getMinimum().getX(), aObjectRange.getMinimum().getY());
206
42
                    }
207
613
                    else
208
613
                    {
209
613
                        aTextBoxMatrix.translate(aTextRange.getMinX(), aTextRange.getMinY());
210
613
                    }
211
212
                    // check if SdrTextWordWrapItem is set
213
655
                    bWordWrap = GetCustomShapeObj().GetMergedItem(SDRATTR_TEXT_WORDWRAP).GetValue();
214
655
                }
215
216
                // fill object matrix
217
846
                const basegfx::B2DHomMatrix aObjectMatrix(basegfx::utils::createScaleTranslateB2DHomMatrix(
218
846
                    aObjectRange.getWidth(), aObjectRange.getHeight(),
219
846
                    aObjectRange.getMinX(), aObjectRange.getMinY()));
220
221
                // create primitive
222
846
                const drawinglayer::primitive2d::Primitive2DReference xReference(
223
846
                    new drawinglayer::primitive2d::SdrCustomShapePrimitive2D(
224
846
                        aAttribute,
225
846
                        std::move(xGroup),
226
846
                        aTextBoxMatrix,
227
846
                        bWordWrap,
228
846
                        b3DShape,
229
846
                        aObjectMatrix));
230
846
                rVisitor.visit(xReference);
231
846
            }
232
1.28k
        }
233
234
} // end of namespace
235
236
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */