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