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