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