1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_drawinglayer.hxx"
26 
27 #include <drawinglayer/primitive2d/textprimitive2d.hxx>
28 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
29 #include <basegfx/polygon/b2dpolypolygon.hxx>
30 #include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx>
31 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
32 #include <drawinglayer/primitive2d/texteffectprimitive2d.hxx>
33 #include <basegfx/matrix/b2dhommatrixtools.hxx>
34 
35 //////////////////////////////////////////////////////////////////////////////
36 
37 using namespace com::sun::star;
38 
39 //////////////////////////////////////////////////////////////////////////////
40 
41 namespace
42 {
43 	// adapts fontScale for usage with TextLayouter. Input is rScale which is the extracted
44 	// scale from a text transformation. A copy is modified so that it contains only positive
45     // scalings and XY-equal scalings to allow to get a non-X-scaled Vcl-Font for TextLayouter.
46     // rScale is adapted accordingly to contain the corrected scale which would need to be
47     // applied to e.g. outlines received from TextLayouter under usage of fontScale. This
48     // includes Y-Scale, X-Scale-correction and mirrorings.
getCorrectedScaleAndFontScale(basegfx::B2DVector & rScale)49     basegfx::B2DVector getCorrectedScaleAndFontScale(basegfx::B2DVector& rScale)
50 	{
51         // copy input value
52         basegfx::B2DVector aFontScale(rScale);
53 
54         // correct FontHeight settings
55         if(basegfx::fTools::equalZero(aFontScale.getY()))
56         {
57             // no font height; choose one and adapt scale to get back to original scaling
58             static double fDefaultFontScale(100.0);
59             rScale.setY(1.0 / fDefaultFontScale);
60             aFontScale.setY(fDefaultFontScale);
61         }
62         else if(basegfx::fTools::less(aFontScale.getY(), 0.0))
63         {
64             // negative font height; invert and adapt scale to get back to original scaling
65             aFontScale.setY(-aFontScale.getY());
66             rScale.setY(-1.0);
67         }
68         else
69         {
70             // positive font height; adapt scale; scaling will be part of the polygons
71             rScale.setY(1.0);
72         }
73 
74         // correct FontWidth settings
75         if(basegfx::fTools::equal(aFontScale.getX(), aFontScale.getY()))
76         {
77             // no FontScale, adapt scale
78             rScale.setX(1.0);
79         }
80         else
81         {
82             // If FontScale is used, force to no FontScale to get a non-scaled VCL font.
83             // Adapt scaling in X accordingly.
84             rScale.setX(aFontScale.getX() / aFontScale.getY());
85             aFontScale.setX(aFontScale.getY());
86         }
87 
88         return aFontScale;
89 	}
90 } // end of anonymous namespace
91 
92 //////////////////////////////////////////////////////////////////////////////
93 
94 namespace drawinglayer
95 {
96 	namespace primitive2d
97 	{
getTextOutlinesAndTransformation(basegfx::B2DPolyPolygonVector & rTarget,basegfx::B2DHomMatrix & rTransformation) const98         void TextSimplePortionPrimitive2D::getTextOutlinesAndTransformation(basegfx::B2DPolyPolygonVector& rTarget, basegfx::B2DHomMatrix& rTransformation) const
99         {
100 			if(getTextLength())
101 			{
102 				// decompose object transformation to single values
103 				basegfx::B2DVector aScale, aTranslate;
104 				double fRotate, fShearX;
105 
106 				// if decomposition returns false, create no geometry since e.g. scaling may
107 				// be zero
108 				if(getTextTransform().decompose(aScale, aTranslate, fRotate, fShearX))
109 				{
110 					// handle special case: If scale is negative in (x,y) (3rd quadrant), it can
111 					// be expressed as rotation by PI
112 					if(basegfx::fTools::less(aScale.getX(), 0.0) && basegfx::fTools::less(aScale.getY(), 0.0))
113 					{
114 						aScale = basegfx::absolute(aScale);
115 						fRotate += F_PI;
116 					}
117 
118 					// for the TextLayouterDevice, it is necessary to have a scaling representing
119 					// the font size. Since we want to extract polygons here, it is okay to
120 					// work just with scaling and to ignore shear, rotation and translation,
121 					// all that can be applied to the polygons later
122 					const basegfx::B2DVector aFontScale(getCorrectedScaleAndFontScale(aScale));
123 
124 					// prepare textlayoutdevice
125 					TextLayouterDevice aTextLayouter;
126 					aTextLayouter.setFontAttribute(
127                         getFontAttribute(),
128                         aFontScale.getX(),
129                         aFontScale.getY(),
130                         getLocale());
131 
132                     // When getting outlines from stretched text (aScale.getX() != 1.0) it
133                     // is necessary to inverse-scale the DXArray (if used) to not get the
134                     // outlines already aligned to given, but wrong DXArray
135                     if(getDXArray().size() && !basegfx::fTools::equal(aScale.getX(), 1.0))
136                     {
137             			::std::vector< double > aScaledDXArray = getDXArray();
138                         const double fDXArrayScale(1.0 / aScale.getX());
139 
140                         for(sal_uInt32 a(0); a < aScaledDXArray.size(); a++)
141                         {
142                             aScaledDXArray[a] *= fDXArrayScale;
143                         }
144 
145                         // get the text outlines
146 					    aTextLayouter.getTextOutlines(
147                             rTarget,
148                             getText(),
149                             getTextPosition(),
150                             getTextLength(),
151                             aScaledDXArray);
152                     }
153                     else
154                     {
155                         // get the text outlines
156 					    aTextLayouter.getTextOutlines(
157                             rTarget,
158                             getText(),
159                             getTextPosition(),
160                             getTextLength(),
161                             getDXArray());
162                     }
163 
164 					// create primitives for the outlines
165 					const sal_uInt32 nCount(rTarget.size());
166 
167 					if(nCount)
168 					{
169 						// prepare object transformation for polygons
170 						rTransformation = basegfx::tools::createScaleShearXRotateTranslateB2DHomMatrix(
171 							aScale, fShearX, fRotate, aTranslate);
172 					}
173 				}
174 			}
175         }
176 
create2DDecomposition(const geometry::ViewInformation2D &) const177         Primitive2DSequence TextSimplePortionPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
178 		{
179 			Primitive2DSequence aRetval;
180 
181 			if(getTextLength())
182 			{
183 				basegfx::B2DPolyPolygonVector aB2DPolyPolyVector;
184 			    basegfx::B2DHomMatrix aPolygonTransform;
185 
186                 // get text outlines and their object transformation
187                 getTextOutlinesAndTransformation(aB2DPolyPolyVector, aPolygonTransform);
188 
189 				// create primitives for the outlines
190 				const sal_uInt32 nCount(aB2DPolyPolyVector.size());
191 
192 				if(nCount)
193 				{
194 					// alloc space for the primitives
195 					aRetval.realloc(nCount);
196 
197 					// color-filled polypolygons
198 					for(sal_uInt32 a(0L); a < nCount; a++)
199 					{
200 						// prepare polypolygon
201 						basegfx::B2DPolyPolygon& rPolyPolygon = aB2DPolyPolyVector[a];
202 						rPolyPolygon.transform(aPolygonTransform);
203 						aRetval[a] = new PolyPolygonColorPrimitive2D(rPolyPolygon, getFontColor());
204 					}
205 
206 					if(getFontAttribute().getOutline())
207 					{
208 				        // decompose polygon transformation to single values
209 				        basegfx::B2DVector aScale, aTranslate;
210 				        double fRotate, fShearX;
211 				        aPolygonTransform.decompose(aScale, aTranslate, fRotate, fShearX);
212 
213                         // create outline text effect with current content and replace
214 						Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D(
215 							aRetval,
216 							aTranslate,
217 							fRotate,
218 							TEXTEFFECTSTYLE2D_OUTLINE));
219 
220 						aRetval = Primitive2DSequence(&aNewTextEffect, 1);
221 					}
222 				}
223 			}
224 
225 			return aRetval;
226 		}
227 
TextSimplePortionPrimitive2D(const basegfx::B2DHomMatrix & rNewTransform,const String & rText,xub_StrLen aTextPosition,xub_StrLen aTextLength,const::std::vector<double> & rDXArray,const attribute::FontAttribute & rFontAttribute,const::com::sun::star::lang::Locale & rLocale,const basegfx::BColor & rFontColor)228 		TextSimplePortionPrimitive2D::TextSimplePortionPrimitive2D(
229 			const basegfx::B2DHomMatrix& rNewTransform,
230 			const String& rText,
231 			xub_StrLen aTextPosition,
232 			xub_StrLen aTextLength,
233 			const ::std::vector< double >& rDXArray,
234             const attribute::FontAttribute& rFontAttribute,
235             const ::com::sun::star::lang::Locale& rLocale,
236 			const basegfx::BColor& rFontColor)
237 		:	BufferedDecompositionPrimitive2D(),
238 			maTextTransform(rNewTransform),
239 			maText(rText),
240 			maTextPosition(aTextPosition),
241 			maTextLength(aTextLength),
242 			maDXArray(rDXArray),
243 			maFontAttribute(rFontAttribute),
244             maLocale(rLocale),
245 			maFontColor(rFontColor),
246 			maB2DRange()
247 		{
248 #ifdef DBG_UTIL
249 			const xub_StrLen aStringLength(getText().Len());
250 			OSL_ENSURE(aStringLength >= getTextPosition() && aStringLength >= getTextPosition() + getTextLength(),
251 				"TextSimplePortionPrimitive2D with text out of range (!)");
252 #endif
253 		}
254 
LocalesAreEqual(const::com::sun::star::lang::Locale & rA,const::com::sun::star::lang::Locale & rB)255         bool LocalesAreEqual(const ::com::sun::star::lang::Locale& rA, const ::com::sun::star::lang::Locale& rB)
256         {
257             return (rA.Language == rB.Language
258                 && rA.Country == rB.Country
259                 && rA.Variant == rB.Variant);
260         }
261 
operator ==(const BasePrimitive2D & rPrimitive) const262 		bool TextSimplePortionPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
263 		{
264 			if(BufferedDecompositionPrimitive2D::operator==(rPrimitive))
265 			{
266 				const TextSimplePortionPrimitive2D& rCompare = (TextSimplePortionPrimitive2D&)rPrimitive;
267 
268 				return (getTextTransform() == rCompare.getTextTransform()
269 					&& getText() == rCompare.getText()
270 					&& getTextPosition() == rCompare.getTextPosition()
271 					&& getTextLength() == rCompare.getTextLength()
272 					&& getDXArray() == rCompare.getDXArray()
273 					&& getFontAttribute() == rCompare.getFontAttribute()
274                     && LocalesAreEqual(getLocale(), rCompare.getLocale())
275 					&& getFontColor() == rCompare.getFontColor());
276 			}
277 
278 			return false;
279 		}
280 
getB2DRange(const geometry::ViewInformation2D &) const281 		basegfx::B2DRange TextSimplePortionPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const
282 		{
283 			if(maB2DRange.isEmpty() && getTextLength())
284 			{
285 				// get TextBoundRect as base size
286 				// decompose object transformation to single values
287 				basegfx::B2DVector aScale, aTranslate;
288 				double fRotate, fShearX;
289 
290 				if(getTextTransform().decompose(aScale, aTranslate, fRotate, fShearX))
291 				{
292 					// for the TextLayouterDevice, it is necessary to have a scaling representing
293 					// the font size. Since we want to extract polygons here, it is okay to
294 					// work just with scaling and to ignore shear, rotation and translation,
295 					// all that can be applied to the polygons later
296 					const basegfx::B2DVector aFontScale(getCorrectedScaleAndFontScale(aScale));
297 
298 					// prepare textlayoutdevice
299 					TextLayouterDevice aTextLayouter;
300 					aTextLayouter.setFontAttribute(
301                         getFontAttribute(),
302                         aFontScale.getX(),
303                         aFontScale.getY(),
304                         getLocale());
305 
306 					// get basic text range
307 					basegfx::B2DRange aNewRange(aTextLayouter.getTextBoundRect(getText(), getTextPosition(), getTextLength()));
308 
309                     // #i104432#, #i102556# take empty results into account
310                     if(!aNewRange.isEmpty())
311                     {
312 					    // prepare object transformation for range
313 						const basegfx::B2DHomMatrix aRangeTransformation(basegfx::tools::createScaleShearXRotateTranslateB2DHomMatrix(
314 							aScale, fShearX, fRotate, aTranslate));
315 
316 					    // apply range transformation to it
317 					    aNewRange.transform(aRangeTransformation);
318 
319 					    // assign to buffered value
320 					    const_cast< TextSimplePortionPrimitive2D* >(this)->maB2DRange = aNewRange;
321                     }
322 				}
323 			}
324 
325 			return maB2DRange;
326 		}
327 
328 		// provide unique ID
329 		ImplPrimitrive2DIDBlock(TextSimplePortionPrimitive2D, PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D)
330 
331 	} // end of namespace primitive2d
332 } // end of namespace drawinglayer
333 
334 //////////////////////////////////////////////////////////////////////////////
335 // eof
336