1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/config.h>
24 #include <com/sun/star/uno/XComponentContext.hpp>
26 #include <basegfx/matrix/b2dhommatrixtools.hxx>
27 #include <drawinglayer/attribute/fontattribute.hxx>
28 #include <drawinglayer/primitive2d/textlayoutdevice.hxx>
29 #include <comphelper/processfactory.hxx>
30 #include <comphelper/unique_disposing_ptr.hxx>
31 #include <osl/diagnose.h>
32 #include <tools/gen.hxx>
33 #include <vcl/canvastools.hxx>
34 #include <vcl/kernarray.hxx>
35 #include <vcl/timer.hxx>
36 #include <vcl/virdev.hxx>
37 #include <vcl/font.hxx>
38 #include <vcl/metric.hxx>
39 #include <i18nlangtag/languagetag.hxx>
40 #include <vcl/svapp.hxx>
41 #include <vcl/vcllayout.hxx>
42 #include <vcl/glyphitemcache.hxx>
44 namespace drawinglayer::primitive2d
50 // VDev RevDevice provider
52 //the scoped_timed_RefDev owns an ImpTimeRefDev and releases it on dtor
53 //or disposing of the default XComponentContext which causes the underlying
54 //OutputDevice to get released
56 //The ImpTimerRefDev itself, if the timeout ever gets hit, will call
57 //reset on the scoped_timed_RefDev to release the ImpTimerRefDev early
58 //if it's unused for a few minutes
59 class scoped_timed_RefDev
: public comphelper::unique_disposing_ptr
<ImpTimedRefDev
>
63 : comphelper::unique_disposing_ptr
<ImpTimedRefDev
>(
64 (css::uno::Reference
<css::lang::XComponent
>(
65 ::comphelper::getProcessComponentContext(), css::uno::UNO_QUERY_THROW
)))
70 class the_scoped_timed_RefDev
: public rtl::Static
<scoped_timed_RefDev
, the_scoped_timed_RefDev
>
74 class ImpTimedRefDev
: public Timer
76 scoped_timed_RefDev
& mrOwnerOfMe
;
77 VclPtr
<VirtualDevice
> mpVirDev
;
78 sal_uInt32 mnUseCount
;
81 explicit ImpTimedRefDev(scoped_timed_RefDev
& rOwnerofMe
);
82 virtual ~ImpTimedRefDev() override
;
83 virtual void Invoke() override
;
85 VirtualDevice
& acquireVirtualDevice();
86 void releaseVirtualDevice();
89 ImpTimedRefDev::ImpTimedRefDev(scoped_timed_RefDev
& rOwnerOfMe
)
90 : Timer("drawinglayer ImpTimedRefDev destroy mpVirDev")
91 , mrOwnerOfMe(rOwnerOfMe
)
95 SetTimeout(3L * 60L * 1000L); // three minutes
99 ImpTimedRefDev::~ImpTimedRefDev()
101 OSL_ENSURE(0 == mnUseCount
, "destruction of a still used ImpTimedRefDev (!)");
102 const SolarMutexGuard aSolarGuard
;
103 mpVirDev
.disposeAndClear();
106 void ImpTimedRefDev::Invoke()
108 // for obvious reasons, do not call anything after this
112 VirtualDevice
& ImpTimedRefDev::acquireVirtualDevice()
116 mpVirDev
= VclPtr
<VirtualDevice
>::Create();
117 mpVirDev
->SetReferenceDevice(VirtualDevice::RefDevMode::MSO1
);
130 void ImpTimedRefDev::releaseVirtualDevice()
132 OSL_ENSURE(mnUseCount
, "mismatch call number to releaseVirtualDevice() (!)");
141 VirtualDevice
& acquireGlobalVirtualDevice()
143 scoped_timed_RefDev
& rStdRefDevice
= the_scoped_timed_RefDev::get();
146 rStdRefDevice
.reset(new ImpTimedRefDev(rStdRefDevice
));
148 return rStdRefDevice
->acquireVirtualDevice();
151 void releaseGlobalVirtualDevice()
153 scoped_timed_RefDev
& rStdRefDevice
= the_scoped_timed_RefDev::get();
155 OSL_ENSURE(rStdRefDevice
,
156 "releaseGlobalVirtualDevice() without prior acquireGlobalVirtualDevice() call(!)");
157 rStdRefDevice
->releaseVirtualDevice();
160 } // end of anonymous namespace
162 TextLayouterDevice::TextLayouterDevice()
163 : mrDevice(acquireGlobalVirtualDevice())
167 TextLayouterDevice::~TextLayouterDevice() COVERITY_NOEXCEPT_FALSE
{ releaseGlobalVirtualDevice(); }
169 void TextLayouterDevice::setFont(const vcl::Font
& rFont
)
171 mrDevice
.SetFont(rFont
);
172 mnFontScalingFixX
= 1.0;
173 mnFontScalingFixY
= 1.0;
176 void TextLayouterDevice::setFontAttribute(const attribute::FontAttribute
& rFontAttribute
,
177 double fFontScaleX
, double fFontScaleY
,
178 const css::lang::Locale
& rLocale
)
181 = getVclFontFromFontAttribute(rFontAttribute
, fFontScaleX
, fFontScaleY
, 0.0, rLocale
);
183 Size aFontSize
= aFont
.GetFontSize();
184 if (aFontSize
.Height())
186 mnFontScalingFixY
= fFontScaleY
/ aFontSize
.Height();
187 // aFontSize.Width() is 0 for uninformly scaled fonts: see getVclFontFromFontAttribute
189 = fFontScaleX
/ (aFontSize
.Width() ? aFontSize
.Width() : aFontSize
.Height());
193 mnFontScalingFixX
= mnFontScalingFixY
= 1.0;
197 void TextLayouterDevice::setLayoutMode(vcl::text::ComplexTextLayoutFlags nTextLayoutMode
)
199 mrDevice
.SetLayoutMode(nTextLayoutMode
);
202 vcl::text::ComplexTextLayoutFlags
TextLayouterDevice::getLayoutMode() const
204 return mrDevice
.GetLayoutMode();
207 void TextLayouterDevice::setTextColor(const basegfx::BColor
& rColor
)
209 mrDevice
.SetTextColor(Color(rColor
));
212 double TextLayouterDevice::getOverlineOffset() const
214 const ::FontMetric aMetric
= mrDevice
.GetFontMetric();
215 double fRet
= (aMetric
.GetInternalLeading() / 2.0) - aMetric
.GetAscent();
216 return fRet
* mnFontScalingFixY
;
219 double TextLayouterDevice::getUnderlineOffset() const
221 const ::FontMetric aMetric
= mrDevice
.GetFontMetric();
222 double fRet
= aMetric
.GetDescent() / 2.0;
223 return fRet
* mnFontScalingFixY
;
226 double TextLayouterDevice::getStrikeoutOffset() const
228 const ::FontMetric aMetric
= mrDevice
.GetFontMetric();
229 double fRet
= (aMetric
.GetAscent() - aMetric
.GetInternalLeading()) / 3.0;
230 return fRet
* mnFontScalingFixY
;
233 double TextLayouterDevice::getOverlineHeight() const
235 const ::FontMetric aMetric
= mrDevice
.GetFontMetric();
236 double fRet
= aMetric
.GetInternalLeading() / 2.5;
237 return fRet
* mnFontScalingFixY
;
240 double TextLayouterDevice::getUnderlineHeight() const
242 const ::FontMetric aMetric
= mrDevice
.GetFontMetric();
243 double fRet
= aMetric
.GetDescent() / 4.0;
244 return fRet
* mnFontScalingFixY
;
247 double TextLayouterDevice::getTextHeight() const
249 return mrDevice
.GetTextHeightDouble() * mnFontScalingFixY
;
252 double TextLayouterDevice::getTextWidth(const OUString
& rText
, sal_uInt32 nIndex
,
253 sal_uInt32 nLength
) const
255 return mrDevice
.GetTextWidthDouble(rText
, nIndex
, nLength
) * mnFontScalingFixX
;
258 void TextLayouterDevice::getTextOutlines(basegfx::B2DPolyPolygonVector
& rB2DPolyPolyVector
,
259 const OUString
& rText
, sal_uInt32 nIndex
,
260 sal_uInt32 nLength
, const std::vector
<double>& rDXArray
,
261 const std::vector
<sal_Bool
>& rKashidaArray
) const
263 const sal_uInt32
nDXArrayCount(rDXArray
.size());
264 sal_uInt32
nTextLength(nLength
);
265 const sal_uInt32
nStringLength(rText
.getLength());
267 if (nTextLength
+ nIndex
> nStringLength
)
269 nTextLength
= nStringLength
- nIndex
;
274 OSL_ENSURE(nDXArrayCount
== nTextLength
,
275 "DXArray size does not correspond to text portion size (!)");
277 mrDevice
.GetTextOutlines(rB2DPolyPolyVector
, rText
, nIndex
, nIndex
, nLength
, 0, rDXArray
,
282 mrDevice
.GetTextOutlines(rB2DPolyPolyVector
, rText
, nIndex
, nIndex
, nLength
);
284 if (!rtl_math_approxEqual(mnFontScalingFixY
, 1.0)
285 || !rtl_math_approxEqual(mnFontScalingFixX
, 1.0))
287 auto scale
= basegfx::utils::createScaleB2DHomMatrix(mnFontScalingFixX
, mnFontScalingFixY
);
288 for (auto& poly
: rB2DPolyPolyVector
)
289 poly
.transform(scale
);
293 basegfx::B2DRange
TextLayouterDevice::getTextBoundRect(const OUString
& rText
, sal_uInt32 nIndex
,
294 sal_uInt32 nLength
) const
296 sal_uInt32
nTextLength(nLength
);
297 const sal_uInt32
nStringLength(rText
.getLength());
299 if (nTextLength
+ nIndex
> nStringLength
)
301 nTextLength
= nStringLength
- nIndex
;
306 basegfx::B2DRange aRect
;
307 mrDevice
.GetTextBoundRect(aRect
, rText
, nIndex
, nIndex
, nLength
);
308 if (!rtl_math_approxEqual(mnFontScalingFixY
, 1.0)
309 || !rtl_math_approxEqual(mnFontScalingFixX
, 1.0))
312 basegfx::utils::createScaleB2DHomMatrix(mnFontScalingFixX
, mnFontScalingFixY
));
317 return basegfx::B2DRange();
320 double TextLayouterDevice::getFontAscent() const
322 const ::FontMetric aMetric
= mrDevice
.GetFontMetric();
323 return aMetric
.GetAscent() * mnFontScalingFixY
;
326 double TextLayouterDevice::getFontDescent() const
328 const ::FontMetric aMetric
= mrDevice
.GetFontMetric();
329 return aMetric
.GetDescent() * mnFontScalingFixY
;
332 void TextLayouterDevice::addTextRectActions(const ::tools::Rectangle
& rRectangle
,
333 const OUString
& rText
, DrawTextFlags nStyle
,
334 GDIMetaFile
& rGDIMetaFile
) const
336 mrDevice
.AddTextRectActions(rRectangle
, rText
, nStyle
, rGDIMetaFile
);
339 std::vector
<double> TextLayouterDevice::getTextArray(const OUString
& rText
, sal_uInt32 nIndex
,
340 sal_uInt32 nLength
, bool bCaret
) const
342 std::vector
<double> aRetval
;
343 sal_uInt32
nTextLength(nLength
);
344 const sal_uInt32
nStringLength(rText
.getLength());
346 if (nTextLength
+ nIndex
> nStringLength
)
348 nTextLength
= nStringLength
- nIndex
;
354 mrDevice
.GetTextArray(rText
, &aArray
, nIndex
, nTextLength
, bCaret
);
355 aRetval
.reserve(aArray
.size());
356 for (size_t i
= 0, nEnd
= aArray
.size(); i
< nEnd
; ++i
)
357 aRetval
.push_back(aArray
[i
] * mnFontScalingFixX
);
363 std::unique_ptr
<SalLayout
>
364 TextLayouterDevice::getSalLayout(const OUString
& rText
, sal_uInt32 nIndex
, sal_uInt32 nLength
,
365 const basegfx::B2DPoint
& rStartPoint
, const KernArray
& rDXArray
,
366 std::span
<const sal_Bool
> pKashidaAry
) const
368 const SalLayoutGlyphs
* pGlyphs(
369 SalLayoutGlyphsCache::self()->GetLayoutGlyphs(&mrDevice
, rText
, nIndex
, nLength
));
370 const Point
aStartPoint(basegfx::fround
<tools::Long
>(rStartPoint
.getX()),
371 basegfx::fround
<tools::Long
>(rStartPoint
.getY()));
372 return mrDevice
.ImplLayout(rText
, nIndex
, nLength
, aStartPoint
, 0, rDXArray
, pKashidaAry
,
373 SalLayoutFlags::NONE
, nullptr, pGlyphs
);
376 void TextLayouterDevice::createEmphasisMarks(
377 SalLayout
& rSalLayout
, TextEmphasisMark aTextEmphasisMark
, bool bAbove
,
378 const std::function
<void(const basegfx::B2DPoint
&, const basegfx::B2DPolyPolygon
&, bool,
379 const tools::Rectangle
&, const tools::Rectangle
&)>& rCallback
) const
381 FontEmphasisMark
nEmphasisMark(FontEmphasisMark::NONE
);
382 double fEmphasisHeight(getTextHeight() * (250.0 / 1000.0));
384 switch (aTextEmphasisMark
)
386 case TEXT_FONT_EMPHASIS_MARK_DOT
:
387 nEmphasisMark
= FontEmphasisMark::Dot
;
389 case TEXT_FONT_EMPHASIS_MARK_CIRCLE
:
390 nEmphasisMark
= FontEmphasisMark::Circle
;
392 case TEXT_FONT_EMPHASIS_MARK_DISC
:
393 nEmphasisMark
= FontEmphasisMark::Disc
;
395 case TEXT_FONT_EMPHASIS_MARK_ACCENT
:
396 nEmphasisMark
= FontEmphasisMark::Accent
;
403 nEmphasisMark
|= FontEmphasisMark::PosAbove
;
405 nEmphasisMark
|= FontEmphasisMark::PosBelow
;
407 mrDevice
.createEmphasisMarks(nEmphasisMark
, static_cast<tools::Long
>(fEmphasisHeight
),
408 rSalLayout
, rCallback
);
411 // helper methods for vcl font handling
413 vcl::Font
getVclFontFromFontAttribute(const attribute::FontAttribute
& rFontAttribute
,
414 double fFontScaleX
, double fFontScaleY
, double fFontRotation
,
415 const css::lang::Locale
& rLocale
)
417 // detect FontScaling
418 const sal_uInt32
nHeight(basegfx::fround(fabs(fFontScaleY
)));
419 const sal_uInt32
nWidth(basegfx::fround(fabs(fFontScaleX
)));
420 const bool bFontIsScaled(nHeight
!= nWidth
);
423 // for WIN32 systems, start with creating an unscaled font. If FontScaling
424 // is wanted, that width needs to be adapted using FontMetric again to get a
425 // width of the unscaled font
426 vcl::Font
aRetval(rFontAttribute
.getFamilyName(), rFontAttribute
.getStyleName(),
429 // for non-WIN32 systems things are easier since these accept a Font creation
430 // with initially nWidth != nHeight for FontScaling. Despite that, use zero for
431 // FontWidth when no scaling is used to explicitly have that zero when e.g. the
432 // Font would be recorded in a MetaFile (The MetaFile FontAction WILL record a
433 // set FontWidth; import that in a WIN32 system, and trouble is there)
434 vcl::Font
aRetval(rFontAttribute
.getFamilyName(), rFontAttribute
.getStyleName(),
435 Size(bFontIsScaled
? std::max
<sal_uInt32
>(nWidth
, 1) : 0, nHeight
));
437 // define various other FontAttribute
438 aRetval
.SetAlignment(ALIGN_BASELINE
);
439 aRetval
.SetCharSet(rFontAttribute
.getSymbol() ? RTL_TEXTENCODING_SYMBOL
440 : RTL_TEXTENCODING_UNICODE
);
441 aRetval
.SetVertical(rFontAttribute
.getVertical());
442 aRetval
.SetWeight(static_cast<FontWeight
>(rFontAttribute
.getWeight()));
443 aRetval
.SetItalic(rFontAttribute
.getItalic() ? ITALIC_NORMAL
: ITALIC_NONE
);
444 aRetval
.SetOutline(rFontAttribute
.getOutline());
445 aRetval
.SetPitch(rFontAttribute
.getMonospaced() ? PITCH_FIXED
: PITCH_VARIABLE
);
446 aRetval
.SetLanguage(LanguageTag::convertToLanguageType(rLocale
, false));
449 // for WIN32 systems, correct the FontWidth if FontScaling is used
450 if (bFontIsScaled
&& nHeight
> 0)
452 const FontMetric
aUnscaledFontMetric(
453 Application::GetDefaultDevice()->GetFontMetric(aRetval
));
455 if (aUnscaledFontMetric
.GetAverageFontWidth() > 0)
457 const double fScaleFactor(static_cast<double>(nWidth
) / static_cast<double>(nHeight
));
458 const sal_uInt32
nScaledWidth(basegfx::fround(
459 static_cast<double>(aUnscaledFontMetric
.GetAverageFontWidth()) * fScaleFactor
));
460 aRetval
.SetAverageFontWidth(nScaledWidth
);
464 // handle FontRotation (if defined)
465 if (!basegfx::fTools::equalZero(fFontRotation
))
467 int aRotate10th(-basegfx::rad2deg
<10>(fFontRotation
));
468 aRetval
.SetOrientation(Degree10(aRotate10th
% 3600));
474 attribute::FontAttribute
getFontAttributeFromVclFont(basegfx::B2DVector
& o_rSize
,
475 const vcl::Font
& rFont
, bool bRTL
,
478 const attribute::FontAttribute
aRetval(
479 rFont
.GetFamilyName(), rFont
.GetStyleName(), static_cast<sal_uInt16
>(rFont
.GetWeight()),
480 RTL_TEXTENCODING_SYMBOL
== rFont
.GetCharSet(), rFont
.IsVertical(),
481 ITALIC_NONE
!= rFont
.GetItalic(), PITCH_FIXED
== rFont
.GetPitch(), rFont
.IsOutline(), bRTL
,
485 // set FontHeight and init to no FontScaling
486 o_rSize
.setY(std::max
<tools::Long
>(rFont
.GetFontSize().getHeight(), 0));
487 o_rSize
.setX(o_rSize
.getY());
490 // for WIN32 systems, the FontScaling at the Font is detected by
491 // checking that FontWidth != 0. When FontScaling is used, WIN32
492 // needs to do extra stuff to detect the correct width (since it's
493 // zero and not equal the font height) and its relationship to
495 if (rFont
.GetFontSize().getWidth() > 0)
497 vcl::Font
aUnscaledFont(rFont
);
498 aUnscaledFont
.SetAverageFontWidth(0);
499 const FontMetric
aUnscaledFontMetric(
500 Application::GetDefaultDevice()->GetFontMetric(aUnscaledFont
));
502 if (aUnscaledFontMetric
.GetAverageFontWidth() > 0)
504 const double fScaleFactor(
505 static_cast<double>(rFont
.GetFontSize().getWidth())
506 / static_cast<double>(aUnscaledFontMetric
.GetAverageFontWidth()));
507 o_rSize
.setX(fScaleFactor
* o_rSize
.getY());
511 // For non-WIN32 systems the detection is the same, but the value
512 // is easier achieved since width == height is interpreted as no
513 // scaling. Ergo, Width == 0 means width == height, and width != 0
514 // means the scaling is in the direct relation of width to height
515 if (rFont
.GetFontSize().getWidth() > 0)
517 o_rSize
.setX(static_cast<double>(rFont
.GetFontSize().getWidth()));
523 } // end of namespace
525 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */