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 <drawinglayer/primitive2d/textprimitive2d.hxx>
21 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
22 #include <basegfx/polygon/b2dpolypolygon.hxx>
23 #include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx>
24 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
25 #include <drawinglayer/primitive2d/texteffectprimitive2d.hxx>
26 #include <basegfx/matrix/b2dhommatrixtools.hxx>
27 
28 
29 using namespace com::sun::star;
30 
31 
32 namespace
33 {
34     // adapts fontScale for usage with TextLayouter. Input is rScale which is the extracted
35     // scale from a text transformation. A copy is modified so that it contains only positive
36     // scalings and XY-equal scalings to allow to get a non-X-scaled Vcl-Font for TextLayouter.
37     // rScale is adapted accordingly to contain the corrected scale which would need to be
38     // applied to e.g. outlines received from TextLayouter under usage of fontScale. This
39     // includes Y-Scale, X-Scale-correction and mirrorings.
getCorrectedScaleAndFontScale(basegfx::B2DVector & rScale)40     basegfx::B2DVector getCorrectedScaleAndFontScale(basegfx::B2DVector& rScale)
41     {
42         // copy input value
43         basegfx::B2DVector aFontScale(rScale);
44 
45         // correct FontHeight settings
46         if(basegfx::fTools::equalZero(aFontScale.getY()))
47         {
48             // no font height; choose one and adapt scale to get back to original scaling
49             static const double fDefaultFontScale(100.0);
50             rScale.setY(1.0 / fDefaultFontScale);
51             aFontScale.setY(fDefaultFontScale);
52         }
53         else if(basegfx::fTools::less(aFontScale.getY(), 0.0))
54         {
55             // negative font height; invert and adapt scale to get back to original scaling
56             aFontScale.setY(-aFontScale.getY());
57             rScale.setY(-1.0);
58         }
59         else
60         {
61             // positive font height; adapt scale; scaling will be part of the polygons
62             rScale.setY(1.0);
63         }
64 
65         // correct FontWidth settings
66         if(basegfx::fTools::equal(aFontScale.getX(), aFontScale.getY()))
67         {
68             // no FontScale, adapt scale
69             rScale.setX(1.0);
70         }
71         else
72         {
73             // If FontScale is used, force to no FontScale to get a non-scaled VCL font.
74             // Adapt scaling in X accordingly.
75             rScale.setX(aFontScale.getX() / aFontScale.getY());
76             aFontScale.setX(aFontScale.getY());
77         }
78 
79         return aFontScale;
80     }
81 } // end of anonymous namespace
82 
83 
84 namespace drawinglayer
85 {
86     namespace primitive2d
87     {
getTextOutlinesAndTransformation(basegfx::B2DPolyPolygonVector & rTarget,basegfx::B2DHomMatrix & rTransformation) const88         void TextSimplePortionPrimitive2D::getTextOutlinesAndTransformation(basegfx::B2DPolyPolygonVector& rTarget, basegfx::B2DHomMatrix& rTransformation) const
89         {
90             if(getTextLength())
91             {
92                 // decompose object transformation to single values
93                 basegfx::B2DVector aScale, aTranslate;
94                 double fRotate, fShearX;
95 
96                 // if decomposition returns false, create no geometry since e.g. scaling may
97                 // be zero
98                 if (getTextTransform().decompose(aScale, aTranslate, fRotate, fShearX) && aScale.getX() != 0.0)
99                 {
100                     // handle special case: If scale is negative in (x,y) (3rd quadrant), it can
101                     // be expressed as rotation by PI
102                     if(basegfx::fTools::less(aScale.getX(), 0.0) && basegfx::fTools::less(aScale.getY(), 0.0))
103                     {
104                         aScale = basegfx::absolute(aScale);
105                         fRotate += F_PI;
106                     }
107 
108                     // for the TextLayouterDevice, it is necessary to have a scaling representing
109                     // the font size. Since we want to extract polygons here, it is okay to
110                     // work just with scaling and to ignore shear, rotation and translation,
111                     // all that can be applied to the polygons later
112                     const basegfx::B2DVector aFontScale(getCorrectedScaleAndFontScale(aScale));
113 
114                     // prepare textlayoutdevice
115                     TextLayouterDevice aTextLayouter;
116                     aTextLayouter.setFontAttribute(
117                         getFontAttribute(),
118                         aFontScale.getX(),
119                         aFontScale.getY(),
120                         getLocale());
121 
122                     // When getting outlines from stretched text (aScale.getX() != 1.0) it
123                     // is necessary to inverse-scale the DXArray (if used) to not get the
124                     // outlines already aligned to given, but wrong DXArray
125                     if(!getDXArray().empty() && !basegfx::fTools::equal(aScale.getX(), 1.0))
126                     {
127                         std::vector< double > aScaledDXArray = getDXArray();
128                         const double fDXArrayScale(1.0 / aScale.getX());
129 
130                         for(double & a : aScaledDXArray)
131                         {
132                             a *= fDXArrayScale;
133                         }
134 
135                         // get the text outlines
136                         aTextLayouter.getTextOutlines(
137                             rTarget,
138                             getText(),
139                             getTextPosition(),
140                             getTextLength(),
141                             aScaledDXArray);
142                     }
143                     else
144                     {
145                         // get the text outlines
146                         aTextLayouter.getTextOutlines(
147                             rTarget,
148                             getText(),
149                             getTextPosition(),
150                             getTextLength(),
151                             getDXArray());
152                     }
153 
154                     // create primitives for the outlines
155                     const sal_uInt32 nCount(rTarget.size());
156 
157                     if(nCount)
158                     {
159                         // prepare object transformation for polygons
160                         rTransformation = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
161                             aScale, fShearX, fRotate, aTranslate);
162                     }
163                 }
164             }
165         }
166 
create2DDecomposition(Primitive2DContainer & rContainer,const geometry::ViewInformation2D &) const167         void TextSimplePortionPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const
168         {
169             if(!getTextLength())
170                 return;
171 
172             Primitive2DContainer aRetval;
173             basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
174             basegfx::B2DHomMatrix aPolygonTransform;
175 
176             // get text outlines and their object transformation
177             getTextOutlinesAndTransformation(aB2DPolyPolyVector, aPolygonTransform);
178 
179             // create primitives for the outlines
180             const sal_uInt32 nCount(aB2DPolyPolyVector.size());
181 
182             if(!nCount)
183                 return;
184 
185             // alloc space for the primitives
186             aRetval.resize(nCount);
187 
188             // color-filled polypolygons
189             for(sal_uInt32 a(0); a < nCount; a++)
190             {
191                 // prepare polypolygon
192                 basegfx::B2DPolyPolygon& rPolyPolygon = aB2DPolyPolyVector[a];
193                 rPolyPolygon.transform(aPolygonTransform);
194                 aRetval[a] = new PolyPolygonColorPrimitive2D(rPolyPolygon, getFontColor());
195             }
196 
197             if(getFontAttribute().getOutline())
198             {
199                 // decompose polygon transformation to single values
200                 basegfx::B2DVector aScale, aTranslate;
201                 double fRotate, fShearX;
202                 aPolygonTransform.decompose(aScale, aTranslate, fRotate, fShearX);
203 
204                 // create outline text effect with current content and replace
205                 Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D(
206                     aRetval,
207                     aTranslate,
208                     fRotate,
209                     TextEffectStyle2D::Outline));
210 
211                 aRetval = Primitive2DContainer { aNewTextEffect };
212             }
213 
214             rContainer.insert(rContainer.end(), aRetval.begin(), aRetval.end());
215         }
216 
TextSimplePortionPrimitive2D(const basegfx::B2DHomMatrix & rNewTransform,const OUString & rText,sal_Int32 nTextPosition,sal_Int32 nTextLength,const std::vector<double> & rDXArray,const attribute::FontAttribute & rFontAttribute,const css::lang::Locale & rLocale,const basegfx::BColor & rFontColor,bool bFilled,long nWidthToFill,const Color & rTextFillColor)217         TextSimplePortionPrimitive2D::TextSimplePortionPrimitive2D(
218             const basegfx::B2DHomMatrix& rNewTransform,
219             const OUString& rText,
220             sal_Int32 nTextPosition,
221             sal_Int32 nTextLength,
222             const std::vector< double >& rDXArray,
223             const attribute::FontAttribute& rFontAttribute,
224             const css::lang::Locale& rLocale,
225             const basegfx::BColor& rFontColor,
226             bool bFilled,
227             long nWidthToFill,
228             const Color& rTextFillColor)
229         :   BufferedDecompositionPrimitive2D(),
230             maTextTransform(rNewTransform),
231             maText(rText),
232             mnTextPosition(nTextPosition),
233             mnTextLength(nTextLength),
234             maDXArray(rDXArray),
235             maFontAttribute(rFontAttribute),
236             maLocale(rLocale),
237             maFontColor(rFontColor),
238             mbFilled(bFilled),
239             mnWidthToFill(nWidthToFill),
240             maTextFillColor(rTextFillColor),
241             maB2DRange()
242         {
243 #if OSL_DEBUG_LEVEL > 0
244             const sal_Int32 aStringLength(getText().getLength());
245             OSL_ENSURE(aStringLength >= getTextPosition() && aStringLength >= getTextPosition() + getTextLength(),
246                 "TextSimplePortionPrimitive2D with text out of range (!)");
247 #endif
248         }
249 
LocalesAreEqual(const css::lang::Locale & rA,const css::lang::Locale & rB)250         bool LocalesAreEqual(const css::lang::Locale& rA, const css::lang::Locale& rB)
251         {
252             return (rA.Language == rB.Language
253                 && rA.Country == rB.Country
254                 && rA.Variant == rB.Variant);
255         }
256 
operator ==(const BasePrimitive2D & rPrimitive) const257         bool TextSimplePortionPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
258         {
259             if(BufferedDecompositionPrimitive2D::operator==(rPrimitive))
260             {
261                 const TextSimplePortionPrimitive2D& rCompare = static_cast<const TextSimplePortionPrimitive2D&>(rPrimitive);
262 
263                 return (getTextTransform() == rCompare.getTextTransform()
264                     && getText() == rCompare.getText()
265                     && getTextPosition() == rCompare.getTextPosition()
266                     && getTextLength() == rCompare.getTextLength()
267                     && getDXArray() == rCompare.getDXArray()
268                     && getFontAttribute() == rCompare.getFontAttribute()
269                     && LocalesAreEqual(getLocale(), rCompare.getLocale())
270                     && getFontColor() == rCompare.getFontColor()
271                     && mbFilled == rCompare.mbFilled
272                     && mnWidthToFill == rCompare.mnWidthToFill
273                     && maTextFillColor == rCompare.maTextFillColor);
274             }
275 
276             return false;
277         }
278 
getB2DRange(const geometry::ViewInformation2D &) const279         basegfx::B2DRange TextSimplePortionPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const
280         {
281             if(maB2DRange.isEmpty() && getTextLength())
282             {
283                 // get TextBoundRect as base size
284                 // decompose object transformation to single values
285                 basegfx::B2DVector aScale, aTranslate;
286                 double fRotate, fShearX;
287 
288                 if(getTextTransform().decompose(aScale, aTranslate, fRotate, fShearX))
289                 {
290                     // for the TextLayouterDevice, it is necessary to have a scaling representing
291                     // the font size. Since we want to extract polygons here, it is okay to
292                     // work just with scaling and to ignore shear, rotation and translation,
293                     // all that can be applied to the polygons later
294                     const basegfx::B2DVector aFontScale(getCorrectedScaleAndFontScale(aScale));
295 
296                     // prepare textlayoutdevice
297                     TextLayouterDevice aTextLayouter;
298                     aTextLayouter.setFontAttribute(
299                         getFontAttribute(),
300                         aFontScale.getX(),
301                         aFontScale.getY(),
302                         getLocale());
303 
304                     // get basic text range
305                     basegfx::B2DRange aNewRange(aTextLayouter.getTextBoundRect(getText(), getTextPosition(), getTextLength()));
306 
307                     // #i104432#, #i102556# take empty results into account
308                     if(!aNewRange.isEmpty())
309                     {
310                         // prepare object transformation for range
311                         const basegfx::B2DHomMatrix aRangeTransformation(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix(
312                             aScale, fShearX, fRotate, aTranslate));
313 
314                         // apply range transformation to it
315                         aNewRange.transform(aRangeTransformation);
316 
317                         // assign to buffered value
318                         const_cast< TextSimplePortionPrimitive2D* >(this)->maB2DRange = aNewRange;
319                     }
320                 }
321             }
322 
323             return maB2DRange;
324         }
325 
326         // provide unique ID
327         ImplPrimitive2DIDBlock(TextSimplePortionPrimitive2D, PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D)
328 
329     } // end of namespace primitive2d
330 } // end of namespace drawinglayer
331 
332 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
333