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/textdecoratedprimitive2d.hxx>
28 #include <drawinglayer/primitive2d/polygonprimitive2d.hxx>
29 #include <drawinglayer/attribute/strokeattribute.hxx>
30 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx>
31 #include <basegfx/matrix/b2dhommatrixtools.hxx>
32 #include <drawinglayer/primitive2d/texteffectprimitive2d.hxx>
33 #include <drawinglayer/primitive2d/shadowprimitive2d.hxx>
34 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
35 #include <drawinglayer/primitive2d/textlineprimitive2d.hxx>
36 #include <drawinglayer/primitive2d/textstrikeoutprimitive2d.hxx>
37 #include <drawinglayer/primitive2d/textbreakuphelper.hxx>
38 
39 //////////////////////////////////////////////////////////////////////////////
40 
41 namespace drawinglayer
42 {
43 	namespace primitive2d
44 	{
impCreateGeometryContent(std::vector<Primitive2DReference> & rTarget,basegfx::tools::B2DHomMatrixBufferedOnDemandDecompose & rDecTrans,const String & rText,xub_StrLen aTextPosition,xub_StrLen aTextLength,const::std::vector<double> & rDXArray,const attribute::FontAttribute & rFontAttribute) const45         void TextDecoratedPortionPrimitive2D::impCreateGeometryContent(
46             std::vector< Primitive2DReference >& rTarget,
47             basegfx::tools::B2DHomMatrixBufferedOnDemandDecompose& rDecTrans,
48             const String& rText,
49 			xub_StrLen aTextPosition,
50 			xub_StrLen aTextLength,
51             const ::std::vector< double >& rDXArray,
52             const attribute::FontAttribute& rFontAttribute) const
53         {
54             // create the SimpleTextPrimitive needed in any case
55 	        rTarget.push_back(Primitive2DReference(
56                 new TextSimplePortionPrimitive2D(
57                     rDecTrans.getB2DHomMatrix(),
58                     rText,
59 				    aTextPosition,
60 				    aTextLength,
61                     rDXArray,
62                     rFontAttribute,
63                     getLocale(),
64                     getFontColor())));
65 
66 			// see if something else needs to be done
67             const bool bOverlineUsed(TEXT_LINE_NONE != getFontOverline());
68             const bool bUnderlineUsed(TEXT_LINE_NONE != getFontUnderline());
69 			const bool bStrikeoutUsed(TEXT_STRIKEOUT_NONE != getTextStrikeout());
70 
71             if(bUnderlineUsed || bStrikeoutUsed || bOverlineUsed)
72 			{
73 				// common preparations
74 			    TextLayouterDevice aTextLayouter;
75 
76                 // TextLayouterDevice is needed to get metrics for text decorations like
77                 // underline/strikeout/emphasis marks from it. For setup, the font size is needed
78 			    aTextLayouter.setFontAttribute(
79                     getFontAttribute(),
80                     rDecTrans.getScale().getX(),
81                     rDecTrans.getScale().getY(),
82                     getLocale());
83 
84 				// get text width
85 				double fTextWidth(0.0);
86 
87 				if(rDXArray.empty())
88 				{
89 					fTextWidth = aTextLayouter.getTextWidth(rText, aTextPosition, aTextLength);
90 				}
91 				else
92 				{
93 					fTextWidth = rDXArray.back() * rDecTrans.getScale().getX();
94                     const double fFontScaleX(rDecTrans.getScale().getX());
95 
96                     if(!basegfx::fTools::equal(fFontScaleX, 1.0)
97                         && !basegfx::fTools::equalZero(fFontScaleX))
98                     {
99                         // need to take FontScaling out of the DXArray
100                         fTextWidth /= fFontScaleX;
101                     }
102 				}
103 
104                 if(bOverlineUsed)
105                 {
106                     // create primitive geometry for overline
107 				    rTarget.push_back(Primitive2DReference(
108                         new TextLinePrimitive2D(
109                             rDecTrans.getB2DHomMatrix(),
110                             fTextWidth,
111                             aTextLayouter.getOverlineOffset(),
112                             aTextLayouter.getOverlineHeight(),
113                             getFontOverline(),
114                             getOverlineColor())));
115                 }
116 
117 				if(bUnderlineUsed)
118 				{
119 					// create primitive geometry for underline
120 				    rTarget.push_back(Primitive2DReference(
121                         new TextLinePrimitive2D(
122                             rDecTrans.getB2DHomMatrix(),
123                             fTextWidth,
124                             aTextLayouter.getUnderlineOffset(),
125                             aTextLayouter.getUnderlineHeight(),
126                             getFontUnderline(),
127                             getTextlineColor())));
128                 }
129 
130 				if(bStrikeoutUsed)
131 				{
132 					// create primitive geometry for strikeout
133                     if(TEXT_STRIKEOUT_SLASH == getTextStrikeout() || TEXT_STRIKEOUT_X == getTextStrikeout())
134                     {
135                         // strikeout with character
136                         const sal_Unicode aStrikeoutChar(TEXT_STRIKEOUT_SLASH == getTextStrikeout() ? '/' : 'X');
137 
138                         rTarget.push_back(Primitive2DReference(
139                             new TextCharacterStrikeoutPrimitive2D(
140                                 rDecTrans.getB2DHomMatrix(),
141                                 fTextWidth,
142                                 getFontColor(),
143                                 aStrikeoutChar,
144                                 getFontAttribute(),
145                                 getLocale())));
146                     }
147                     else
148                     {
149                         // strikeout with geometry
150                         rTarget.push_back(Primitive2DReference(
151                             new TextGeometryStrikeoutPrimitive2D(
152                                 rDecTrans.getB2DHomMatrix(),
153                                 fTextWidth,
154                                 getFontColor(),
155                                 aTextLayouter.getUnderlineHeight(),
156                                 aTextLayouter.getStrikeoutOffset(),
157                                 getTextStrikeout())));
158                     }
159 				}
160 			}
161 
162             // TODO: Handle Font Emphasis Above/Below
163         }
164 
create2DDecomposition(const geometry::ViewInformation2D &) const165 		Primitive2DSequence TextDecoratedPortionPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const
166         {
167             if(getWordLineMode())
168             {
169                 // support for single word mode; split to single word primitives
170                 // using TextBreakupHelper
171                 const TextBreakupHelper aTextBreakupHelper(*this);
172                 const Primitive2DSequence aBroken(aTextBreakupHelper.getResult(BreakupUnit_word));
173 
174                 if(aBroken.hasElements())
175                 {
176                     // was indeed split to several words, use as result
177                     return aBroken;
178                 }
179                 else
180                 {
181                     // no split, was already a single word. Continue to
182                     // decompse local entity
183                 }
184             }
185             std::vector< Primitive2DReference > aNewPrimitives;
186             basegfx::tools::B2DHomMatrixBufferedOnDemandDecompose aDecTrans(getTextTransform());
187             Primitive2DSequence aRetval;
188 
189             // create basic geometry such as SimpleTextPrimitive, Overline, Underline,
190             // Strikeout, etc...
191             // prepare new font attributes WITHOUT outline
192             const attribute::FontAttribute aNewFontAttribute(
193                 getFontAttribute().getFamilyName(),
194                 getFontAttribute().getStyleName(),
195                 getFontAttribute().getWeight(),
196                 getFontAttribute().getSymbol(),
197                 getFontAttribute().getVertical(),
198                 getFontAttribute().getItalic(),
199                 false,             // no outline anymore, handled locally
200                 getFontAttribute().getRTL(),
201                 getFontAttribute().getBiDiStrong());
202 
203 			// handle as one word
204             impCreateGeometryContent(aNewPrimitives, aDecTrans, getText(), getTextPosition(), getTextLength(), getDXArray(), aNewFontAttribute);
205 
206             // convert to Primitive2DSequence
207             const sal_uInt32 nMemberCount(aNewPrimitives.size());
208 
209 			if(nMemberCount)
210             {
211                 aRetval.realloc(nMemberCount);
212 
213                 for(sal_uInt32 a(0); a < nMemberCount; a++)
214                 {
215                     aRetval[a] = aNewPrimitives[a];
216                 }
217             }
218 
219             // Handle Shadow, Outline and TextRelief
220             if(aRetval.hasElements())
221             {
222                 // outline AND shadow depend on NO TextRelief (see dialog)
223                 const bool bHasTextRelief(TEXT_RELIEF_NONE != getTextRelief());
224                 const bool bHasShadow(!bHasTextRelief && getShadow());
225                 const bool bHasOutline(!bHasTextRelief && getFontAttribute().getOutline());
226 
227                 if(bHasShadow || bHasTextRelief || bHasOutline)
228                 {
229                     Primitive2DReference aShadow;
230 
231                     if(bHasShadow)
232                     {
233                         // create shadow with current content (in aRetval). Text shadow
234                         // is constant, relative to font size, rotated with the text and has a
235                         // constant color.
236                         // shadow parameter values
237                         static double fFactor(1.0 / 24.0);
238                         const double fTextShadowOffset(aDecTrans.getScale().getY() * fFactor);
239                         static basegfx::BColor aShadowColor(0.3, 0.3, 0.3);
240 
241                         // preapare shadow transform matrix
242                         const basegfx::B2DHomMatrix aShadowTransform(basegfx::tools::createTranslateB2DHomMatrix(
243                             fTextShadowOffset, fTextShadowOffset));
244 
245                         // create shadow primitive
246                         aShadow = Primitive2DReference(new ShadowPrimitive2D(
247                             aShadowTransform,
248                             aShadowColor,
249                             aRetval));
250                     }
251 
252                     if(bHasTextRelief)
253                     {
254                         // create emboss using an own helper primitive since this will
255                         // be view-dependent
256 						const basegfx::BColor aBBlack(0.0, 0.0, 0.0);
257 						const bool bDefaultTextColor(aBBlack == getFontColor());
258 						TextEffectStyle2D aTextEffectStyle2D(TEXTEFFECTSTYLE2D_RELIEF_EMBOSSED);
259 
260 						if(bDefaultTextColor)
261 						{
262 							if(TEXT_RELIEF_ENGRAVED == getTextRelief())
263 							{
264 								aTextEffectStyle2D = TEXTEFFECTSTYLE2D_RELIEF_ENGRAVED_DEFAULT;
265 							}
266 							else
267 							{
268 								aTextEffectStyle2D = TEXTEFFECTSTYLE2D_RELIEF_EMBOSSED_DEFAULT;
269 							}
270 						}
271 						else
272 						{
273 							if(TEXT_RELIEF_ENGRAVED == getTextRelief())
274 							{
275 								aTextEffectStyle2D = TEXTEFFECTSTYLE2D_RELIEF_ENGRAVED;
276 							}
277 							else
278 							{
279 								aTextEffectStyle2D = TEXTEFFECTSTYLE2D_RELIEF_EMBOSSED;
280 							}
281 						}
282 
283 						Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D(
284 							aRetval,
285 							aDecTrans.getTranslate(),
286 							aDecTrans.getRotate(),
287                             aTextEffectStyle2D));
288                         aRetval = Primitive2DSequence(&aNewTextEffect, 1);
289                     }
290                     else if(bHasOutline)
291                     {
292                         // create outline using an own helper primitive since this will
293                         // be view-dependent
294                         Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D(
295 							aRetval,
296 							aDecTrans.getTranslate(),
297 							aDecTrans.getRotate(),
298 							TEXTEFFECTSTYLE2D_OUTLINE));
299                         aRetval = Primitive2DSequence(&aNewTextEffect, 1);
300                     }
301 
302                     if(aShadow.is())
303                     {
304                         // put shadow in front if there is one to paint timely before
305                         // but placed behind content
306                         const Primitive2DSequence aContent(aRetval);
307                         aRetval = Primitive2DSequence(&aShadow, 1);
308                         appendPrimitive2DSequenceToPrimitive2DSequence(aRetval, aContent);
309                     }
310                 }
311             }
312 
313             return aRetval;
314         }
315 
TextDecoratedPortionPrimitive2D(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,const basegfx::BColor & rOverlineColor,const basegfx::BColor & rTextlineColor,TextLine eFontOverline,TextLine eFontUnderline,bool bUnderlineAbove,TextStrikeout eTextStrikeout,bool bWordLineMode,TextEmphasisMark eTextEmphasisMark,bool bEmphasisMarkAbove,bool bEmphasisMarkBelow,TextRelief eTextRelief,bool bShadow)316         TextDecoratedPortionPrimitive2D::TextDecoratedPortionPrimitive2D(
317 
318             // TextSimplePortionPrimitive2D parameters
319 			const basegfx::B2DHomMatrix& rNewTransform,
320 			const String& rText,
321 			xub_StrLen aTextPosition,
322 			xub_StrLen aTextLength,
323 			const ::std::vector< double >& rDXArray,
324             const attribute::FontAttribute& rFontAttribute,
325             const ::com::sun::star::lang::Locale& rLocale,
326 			const basegfx::BColor& rFontColor,
327 
328             // local parameters
329             const basegfx::BColor& rOverlineColor,
330             const basegfx::BColor& rTextlineColor,
331             TextLine eFontOverline,
332             TextLine eFontUnderline,
333 			bool bUnderlineAbove,
334 			TextStrikeout eTextStrikeout,
335 			bool bWordLineMode,
336 			TextEmphasisMark eTextEmphasisMark,
337 			bool bEmphasisMarkAbove,
338 			bool bEmphasisMarkBelow,
339 			TextRelief eTextRelief,
340 			bool bShadow)
341 		:	TextSimplePortionPrimitive2D(rNewTransform, rText, aTextPosition, aTextLength, rDXArray, rFontAttribute, rLocale, rFontColor),
342             maOverlineColor(rOverlineColor),
343             maTextlineColor(rTextlineColor),
344             meFontOverline(eFontOverline),
345             meFontUnderline(eFontUnderline),
346 			meTextStrikeout(eTextStrikeout),
347 			meTextEmphasisMark(eTextEmphasisMark),
348 			meTextRelief(eTextRelief),
349 			mbUnderlineAbove(bUnderlineAbove),
350 			mbWordLineMode(bWordLineMode),
351 			mbEmphasisMarkAbove(bEmphasisMarkAbove),
352 			mbEmphasisMarkBelow(bEmphasisMarkBelow),
353 			mbShadow(bShadow)
354 		{
355 		}
356 
decoratedIsNeeded() const357         bool TextDecoratedPortionPrimitive2D::decoratedIsNeeded() const
358         {
359             return (TEXT_LINE_NONE != getFontOverline()
360                  || TEXT_LINE_NONE != getFontUnderline()
361                  || TEXT_STRIKEOUT_NONE != getTextStrikeout()
362                  || TEXT_EMPHASISMARK_NONE != getTextEmphasisMark()
363                  || TEXT_RELIEF_NONE != getTextRelief()
364                  || getShadow());
365         }
366 
operator ==(const BasePrimitive2D & rPrimitive) const367 		bool TextDecoratedPortionPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const
368 		{
369 			if(TextSimplePortionPrimitive2D::operator==(rPrimitive))
370 			{
371 				const TextDecoratedPortionPrimitive2D& rCompare = (TextDecoratedPortionPrimitive2D&)rPrimitive;
372 
373                 return (getOverlineColor() == rCompare.getOverlineColor()
374                     && getTextlineColor() == rCompare.getTextlineColor()
375                     && getFontOverline() == rCompare.getFontOverline()
376                     && getFontUnderline() == rCompare.getFontUnderline()
377 					&& getTextStrikeout() == rCompare.getTextStrikeout()
378 					&& getTextEmphasisMark() == rCompare.getTextEmphasisMark()
379 					&& getTextRelief() == rCompare.getTextRelief()
380 					&& getUnderlineAbove() == rCompare.getUnderlineAbove()
381 					&& getWordLineMode() == rCompare.getWordLineMode()
382 					&& getEmphasisMarkAbove() == rCompare.getEmphasisMarkAbove()
383 					&& getEmphasisMarkBelow() == rCompare.getEmphasisMarkBelow()
384 					&& getShadow() == rCompare.getShadow());
385 			}
386 
387 			return false;
388 		}
389 
390         // #i96475#
391         // Added missing implementation. Decorations may (will) stick out of the text's
392         // inking area, so add them if needed
getB2DRange(const geometry::ViewInformation2D & rViewInformation) const393 		basegfx::B2DRange TextDecoratedPortionPrimitive2D::getB2DRange(const geometry::ViewInformation2D& rViewInformation) const
394 		{
395             if(decoratedIsNeeded())
396             {
397                 // decoration is used, fallback to BufferedDecompositionPrimitive2D::getB2DRange which uses
398                 // the own local decomposition for computation and thus creates all necessary
399                 // geometric objects
400                 return BufferedDecompositionPrimitive2D::getB2DRange(rViewInformation);
401             }
402             else
403             {
404                 // no relevant decoration used, fallback to TextSimplePortionPrimitive2D::getB2DRange
405                 return TextSimplePortionPrimitive2D::getB2DRange(rViewInformation);
406             }
407         }
408 
409 		// provide unique ID
410 		ImplPrimitrive2DIDBlock(TextDecoratedPortionPrimitive2D, PRIMITIVE2D_ID_TEXTDECORATEDPORTIONPRIMITIVE2D)
411 
412 	} // end of namespace primitive2d
413 } // end of namespace drawinglayer
414 
415 //////////////////////////////////////////////////////////////////////////////
416 // eof
417