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 // MARKER(update_precomp.py): autogen include statement, do not remove
23 #include "precompiled_drawinglayer.hxx"
24 
25 #include <drawinglayer/primitive2d/textbreakuphelper.hxx>
26 #include <drawinglayer/primitive2d/textdecoratedprimitive2d.hxx>
27 #include <com/sun/star/i18n/XBreakIterator.hpp>
28 #include <comphelper/processfactory.hxx>
29 #include <com/sun/star/i18n/CharacterIteratorMode.hdl>
30 #include <com/sun/star/i18n/WordType.hpp>
31 #include <com/sun/star/i18n/CharType.hpp>
32 
33 //////////////////////////////////////////////////////////////////////////////
34 
35 namespace drawinglayer
36 {
37     namespace primitive2d
38     {
TextBreakupHelper(const TextSimplePortionPrimitive2D & rSource)39         TextBreakupHelper::TextBreakupHelper(const TextSimplePortionPrimitive2D& rSource)
40         :   mrSource(rSource),
41             mxResult(),
42             maTextLayouter(),
43             maDecTrans(),
44             mbNoDXArray(false)
45         {
46             OSL_ENSURE(dynamic_cast< const TextSimplePortionPrimitive2D* >(&mrSource), "TextBreakupHelper with illegal primitive created (!)");
47             maDecTrans = mrSource.getTextTransform();
48             mbNoDXArray = mrSource.getDXArray().empty();
49 
50             if(mbNoDXArray)
51             {
52                 // init TextLayouter when no dxarray
53                 maTextLayouter.setFontAttribute(
54                     mrSource.getFontAttribute(),
55                     maDecTrans.getScale().getX(),
56                     maDecTrans.getScale().getY(),
57                     mrSource.getLocale());
58             }
59         }
60 
~TextBreakupHelper()61         TextBreakupHelper::~TextBreakupHelper()
62         {
63         }
64 
breakupPortion(Primitive2DVector & rTempResult,sal_uInt32 nIndex,sal_uInt32 nLength,bool bWordLineMode)65         void TextBreakupHelper::breakupPortion(Primitive2DVector& rTempResult, sal_uInt32 nIndex, sal_uInt32 nLength, bool bWordLineMode)
66         {
67             if(nLength && !(nIndex == mrSource.getTextPosition() && nLength == mrSource.getTextLength()))
68             {
69                 // prepare values for new portion
70                 basegfx::B2DHomMatrix aNewTransform;
71                 ::std::vector< double > aNewDXArray;
72                 const bool bNewStartIsNotOldStart(nIndex > mrSource.getTextPosition());
73 
74                 if(!mbNoDXArray)
75                 {
76                     // prepare new DXArray for the single word
77                     aNewDXArray = ::std::vector< double >(
78                         mrSource.getDXArray().begin() + (nIndex - mrSource.getTextPosition()),
79                         mrSource.getDXArray().begin() + ((nIndex + nLength) - mrSource.getTextPosition()));
80                 }
81 
82                 if(bNewStartIsNotOldStart)
83                 {
84                     // needs to be moved to a new start position
85                     double fOffset(0.0);
86 
87                     if(mbNoDXArray)
88                     {
89                         // evaluate using TextLayouter
90                         fOffset = maTextLayouter.getTextWidth(mrSource.getText(), mrSource.getTextPosition(), nIndex);
91                     }
92                     else
93                     {
94                         // get from DXArray
95                         const sal_uInt32 nIndex2(static_cast< sal_uInt32 >(nIndex - mrSource.getTextPosition()));
96                         fOffset = mrSource.getDXArray()[nIndex2 - 1];
97                     }
98 
99                     // need offset without FontScale for building the new transformation. The
100                     // new transformation will be multiplied with the current text transformation
101                     // so FontScale would be double
102                     double fOffsetNoScale(fOffset);
103                     const double fFontScaleX(maDecTrans.getScale().getX());
104 
105                     if(!basegfx::fTools::equal(fFontScaleX, 1.0)
106                         && !basegfx::fTools::equalZero(fFontScaleX))
107                     {
108                         fOffsetNoScale /= fFontScaleX;
109                     }
110 
111                     // apply needed offset to transformation
112                     aNewTransform.translate(fOffsetNoScale, 0.0);
113 
114                     if(!mbNoDXArray)
115                     {
116                         // DXArray values need to be corrected with the offset, too. Here,
117                         // take the scaled offset since the DXArray is scaled
118                         const sal_uInt32 nArraySize(aNewDXArray.size());
119 
120                         for(sal_uInt32 a(0); a < nArraySize; a++)
121                         {
122                             aNewDXArray[a] -= fOffset;
123                         }
124                     }
125                 }
126 
127                 // add text transformation to new transformation
128                 aNewTransform = maDecTrans.getB2DHomMatrix() * aNewTransform;
129 
130                 // callback to allow evtl. changes
131                 const bool bCreate(allowChange(rTempResult.size(), aNewTransform, nIndex, nLength));
132 
133                 if(bCreate)
134                 {
135                     // check if we have a decorated primitive as source
136                     const TextDecoratedPortionPrimitive2D* pTextDecoratedPortionPrimitive2D =
137                         dynamic_cast< const TextDecoratedPortionPrimitive2D* >(&mrSource);
138 
139                     if(pTextDecoratedPortionPrimitive2D)
140                     {
141                         // create a TextDecoratedPortionPrimitive2D
142                         rTempResult.push_back(
143                             new TextDecoratedPortionPrimitive2D(
144                                 aNewTransform,
145                                 mrSource.getText(),
146                                 nIndex,
147                                 nLength,
148                                 aNewDXArray,
149                                 mrSource.getFontAttribute(),
150                                 mrSource.getLocale(),
151                                 mrSource.getFontColor(),
152 
153                                 pTextDecoratedPortionPrimitive2D->getOverlineColor(),
154                                 pTextDecoratedPortionPrimitive2D->getTextlineColor(),
155                                 pTextDecoratedPortionPrimitive2D->getFontOverline(),
156                                 pTextDecoratedPortionPrimitive2D->getFontUnderline(),
157                                 pTextDecoratedPortionPrimitive2D->getUnderlineAbove(),
158                                 pTextDecoratedPortionPrimitive2D->getTextStrikeout(),
159 
160                                 // reset WordLineMode when BreakupUnit_word is executed; else copy original
161                                 bWordLineMode ? false : pTextDecoratedPortionPrimitive2D->getWordLineMode(),
162 
163                                 pTextDecoratedPortionPrimitive2D->getTextEmphasisMark(),
164                                 pTextDecoratedPortionPrimitive2D->getEmphasisMarkAbove(),
165                                 pTextDecoratedPortionPrimitive2D->getEmphasisMarkBelow(),
166                                 pTextDecoratedPortionPrimitive2D->getTextRelief(),
167                                 pTextDecoratedPortionPrimitive2D->getShadow()));
168                     }
169                     else
170                     {
171                         // create a SimpleTextPrimitive
172                         rTempResult.push_back(
173                             new TextSimplePortionPrimitive2D(
174                                 aNewTransform,
175                                 mrSource.getText(),
176                                 nIndex,
177                                 nLength,
178                                 aNewDXArray,
179                                 mrSource.getFontAttribute(),
180                                 mrSource.getLocale(),
181                                 mrSource.getFontColor()));
182                     }
183                 }
184             }
185         }
186 
allowChange(sal_uInt32,basegfx::B2DHomMatrix &,sal_uInt32,sal_uInt32)187         bool TextBreakupHelper::allowChange(sal_uInt32 /*nCount*/, basegfx::B2DHomMatrix& /*rNewTransform*/, sal_uInt32 /*nIndex*/, sal_uInt32 /*nLength*/)
188         {
189             return true;
190         }
191 
breakup(BreakupUnit aBreakupUnit)192         void TextBreakupHelper::breakup(BreakupUnit aBreakupUnit)
193         {
194             if(mrSource.getTextLength())
195             {
196                 Primitive2DVector aTempResult;
197                 static ::com::sun::star::uno::Reference< ::com::sun::star::i18n::XBreakIterator > xBreakIterator;
198 
199                 if(!xBreakIterator.is())
200                 {
201                     ::com::sun::star::uno::Reference< ::com::sun::star::lang::XMultiServiceFactory > xMSF(::comphelper::getProcessServiceFactory());
202                     xBreakIterator.set(xMSF->createInstance(rtl::OUString::createFromAscii("com.sun.star.i18n.BreakIterator")), ::com::sun::star::uno::UNO_QUERY);
203                 }
204 
205                 if(xBreakIterator.is())
206                 {
207                     const rtl::OUString& rTxt = mrSource.getText();
208                     const sal_Int32 nTextLength(mrSource.getTextLength());
209                     const ::com::sun::star::lang::Locale& rLocale = mrSource.getLocale();
210                     const sal_Int32 nTextPosition(mrSource.getTextPosition());
211                     sal_Int32 nCurrent(nTextPosition);
212 
213                     switch(aBreakupUnit)
214                     {
215                         case BreakupUnit_character:
216                         {
217                             sal_Int32 nDone;
218                             sal_Int32 nNextCellBreak(xBreakIterator->nextCharacters(rTxt, nTextPosition, rLocale, ::com::sun::star::i18n::CharacterIteratorMode::SKIPCELL, 0, nDone));
219                             sal_Int32 a(nTextPosition);
220 
221                             for(; a < nTextPosition + nTextLength; a++)
222                             {
223                                 if(a == nNextCellBreak)
224                                 {
225                                     breakupPortion(aTempResult, nCurrent, a - nCurrent, false);
226                                     nCurrent = a;
227                                     nNextCellBreak = xBreakIterator->nextCharacters(rTxt, a, rLocale, ::com::sun::star::i18n::CharacterIteratorMode::SKIPCELL, 1, nDone);
228                                 }
229                             }
230 
231                             breakupPortion(aTempResult, nCurrent, a - nCurrent, false);
232                             break;
233                         }
234                         case BreakupUnit_word:
235                         {
236                             ::com::sun::star::i18n::Boundary nNextWordBoundary(xBreakIterator->getWordBoundary(rTxt, nTextPosition, rLocale, ::com::sun::star::i18n::WordType::ANY_WORD, sal_True));
237                             sal_Int32 a(nTextPosition);
238 
239                             for(; a < nTextPosition + nTextLength; a++)
240                             {
241                                 if(a == nNextWordBoundary.endPos)
242                                 {
243                                     if(a > nCurrent)
244                                     {
245                                         breakupPortion(aTempResult, nCurrent, a - nCurrent, true);
246                                     }
247 
248                                     nCurrent = a;
249 
250                                     // skip spaces (maybe enhanced with a bool later if needed)
251                                     {
252                                         const sal_Int32 nEndOfSpaces(xBreakIterator->endOfCharBlock(rTxt, a, rLocale, ::com::sun::star::i18n::CharType::SPACE_SEPARATOR));
253 
254                                         if(nEndOfSpaces > a)
255                                         {
256                                             nCurrent = nEndOfSpaces;
257                                         }
258                                     }
259 
260                                     nNextWordBoundary = xBreakIterator->getWordBoundary(rTxt, a + 1, rLocale, ::com::sun::star::i18n::WordType::ANY_WORD, sal_True);
261                                 }
262                             }
263 
264                             if(a > nCurrent)
265                             {
266                                 breakupPortion(aTempResult, nCurrent, a - nCurrent, true);
267                             }
268                             break;
269                         }
270                         case BreakupUnit_sentence:
271                         {
272                             sal_Int32 nNextSentenceBreak(xBreakIterator->endOfSentence(rTxt, nTextPosition, rLocale));
273                             sal_Int32 a(nTextPosition);
274 
275                             for(; a < nTextPosition + nTextLength; a++)
276                             {
277                                 if(a == nNextSentenceBreak)
278                                 {
279                                     breakupPortion(aTempResult, nCurrent, a - nCurrent, false);
280                                     nCurrent = a;
281                                     nNextSentenceBreak = xBreakIterator->endOfSentence(rTxt, a + 1, rLocale);
282                                 }
283                             }
284 
285                             breakupPortion(aTempResult, nCurrent, a - nCurrent, false);
286                             break;
287                         }
288                     }
289                 }
290 
291                 mxResult = Primitive2DVectorToPrimitive2DSequence(aTempResult);
292             }
293         }
294 
getResult(BreakupUnit aBreakupUnit) const295         const Primitive2DSequence& TextBreakupHelper::getResult(BreakupUnit aBreakupUnit) const
296         {
297             if(!mxResult.hasElements())
298             {
299                 const_cast< TextBreakupHelper* >(this)->breakup(aBreakupUnit);
300             }
301 
302             return mxResult;
303         }
304 
305     } // end of namespace primitive2d
306 } // end of namespace drawinglayer
307 
308 //////////////////////////////////////////////////////////////////////////////
309 // eof
310