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/textprimitive2d.hxx> 21 #include <drawinglayer/primitive2d/textlayoutdevice.hxx> 22 #include <basegfx/polygon/b2dpolypolygon.hxx> 23 #include <drawinglayer/primitive2d/polypolygonprimitive2d.hxx> 24 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> 25 #include <drawinglayer/primitive2d/texteffectprimitive2d.hxx> 26 #include <basegfx/matrix/b2dhommatrixtools.hxx> 27 28 29 using namespace com::sun::star; 30 31 32 namespace 33 { 34 // adapts fontScale for usage with TextLayouter. Input is rScale which is the extracted 35 // scale from a text transformation. A copy is modified so that it contains only positive 36 // scalings and XY-equal scalings to allow to get a non-X-scaled Vcl-Font for TextLayouter. 37 // rScale is adapted accordingly to contain the corrected scale which would need to be 38 // applied to e.g. outlines received from TextLayouter under usage of fontScale. This 39 // includes Y-Scale, X-Scale-correction and mirrorings. getCorrectedScaleAndFontScale(basegfx::B2DVector & rScale)40 basegfx::B2DVector getCorrectedScaleAndFontScale(basegfx::B2DVector& rScale) 41 { 42 // copy input value 43 basegfx::B2DVector aFontScale(rScale); 44 45 // correct FontHeight settings 46 if(basegfx::fTools::equalZero(aFontScale.getY())) 47 { 48 // no font height; choose one and adapt scale to get back to original scaling 49 static const double fDefaultFontScale(100.0); 50 rScale.setY(1.0 / fDefaultFontScale); 51 aFontScale.setY(fDefaultFontScale); 52 } 53 else if(basegfx::fTools::less(aFontScale.getY(), 0.0)) 54 { 55 // negative font height; invert and adapt scale to get back to original scaling 56 aFontScale.setY(-aFontScale.getY()); 57 rScale.setY(-1.0); 58 } 59 else 60 { 61 // positive font height; adapt scale; scaling will be part of the polygons 62 rScale.setY(1.0); 63 } 64 65 // correct FontWidth settings 66 if(basegfx::fTools::equal(aFontScale.getX(), aFontScale.getY())) 67 { 68 // no FontScale, adapt scale 69 rScale.setX(1.0); 70 } 71 else 72 { 73 // If FontScale is used, force to no FontScale to get a non-scaled VCL font. 74 // Adapt scaling in X accordingly. 75 rScale.setX(aFontScale.getX() / aFontScale.getY()); 76 aFontScale.setX(aFontScale.getY()); 77 } 78 79 return aFontScale; 80 } 81 } // end of anonymous namespace 82 83 84 namespace drawinglayer 85 { 86 namespace primitive2d 87 { getTextOutlinesAndTransformation(basegfx::B2DPolyPolygonVector & rTarget,basegfx::B2DHomMatrix & rTransformation) const88 void TextSimplePortionPrimitive2D::getTextOutlinesAndTransformation(basegfx::B2DPolyPolygonVector& rTarget, basegfx::B2DHomMatrix& rTransformation) const 89 { 90 if(getTextLength()) 91 { 92 // decompose object transformation to single values 93 basegfx::B2DVector aScale, aTranslate; 94 double fRotate, fShearX; 95 96 // if decomposition returns false, create no geometry since e.g. scaling may 97 // be zero 98 if (getTextTransform().decompose(aScale, aTranslate, fRotate, fShearX) && aScale.getX() != 0.0) 99 { 100 // handle special case: If scale is negative in (x,y) (3rd quadrant), it can 101 // be expressed as rotation by PI 102 if(basegfx::fTools::less(aScale.getX(), 0.0) && basegfx::fTools::less(aScale.getY(), 0.0)) 103 { 104 aScale = basegfx::absolute(aScale); 105 fRotate += F_PI; 106 } 107 108 // for the TextLayouterDevice, it is necessary to have a scaling representing 109 // the font size. Since we want to extract polygons here, it is okay to 110 // work just with scaling and to ignore shear, rotation and translation, 111 // all that can be applied to the polygons later 112 const basegfx::B2DVector aFontScale(getCorrectedScaleAndFontScale(aScale)); 113 114 // prepare textlayoutdevice 115 TextLayouterDevice aTextLayouter; 116 aTextLayouter.setFontAttribute( 117 getFontAttribute(), 118 aFontScale.getX(), 119 aFontScale.getY(), 120 getLocale()); 121 122 // When getting outlines from stretched text (aScale.getX() != 1.0) it 123 // is necessary to inverse-scale the DXArray (if used) to not get the 124 // outlines already aligned to given, but wrong DXArray 125 if(!getDXArray().empty() && !basegfx::fTools::equal(aScale.getX(), 1.0)) 126 { 127 std::vector< double > aScaledDXArray = getDXArray(); 128 const double fDXArrayScale(1.0 / aScale.getX()); 129 130 for(double & a : aScaledDXArray) 131 { 132 a *= fDXArrayScale; 133 } 134 135 // get the text outlines 136 aTextLayouter.getTextOutlines( 137 rTarget, 138 getText(), 139 getTextPosition(), 140 getTextLength(), 141 aScaledDXArray); 142 } 143 else 144 { 145 // get the text outlines 146 aTextLayouter.getTextOutlines( 147 rTarget, 148 getText(), 149 getTextPosition(), 150 getTextLength(), 151 getDXArray()); 152 } 153 154 // create primitives for the outlines 155 const sal_uInt32 nCount(rTarget.size()); 156 157 if(nCount) 158 { 159 // prepare object transformation for polygons 160 rTransformation = basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( 161 aScale, fShearX, fRotate, aTranslate); 162 } 163 } 164 } 165 } 166 create2DDecomposition(Primitive2DContainer & rContainer,const geometry::ViewInformation2D &) const167 void TextSimplePortionPrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const 168 { 169 if(!getTextLength()) 170 return; 171 172 Primitive2DContainer aRetval; 173 basegfx::B2DPolyPolygonVector aB2DPolyPolyVector; 174 basegfx::B2DHomMatrix aPolygonTransform; 175 176 // get text outlines and their object transformation 177 getTextOutlinesAndTransformation(aB2DPolyPolyVector, aPolygonTransform); 178 179 // create primitives for the outlines 180 const sal_uInt32 nCount(aB2DPolyPolyVector.size()); 181 182 if(!nCount) 183 return; 184 185 // alloc space for the primitives 186 aRetval.resize(nCount); 187 188 // color-filled polypolygons 189 for(sal_uInt32 a(0); a < nCount; a++) 190 { 191 // prepare polypolygon 192 basegfx::B2DPolyPolygon& rPolyPolygon = aB2DPolyPolyVector[a]; 193 rPolyPolygon.transform(aPolygonTransform); 194 aRetval[a] = new PolyPolygonColorPrimitive2D(rPolyPolygon, getFontColor()); 195 } 196 197 if(getFontAttribute().getOutline()) 198 { 199 // decompose polygon transformation to single values 200 basegfx::B2DVector aScale, aTranslate; 201 double fRotate, fShearX; 202 aPolygonTransform.decompose(aScale, aTranslate, fRotate, fShearX); 203 204 // create outline text effect with current content and replace 205 Primitive2DReference aNewTextEffect(new TextEffectPrimitive2D( 206 aRetval, 207 aTranslate, 208 fRotate, 209 TextEffectStyle2D::Outline)); 210 211 aRetval = Primitive2DContainer { aNewTextEffect }; 212 } 213 214 rContainer.insert(rContainer.end(), aRetval.begin(), aRetval.end()); 215 } 216 TextSimplePortionPrimitive2D(const basegfx::B2DHomMatrix & rNewTransform,const OUString & rText,sal_Int32 nTextPosition,sal_Int32 nTextLength,const std::vector<double> & rDXArray,const attribute::FontAttribute & rFontAttribute,const css::lang::Locale & rLocale,const basegfx::BColor & rFontColor,bool bFilled,long nWidthToFill,const Color & rTextFillColor)217 TextSimplePortionPrimitive2D::TextSimplePortionPrimitive2D( 218 const basegfx::B2DHomMatrix& rNewTransform, 219 const OUString& rText, 220 sal_Int32 nTextPosition, 221 sal_Int32 nTextLength, 222 const std::vector< double >& rDXArray, 223 const attribute::FontAttribute& rFontAttribute, 224 const css::lang::Locale& rLocale, 225 const basegfx::BColor& rFontColor, 226 bool bFilled, 227 long nWidthToFill, 228 const Color& rTextFillColor) 229 : BufferedDecompositionPrimitive2D(), 230 maTextTransform(rNewTransform), 231 maText(rText), 232 mnTextPosition(nTextPosition), 233 mnTextLength(nTextLength), 234 maDXArray(rDXArray), 235 maFontAttribute(rFontAttribute), 236 maLocale(rLocale), 237 maFontColor(rFontColor), 238 mbFilled(bFilled), 239 mnWidthToFill(nWidthToFill), 240 maTextFillColor(rTextFillColor), 241 maB2DRange() 242 { 243 #if OSL_DEBUG_LEVEL > 0 244 const sal_Int32 aStringLength(getText().getLength()); 245 OSL_ENSURE(aStringLength >= getTextPosition() && aStringLength >= getTextPosition() + getTextLength(), 246 "TextSimplePortionPrimitive2D with text out of range (!)"); 247 #endif 248 } 249 LocalesAreEqual(const css::lang::Locale & rA,const css::lang::Locale & rB)250 bool LocalesAreEqual(const css::lang::Locale& rA, const css::lang::Locale& rB) 251 { 252 return (rA.Language == rB.Language 253 && rA.Country == rB.Country 254 && rA.Variant == rB.Variant); 255 } 256 operator ==(const BasePrimitive2D & rPrimitive) const257 bool TextSimplePortionPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const 258 { 259 if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) 260 { 261 const TextSimplePortionPrimitive2D& rCompare = static_cast<const TextSimplePortionPrimitive2D&>(rPrimitive); 262 263 return (getTextTransform() == rCompare.getTextTransform() 264 && getText() == rCompare.getText() 265 && getTextPosition() == rCompare.getTextPosition() 266 && getTextLength() == rCompare.getTextLength() 267 && getDXArray() == rCompare.getDXArray() 268 && getFontAttribute() == rCompare.getFontAttribute() 269 && LocalesAreEqual(getLocale(), rCompare.getLocale()) 270 && getFontColor() == rCompare.getFontColor() 271 && mbFilled == rCompare.mbFilled 272 && mnWidthToFill == rCompare.mnWidthToFill 273 && maTextFillColor == rCompare.maTextFillColor); 274 } 275 276 return false; 277 } 278 getB2DRange(const geometry::ViewInformation2D &) const279 basegfx::B2DRange TextSimplePortionPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const 280 { 281 if(maB2DRange.isEmpty() && getTextLength()) 282 { 283 // get TextBoundRect as base size 284 // decompose object transformation to single values 285 basegfx::B2DVector aScale, aTranslate; 286 double fRotate, fShearX; 287 288 if(getTextTransform().decompose(aScale, aTranslate, fRotate, fShearX)) 289 { 290 // for the TextLayouterDevice, it is necessary to have a scaling representing 291 // the font size. Since we want to extract polygons here, it is okay to 292 // work just with scaling and to ignore shear, rotation and translation, 293 // all that can be applied to the polygons later 294 const basegfx::B2DVector aFontScale(getCorrectedScaleAndFontScale(aScale)); 295 296 // prepare textlayoutdevice 297 TextLayouterDevice aTextLayouter; 298 aTextLayouter.setFontAttribute( 299 getFontAttribute(), 300 aFontScale.getX(), 301 aFontScale.getY(), 302 getLocale()); 303 304 // get basic text range 305 basegfx::B2DRange aNewRange(aTextLayouter.getTextBoundRect(getText(), getTextPosition(), getTextLength())); 306 307 // #i104432#, #i102556# take empty results into account 308 if(!aNewRange.isEmpty()) 309 { 310 // prepare object transformation for range 311 const basegfx::B2DHomMatrix aRangeTransformation(basegfx::utils::createScaleShearXRotateTranslateB2DHomMatrix( 312 aScale, fShearX, fRotate, aTranslate)); 313 314 // apply range transformation to it 315 aNewRange.transform(aRangeTransformation); 316 317 // assign to buffered value 318 const_cast< TextSimplePortionPrimitive2D* >(this)->maB2DRange = aNewRange; 319 } 320 } 321 } 322 323 return maB2DRange; 324 } 325 326 // provide unique ID 327 ImplPrimitive2DIDBlock(TextSimplePortionPrimitive2D, PRIMITIVE2D_ID_TEXTSIMPLEPORTIONPRIMITIVE2D) 328 329 } // end of namespace primitive2d 330 } // end of namespace drawinglayer 331 332 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 333