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 <sal/config.h> 21 22 #include <algorithm> 23 24 #include <drawinglayer/primitive2d/textlayoutdevice.hxx> 25 #include <comphelper/processfactory.hxx> 26 #include <comphelper/unique_disposing_ptr.hxx> 27 #include <tools/gen.hxx> 28 #include <vcl/canvastools.hxx> 29 #include <vcl/timer.hxx> 30 #include <vcl/virdev.hxx> 31 #include <vcl/font.hxx> 32 #include <vcl/metric.hxx> 33 #include <i18nlangtag/languagetag.hxx> 34 #include <drawinglayer/primitive2d/textprimitive2d.hxx> 35 #include <vcl/svapp.hxx> 36 #include <o3tl/deleter.hxx> 37 38 39 // VDev RevDevice provider 40 41 namespace 42 { 43 class ImpTimedRefDev; 44 45 //the scoped_timed_RefDev owns an ImpTimeRefDev and releases it on dtor 46 //or disposing of the default XComponentContext which causes the underlying 47 //OutputDevice to get released 48 49 //The ImpTimerRefDev itself, if the timeout ever gets hit, will call 50 //reset on the scoped_timed_RefDev to release the ImpTimerRefDev early 51 //if its unused for a few minutes 52 class scoped_timed_RefDev : public comphelper::unique_disposing_ptr<ImpTimedRefDev> 53 { 54 public: scoped_timed_RefDev()55 scoped_timed_RefDev() : comphelper::unique_disposing_ptr<ImpTimedRefDev>((css::uno::Reference<css::lang::XComponent>(::comphelper::getProcessComponentContext(), css::uno::UNO_QUERY_THROW))) 56 { 57 } 58 }; 59 60 class the_scoped_timed_RefDev : public rtl::Static<scoped_timed_RefDev, the_scoped_timed_RefDev> {}; 61 62 class ImpTimedRefDev : public Timer 63 { 64 scoped_timed_RefDev& mrOwnerOfMe; 65 VclPtr<VirtualDevice> mpVirDev; 66 sal_uInt32 mnUseCount; 67 68 public: 69 explicit ImpTimedRefDev(scoped_timed_RefDev& rOwnerofMe); 70 virtual ~ImpTimedRefDev() override; 71 virtual void Invoke() override; 72 73 VirtualDevice& acquireVirtualDevice(); 74 void releaseVirtualDevice(); 75 }; 76 ImpTimedRefDev(scoped_timed_RefDev & rOwnerOfMe)77 ImpTimedRefDev::ImpTimedRefDev(scoped_timed_RefDev& rOwnerOfMe) 78 : Timer( "drawinglayer ImpTimedRefDev destroy mpVirDev" ), 79 mrOwnerOfMe(rOwnerOfMe), 80 mpVirDev(nullptr), 81 mnUseCount(0) 82 { 83 SetTimeout(3L * 60L * 1000L); // three minutes 84 Start(); 85 } 86 ~ImpTimedRefDev()87 ImpTimedRefDev::~ImpTimedRefDev() 88 { 89 OSL_ENSURE(0 == mnUseCount, "destruction of a still used ImpTimedRefDev (!)"); 90 const SolarMutexGuard aSolarGuard; 91 mpVirDev.disposeAndClear(); 92 } 93 Invoke()94 void ImpTimedRefDev::Invoke() 95 { 96 // for obvious reasons, do not call anything after this 97 mrOwnerOfMe.reset(); 98 } 99 acquireVirtualDevice()100 VirtualDevice& ImpTimedRefDev::acquireVirtualDevice() 101 { 102 if(!mpVirDev) 103 { 104 mpVirDev = VclPtr<VirtualDevice>::Create(); 105 mpVirDev->SetReferenceDevice( VirtualDevice::RefDevMode::MSO1 ); 106 } 107 108 if(!mnUseCount) 109 { 110 Stop(); 111 } 112 113 mnUseCount++; 114 115 return *mpVirDev; 116 } 117 releaseVirtualDevice()118 void ImpTimedRefDev::releaseVirtualDevice() 119 { 120 OSL_ENSURE(mnUseCount, "mismatch call number to releaseVirtualDevice() (!)"); 121 mnUseCount--; 122 123 if(!mnUseCount) 124 { 125 Start(); 126 } 127 } 128 } // end of anonymous namespace 129 130 131 // access to one global ImpTimedRefDev incarnation in namespace drawinglayer::primitive 132 133 namespace drawinglayer 134 { 135 namespace primitive2d 136 { 137 // static methods here acquireGlobalVirtualDevice()138 static VirtualDevice& acquireGlobalVirtualDevice() 139 { 140 scoped_timed_RefDev& rStdRefDevice = the_scoped_timed_RefDev::get(); 141 142 if(!rStdRefDevice) 143 rStdRefDevice.reset(new ImpTimedRefDev(rStdRefDevice)); 144 145 return rStdRefDevice->acquireVirtualDevice(); 146 } 147 releaseGlobalVirtualDevice()148 static void releaseGlobalVirtualDevice() 149 { 150 scoped_timed_RefDev& rStdRefDevice = the_scoped_timed_RefDev::get(); 151 152 OSL_ENSURE(rStdRefDevice, "releaseGlobalVirtualDevice() without prior acquireGlobalVirtualDevice() call(!)"); 153 rStdRefDevice->releaseVirtualDevice(); 154 } 155 TextLayouterDevice()156 TextLayouterDevice::TextLayouterDevice() 157 : maSolarGuard(), 158 mrDevice(acquireGlobalVirtualDevice()) 159 { 160 } 161 ~TextLayouterDevice()162 TextLayouterDevice::~TextLayouterDevice() COVERITY_NOEXCEPT_FALSE 163 { 164 releaseGlobalVirtualDevice(); 165 } 166 setFont(const vcl::Font & rFont)167 void TextLayouterDevice::setFont(const vcl::Font& rFont) 168 { 169 mrDevice.SetFont( rFont ); 170 } 171 setFontAttribute(const attribute::FontAttribute & rFontAttribute,double fFontScaleX,double fFontScaleY,const css::lang::Locale & rLocale)172 void TextLayouterDevice::setFontAttribute( 173 const attribute::FontAttribute& rFontAttribute, 174 double fFontScaleX, 175 double fFontScaleY, 176 const css::lang::Locale& rLocale) 177 { 178 setFont(getVclFontFromFontAttribute( 179 rFontAttribute, 180 fFontScaleX, 181 fFontScaleY, 182 0.0, 183 rLocale)); 184 } 185 getOverlineOffset() const186 double TextLayouterDevice::getOverlineOffset() const 187 { 188 const ::FontMetric& rMetric = mrDevice.GetFontMetric(); 189 double fRet = (rMetric.GetInternalLeading() / 2.0) - rMetric.GetAscent(); 190 return fRet; 191 } 192 getUnderlineOffset() const193 double TextLayouterDevice::getUnderlineOffset() const 194 { 195 const ::FontMetric& rMetric = mrDevice.GetFontMetric(); 196 double fRet = rMetric.GetDescent() / 2.0; 197 return fRet; 198 } 199 getStrikeoutOffset() const200 double TextLayouterDevice::getStrikeoutOffset() const 201 { 202 const ::FontMetric& rMetric = mrDevice.GetFontMetric(); 203 double fRet = (rMetric.GetAscent() - rMetric.GetInternalLeading()) / 3.0; 204 return fRet; 205 } 206 getOverlineHeight() const207 double TextLayouterDevice::getOverlineHeight() const 208 { 209 const ::FontMetric& rMetric = mrDevice.GetFontMetric(); 210 double fRet = rMetric.GetInternalLeading() / 2.5; 211 return fRet; 212 } 213 getUnderlineHeight() const214 double TextLayouterDevice::getUnderlineHeight() const 215 { 216 const ::FontMetric& rMetric = mrDevice.GetFontMetric(); 217 double fRet = rMetric.GetDescent() / 4.0; 218 return fRet; 219 } 220 getTextHeight() const221 double TextLayouterDevice::getTextHeight() const 222 { 223 return mrDevice.GetTextHeight(); 224 } 225 getTextWidth(const OUString & rText,sal_uInt32 nIndex,sal_uInt32 nLength) const226 double TextLayouterDevice::getTextWidth( 227 const OUString& rText, 228 sal_uInt32 nIndex, 229 sal_uInt32 nLength) const 230 { 231 return mrDevice.GetTextWidth(rText, nIndex, nLength); 232 } 233 getTextOutlines(basegfx::B2DPolyPolygonVector & rB2DPolyPolyVector,const OUString & rText,sal_uInt32 nIndex,sal_uInt32 nLength,const std::vector<double> & rDXArray) const234 void TextLayouterDevice::getTextOutlines( 235 basegfx::B2DPolyPolygonVector& rB2DPolyPolyVector, 236 const OUString& rText, 237 sal_uInt32 nIndex, 238 sal_uInt32 nLength, 239 const std::vector< double >& rDXArray) const 240 { 241 const sal_uInt32 nDXArrayCount(rDXArray.size()); 242 sal_uInt32 nTextLength(nLength); 243 const sal_uInt32 nStringLength(rText.getLength()); 244 245 if(nTextLength + nIndex > nStringLength) 246 { 247 nTextLength = nStringLength - nIndex; 248 } 249 250 if(nDXArrayCount) 251 { 252 OSL_ENSURE(nDXArrayCount == nTextLength, "DXArray size does not correspond to text portion size (!)"); 253 std::vector< long > aIntegerDXArray(nDXArrayCount); 254 255 for(sal_uInt32 a(0); a < nDXArrayCount; a++) 256 { 257 aIntegerDXArray[a] = basegfx::fround(rDXArray[a]); 258 } 259 260 mrDevice.GetTextOutlines( 261 rB2DPolyPolyVector, 262 rText, 263 nIndex, 264 nIndex, 265 nLength, 266 0, 267 aIntegerDXArray.data()); 268 } 269 else 270 { 271 mrDevice.GetTextOutlines( 272 rB2DPolyPolyVector, 273 rText, 274 nIndex, 275 nIndex, 276 nLength); 277 } 278 } 279 getTextBoundRect(const OUString & rText,sal_uInt32 nIndex,sal_uInt32 nLength) const280 basegfx::B2DRange TextLayouterDevice::getTextBoundRect( 281 const OUString& rText, 282 sal_uInt32 nIndex, 283 sal_uInt32 nLength) const 284 { 285 sal_uInt32 nTextLength(nLength); 286 const sal_uInt32 nStringLength(rText.getLength()); 287 288 if(nTextLength + nIndex > nStringLength) 289 { 290 nTextLength = nStringLength - nIndex; 291 } 292 293 if(nTextLength) 294 { 295 ::tools::Rectangle aRect; 296 297 mrDevice.GetTextBoundRect( 298 aRect, 299 rText, 300 nIndex, 301 nIndex, 302 nLength); 303 304 // #i104432#, #i102556# take empty results into account 305 if(!aRect.IsEmpty()) 306 { 307 return vcl::unotools::b2DRectangleFromRectangle(aRect); 308 } 309 } 310 311 return basegfx::B2DRange(); 312 } 313 getFontAscent() const314 double TextLayouterDevice::getFontAscent() const 315 { 316 const ::FontMetric& rMetric = mrDevice.GetFontMetric(); 317 return rMetric.GetAscent(); 318 } 319 getFontDescent() const320 double TextLayouterDevice::getFontDescent() const 321 { 322 const ::FontMetric& rMetric = mrDevice.GetFontMetric(); 323 return rMetric.GetDescent(); 324 } 325 addTextRectActions(const::tools::Rectangle & rRectangle,const OUString & rText,DrawTextFlags nStyle,GDIMetaFile & rGDIMetaFile) const326 void TextLayouterDevice::addTextRectActions( 327 const ::tools::Rectangle& rRectangle, 328 const OUString& rText, 329 DrawTextFlags nStyle, 330 GDIMetaFile& rGDIMetaFile) const 331 { 332 mrDevice.AddTextRectActions( 333 rRectangle, rText, nStyle, rGDIMetaFile); 334 } 335 getTextArray(const OUString & rText,sal_uInt32 nIndex,sal_uInt32 nLength) const336 std::vector< double > TextLayouterDevice::getTextArray( 337 const OUString& rText, 338 sal_uInt32 nIndex, 339 sal_uInt32 nLength) const 340 { 341 std::vector< double > aRetval; 342 sal_uInt32 nTextLength(nLength); 343 const sal_uInt32 nStringLength(rText.getLength()); 344 345 if(nTextLength + nIndex > nStringLength) 346 { 347 nTextLength = nStringLength - nIndex; 348 } 349 350 if(nTextLength) 351 { 352 aRetval.reserve(nTextLength); 353 std::vector<long> aArray(nTextLength); 354 mrDevice.GetTextArray(rText, aArray.data(), nIndex, nLength); 355 aRetval.assign(aArray.begin(), aArray.end()); 356 } 357 358 return aRetval; 359 } 360 361 } // end of namespace primitive2d 362 } // end of namespace drawinglayer 363 364 365 // helper methods for vcl font handling 366 367 namespace drawinglayer 368 { 369 namespace primitive2d 370 { getVclFontFromFontAttribute(const attribute::FontAttribute & rFontAttribute,double fFontScaleX,double fFontScaleY,double fFontRotation,const css::lang::Locale & rLocale)371 vcl::Font getVclFontFromFontAttribute( 372 const attribute::FontAttribute& rFontAttribute, 373 double fFontScaleX, 374 double fFontScaleY, 375 double fFontRotation, 376 const css::lang::Locale& rLocale) 377 { 378 // detect FontScaling 379 const sal_uInt32 nHeight(basegfx::fround(fabs(fFontScaleY))); 380 const sal_uInt32 nWidth(basegfx::fround(fabs(fFontScaleX))); 381 const bool bFontIsScaled(nHeight != nWidth); 382 383 #ifdef _WIN32 384 // for WIN32 systems, start with creating an unscaled font. If FontScaling 385 // is wanted, that width needs to be adapted using FontMetric again to get a 386 // width of the unscaled font 387 vcl::Font aRetval( 388 rFontAttribute.getFamilyName(), 389 rFontAttribute.getStyleName(), 390 Size(0, nHeight)); 391 #else 392 // for non-WIN32 systems things are easier since these accept a Font creation 393 // with initially nWidth != nHeight for FontScaling. Despite that, use zero for 394 // FontWidth when no scaling is used to explicitly have that zero when e.g. the 395 // Font would be recorded in a MetaFile (The MetaFile FontAction WILL record a 396 // set FontWidth; import that in a WIN32 system, and trouble is there) 397 vcl::Font aRetval( 398 rFontAttribute.getFamilyName(), 399 rFontAttribute.getStyleName(), 400 Size(bFontIsScaled ? std::max<sal_uInt32>(nWidth, 1) : 0, nHeight)); 401 #endif 402 // define various other FontAttribute 403 aRetval.SetAlignment(ALIGN_BASELINE); 404 aRetval.SetCharSet(rFontAttribute.getSymbol() ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE); 405 aRetval.SetVertical(rFontAttribute.getVertical()); 406 aRetval.SetWeight(static_cast<FontWeight>(rFontAttribute.getWeight())); 407 aRetval.SetItalic(rFontAttribute.getItalic() ? ITALIC_NORMAL : ITALIC_NONE); 408 aRetval.SetOutline(rFontAttribute.getOutline()); 409 aRetval.SetPitch(rFontAttribute.getMonospaced() ? PITCH_FIXED : PITCH_VARIABLE); 410 aRetval.SetLanguage(LanguageTag::convertToLanguageType( rLocale, false)); 411 412 // handle FontRotation (if defined) 413 if(!basegfx::fTools::equalZero(fFontRotation)) 414 { 415 sal_Int16 aRotate10th(static_cast<sal_Int16>(fFontRotation * (-1800.0/F_PI))); 416 aRetval.SetOrientation(aRotate10th % 3600); 417 } 418 419 return aRetval; 420 } 421 getFontAttributeFromVclFont(basegfx::B2DVector & o_rSize,const vcl::Font & rFont,bool bRTL,bool bBiDiStrong)422 attribute::FontAttribute getFontAttributeFromVclFont( 423 basegfx::B2DVector& o_rSize, 424 const vcl::Font& rFont, 425 bool bRTL, 426 bool bBiDiStrong) 427 { 428 const attribute::FontAttribute aRetval( 429 rFont.GetFamilyName(), 430 rFont.GetStyleName(), 431 static_cast<sal_uInt16>(rFont.GetWeight()), 432 RTL_TEXTENCODING_SYMBOL == rFont.GetCharSet(), 433 rFont.IsVertical(), 434 ITALIC_NONE != rFont.GetItalic(), 435 PITCH_FIXED == rFont.GetPitch(), 436 rFont.IsOutline(), 437 bRTL, 438 bBiDiStrong); 439 // TODO: eKerning 440 441 // set FontHeight and init to no FontScaling 442 o_rSize.setY(std::max<long>(rFont.GetFontSize().getHeight(), 0)); 443 o_rSize.setX(o_rSize.getY()); 444 445 #ifdef _WIN32 446 // for WIN32 systems, the FontScaling at the Font is detected by 447 // checking that FontWidth != 0. When FontScaling is used, WIN32 448 // needs to do extra stuff to detect the correct width (since it's 449 // zero and not equal the font height) and its relationship to 450 // the height 451 if(rFont.GetFontSize().getWidth() > 0) 452 { 453 vcl::Font aUnscaledFont(rFont); 454 aUnscaledFont.SetAverageFontWidth(0); 455 const FontMetric aUnscaledFontMetric(Application::GetDefaultDevice()->GetFontMetric(aUnscaledFont)); 456 457 if(aUnscaledFontMetric.GetAverageFontWidth() > 0) 458 { 459 const double fScaleFactor(static_cast<double>(rFont.GetFontSize().getWidth()) / static_cast<double>(aUnscaledFontMetric.GetAverageFontWidth())); 460 o_rSize.setX(fScaleFactor * o_rSize.getY()); 461 } 462 } 463 #else 464 // For non-WIN32 systems the detection is the same, but the value 465 // is easier achieved since width == height is interpreted as no 466 // scaling. Ergo, Width == 0 means width == height, and width != 0 467 // means the scaling is in the direct relation of width to height 468 if(rFont.GetFontSize().getWidth() > 0) 469 { 470 o_rSize.setX(static_cast<double>(rFont.GetFontSize().getWidth())); 471 } 472 #endif 473 return aRetval; 474 } 475 } // end of namespace primitive2d 476 } // end of namespace drawinglayer 477 478 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 479