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