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