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/.
10 #include <sal/config.h>
12 #include <skia/win/gdiimpl.hxx>
14 #include <win/saldata.hxx>
15 #include <vcl/skia/SkiaHelper.hxx>
16 #include <skia/utils.hxx>
17 #include <skia/zone.hxx>
18 #include <skia/win/font.hxx>
19 #include <comphelper/scopeguard.hxx>
20 #include <comphelper/windowserrorstring.hxx>
21 #include <sal/log.hxx>
26 #include <SkPixelRef.h>
27 #include <SkTypeface_win.h>
29 #include <SkFontMgr.h>
30 #include <tools/window/win/WindowContextFactory_win.h>
31 #include <tools/window/WindowContext.h>
35 #include <type_traits>
39 sal::systools::COMReference
<IDWriteFontCollection
>
40 getDWritePrivateFontCollection(IDWriteFontFace
* fontFace
)
43 sal::systools::ThrowIfFailed(fontFace
->GetFiles(&numberOfFiles
, nullptr), SAL_WHERE
);
44 if (numberOfFiles
!= 1)
47 sal::systools::COMReference
<IDWriteFontFile
> fontFile
;
48 sal::systools::ThrowIfFailed(fontFace
->GetFiles(&numberOfFiles
, &fontFile
), SAL_WHERE
);
50 static sal::systools::COMReference
<IDWriteFactory3
> dwriteFactory3
= [] {
51 IDWriteFactory
* dwriteFactory
= WinSalGraphics::getDWriteFactory();
52 sal::systools::COMReference
<IDWriteFactory3
> factory3
;
53 sal::systools::ThrowIfFailed(dwriteFactory
->QueryInterface(&factory3
), SAL_WHERE
);
57 static sal::systools::COMReference
<IDWriteFontSetBuilder
> dwriteFontSetBuilder
= [] {
58 sal::systools::COMReference
<IDWriteFontSetBuilder
> builder
;
59 sal::systools::ThrowIfFailed(dwriteFactory3
->CreateFontSetBuilder(&builder
), SAL_WHERE
);
64 DWRITE_FONT_FILE_TYPE fileType
;
66 sal::systools::ThrowIfFailed(
67 fontFile
->Analyze(&isSupported
, &fileType
, nullptr, &numberOfFonts
), SAL_WHERE
);
71 // For each font within the font file, get a font face reference and add to the builder.
72 for (UINT32 fontIndex
= 0; fontIndex
< numberOfFonts
; ++fontIndex
)
74 sal::systools::COMReference
<IDWriteFontFaceReference
> fontFaceReference
;
75 if (FAILED(dwriteFactory3
->CreateFontFaceReference(
76 fontFile
, fontIndex
, DWRITE_FONT_SIMULATIONS_NONE
, &fontFaceReference
)))
79 // Leave it to DirectWrite to read properties directly out of the font files
80 dwriteFontSetBuilder
->AddFontFaceReference(fontFaceReference
);
83 sal::systools::COMReference
<IDWriteFontSet
> fontSet
;
84 sal::systools::ThrowIfFailed(dwriteFontSetBuilder
->CreateFontSet(&fontSet
), SAL_WHERE
);
86 sal::systools::COMReference
<IDWriteFontCollection1
> fc1
;
87 sal::systools::ThrowIfFailed(dwriteFactory3
->CreateFontCollectionFromFontSet(fontSet
, &fc1
),
93 using namespace SkiaHelper
;
95 WinSkiaSalGraphicsImpl::WinSkiaSalGraphicsImpl(WinSalGraphics
& rGraphics
,
96 SalGeometryProvider
* mpProvider
)
97 : SkiaSalGraphicsImpl(rGraphics
, mpProvider
)
98 , mWinParent(rGraphics
)
102 void WinSkiaSalGraphicsImpl::createWindowSurfaceInternal(bool forceRaster
)
104 assert(!mWindowContext
);
107 skwindow::DisplayParams displayParams
;
108 assert(GetWidth() > 0 && GetHeight() > 0);
109 displayParams
.fSurfaceProps
= *surfaceProps();
110 switch (forceRaster
? RenderRaster
: renderMethodToUse())
113 mWindowContext
= skwindow::MakeRasterForWin(mWinParent
.gethWnd(), displayParams
);
115 mSurface
= mWindowContext
->getBackbufferSurface();
118 mWindowContext
= skwindow::MakeVulkanForWin(mWinParent
.gethWnd(), displayParams
);
119 // See flushSurfaceToWindowContext().
121 mSurface
= createSkSurface(GetWidth(), GetHeight());
129 void WinSkiaSalGraphicsImpl::Flush() { performFlush(); }
131 bool WinSkiaSalGraphicsImpl::TryRenderCachedNativeControl(ControlCacheKey
const& rControlCacheKey
,
134 static bool gbCacheEnabled
= !getenv("SAL_WITHOUT_WIDGET_CACHE");
138 auto& controlsCache
= SkiaControlsCache::get();
139 SkiaControlCacheType::const_iterator iterator
= controlsCache
.find(rControlCacheKey
);
140 if (iterator
== controlsCache
.end())
144 SAL_INFO("vcl.skia.trace", "tryrendercachednativecontrol("
146 << SkIRect::MakeXYWH(nX
, nY
, iterator
->second
->width(),
147 iterator
->second
->height()));
149 SkRect::MakeXYWH(nX
, nY
, iterator
->second
->width(), iterator
->second
->height()));
150 mSurface
->getCanvas()->drawImage(iterator
->second
, nX
, nY
);
155 bool WinSkiaSalGraphicsImpl::RenderAndCacheNativeControl(CompatibleDC
& rWhite
, CompatibleDC
& rBlack
,
157 ControlCacheKey
& aControlCacheKey
)
159 assert(dynamic_cast<SkiaCompatibleDC
*>(&rWhite
));
160 assert(dynamic_cast<SkiaCompatibleDC
*>(&rBlack
));
162 sk_sp
<SkImage
> image
= static_cast<SkiaCompatibleDC
&>(rBlack
).getAsImageDiff(
163 static_cast<SkiaCompatibleDC
&>(rWhite
));
165 SAL_INFO("vcl.skia.trace",
166 "renderandcachednativecontrol("
167 << this << "): " << SkIRect::MakeXYWH(nX
, nY
, image
->width(), image
->height()));
168 addUpdateRegion(SkRect::MakeXYWH(nX
, nY
, image
->width(), image
->height()));
169 mSurface
->getCanvas()->drawImage(image
, nX
, nY
);
172 if (!aControlCacheKey
.canCacheControl())
174 SkiaControlCachePair
pair(aControlCacheKey
, std::move(image
));
175 SkiaControlsCache::get().insert(std::move(pair
));
180 WinSkiaSalGraphicsImpl::createDirectWriteTypeface(const WinFontInstance
* pWinFont
) try
182 using sal::systools::ThrowIfFailed
;
183 IDWriteFactory
* dwriteFactory
= WinSalGraphics::getDWriteFactory();
186 dwriteFontMgr
= SkFontMgr_New_DirectWrite(dwriteFactory
);
192 IDWriteFontFace
* fontFace
= pWinFont
->GetDWFontFace();
196 sal::systools::COMReference
<IDWriteFontCollection
> collection
;
197 ThrowIfFailed(dwriteFactory
->GetSystemFontCollection(&collection
), SAL_WHERE
);
198 sal::systools::COMReference
<IDWriteFont
> font
;
199 // As said above, this fails for our fonts.
200 if (FAILED(collection
->GetFontFromFontFace(fontFace
, &font
)))
202 // If not found in system collection, try our private font collection.
203 // If that's not possible we'll fall back to Skia's GDI-based font rendering.
204 if (!dwritePrivateCollection
205 || FAILED(dwritePrivateCollection
->GetFontFromFontFace(fontFace
, &font
)))
207 // Our private fonts are installed using AddFontResourceExW( FR_PRIVATE )
208 // and that does not make them available to the DWrite system font
209 // collection. For such cases attempt to update a collection of
210 // private fonts with this newly used font.
212 dwritePrivateCollection
= getDWritePrivateFontCollection(fontFace
);
213 if (!dwritePrivateCollection
) // Not one file? Unsupported font?
215 ThrowIfFailed(dwritePrivateCollection
->GetFontFromFontFace(fontFace
, &font
), SAL_WHERE
);
218 sal::systools::COMReference
<IDWriteFontFamily
> fontFamily
;
219 ThrowIfFailed(font
->GetFontFamily(&fontFamily
), SAL_WHERE
);
220 return sk_sp
<SkTypeface
>(
221 SkCreateTypefaceDirectWrite(dwriteFontMgr
, fontFace
, font
.get(), fontFamily
.get()));
223 catch (const sal::systools::ComError
& e
)
225 SAL_DETAIL_LOG_STREAM(SAL_DETAIL_ENABLE_LOG_INFO
, ::SAL_DETAIL_LOG_LEVEL_INFO
, "vcl.skia",
227 "HRESULT 0x" << OUString::number(e
.GetHresult(), 16) << ": "
228 << WindowsErrorStringFromHRESULT(e
.GetHresult()));
232 bool WinSkiaSalGraphicsImpl::DrawTextLayout(const GenericSalLayout
& rLayout
)
234 assert(dynamic_cast<SkiaWinFontInstance
*>(&rLayout
.GetFont()));
235 SkiaWinFontInstance
& rWinFont
= static_cast<SkiaWinFontInstance
&>(rLayout
.GetFont());
236 const vcl::font::FontSelectPattern
& rFSD
= rWinFont
.GetFontSelectPattern();
237 if (rFSD
.mnHeight
== 0)
239 const HFONT hLayoutFont
= rWinFont
.GetHFONT();
241 if (GetObjectW(hLayoutFont
, sizeof(logFont
), &logFont
) == 0)
246 sk_sp
<SkTypeface
> typeface
= rWinFont
.GetSkiaTypeface();
249 typeface
= createDirectWriteTypeface(&rWinFont
);
251 if (!typeface
) // fall back to GDI text rendering
253 // If lfWidth is kept, then with hScale != 1 characters get too wide, presumably
254 // because the horizontal scaling gets applied twice if GDI is used for drawing (tdf#141715).
255 // Using lfWidth /= hScale gives slightly incorrect sizes, for a reason I don't understand.
256 // LOGFONT docs say that 0 means GDI will find out the right value on its own somehow,
257 // and it apparently works.
259 // Reset LOGFONT orientation, the proper orientation is applied by drawGenericLayout(),
260 // and keeping this would make it get applied once more when doing the actual GDI drawing.
261 // Resetting it here does not seem to cause any problem.
262 logFont
.lfOrientation
= 0;
263 logFont
.lfEscapement
= 0;
264 typeface
= SkCreateTypefaceFromLOGFONT(logFont
);
269 // Cache the typeface.
270 rWinFont
.SetSkiaTypeface(typeface
, dwrite
);
273 SkFont
font(typeface
);
275 bool bSubpixelPositioning
= rLayout
.GetSubpixelPositioning();
276 SkFont::Edging ePreferredAliasing
277 = bSubpixelPositioning
? SkFont::Edging::kSubpixelAntiAlias
: fontEdging
;
278 if (bSubpixelPositioning
)
280 // note that SkFont defaults to a BaselineSnap of true, so I think really only
281 // subpixel in text direction
282 font
.setSubpixel(true);
284 font
.setEdging(logFont
.lfQuality
== NONANTIALIASED_QUALITY
? SkFont::Edging::kAlias
285 : ePreferredAliasing
);
287 double nHeight
= rFSD
.mnHeight
;
288 double nWidth
= rFSD
.mnWidth
? rFSD
.mnWidth
* rWinFont
.GetAverageWidthFactor() : nHeight
;
289 font
.setSize(nHeight
);
290 font
.setScaleX(nWidth
/ nHeight
);
292 SkFont
verticalFont(font
);
293 verticalFont
.setSize(nWidth
);
294 verticalFont
.setScaleX(nHeight
/ nWidth
);
296 assert(dynamic_cast<SkiaSalGraphicsImpl
*>(mWinParent
.GetImpl()));
297 SkiaSalGraphicsImpl
* impl
= static_cast<SkiaSalGraphicsImpl
*>(mWinParent
.GetImpl());
298 COLORREF color
= ::GetTextColor(mWinParent
.getHDC());
299 Color
salColor(GetRValue(color
), GetGValue(color
), GetBValue(color
));
300 impl
->drawGenericLayout(rLayout
, salColor
, font
, verticalFont
);
304 SkFont::Edging
WinSkiaSalGraphicsImpl::fontEdging
;
306 void WinSkiaSalGraphicsImpl::initFontInfo()
308 // Skia needs to be explicitly told what kind of antialiasing should be used,
309 // get it from system settings. This does not actually matter for the text
310 // rendering itself, since Skia has been patched to simply use the setting
311 // from the LOGFONT, which gets set by VCL's ImplGetLogFontFromFontSelect()
312 // and that one normally uses DEFAULT_QUALITY, so Windows will select
313 // the appropriate AA setting. But Skia internally chooses the format to which
314 // the glyphs will be rendered based on this setting (subpixel AA requires colors,
316 fontEdging
= SkFont::Edging::kAlias
;
317 SkPixelGeometry pixelGeometry
= kUnknown_SkPixelGeometry
;
319 if (SystemParametersInfo(SPI_GETFONTSMOOTHING
, 0, &set
, 0) && set
)
322 if (SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE
, 0, &set2
, 0)
323 && set2
== FE_FONTSMOOTHINGCLEARTYPE
)
325 fontEdging
= SkFont::Edging::kSubpixelAntiAlias
;
326 if (SystemParametersInfo(SPI_GETFONTSMOOTHINGORIENTATION
, 0, &set2
, 0)
327 && set2
== FE_FONTSMOOTHINGORIENTATIONBGR
)
328 // No idea how to tell if it's horizontal or vertical.
329 pixelGeometry
= kBGR_H_SkPixelGeometry
;
331 pixelGeometry
= kRGB_H_SkPixelGeometry
; // default
334 fontEdging
= SkFont::Edging::kAntiAlias
;
336 setPixelGeometry(pixelGeometry
);
339 void WinSkiaSalGraphicsImpl::ClearDevFontCache()
341 dwriteFontMgr
.reset();
342 dwritePrivateCollection
.clear();
344 initFontInfo(); // get font info again, just in case
347 SkiaCompatibleDC::SkiaCompatibleDC(SalGraphics
& rGraphics
, int x
, int y
, int width
, int height
)
348 : CompatibleDC(rGraphics
, x
, y
, width
, height
, false)
352 sk_sp
<SkImage
> SkiaCompatibleDC::getAsImageDiff(const SkiaCompatibleDC
& white
) const
355 assert(maRects
.mnSrcWidth
== white
.maRects
.mnSrcWidth
356 || maRects
.mnSrcHeight
== white
.maRects
.mnSrcHeight
);
358 if (!tmpBitmap
.tryAllocPixels(SkImageInfo::Make(maRects
.mnSrcWidth
, maRects
.mnSrcHeight
,
359 kBGRA_8888_SkColorType
, kPremul_SkAlphaType
),
360 maRects
.mnSrcWidth
* 4))
362 // Native widgets are drawn twice on black/white background to synthetize alpha
363 // (commit c6b66646870cb2bffaa73565affcf80bf74e0b5c). The problem is that
364 // most widgets when drawn on transparent background are drawn properly (and the result
365 // is in premultiplied alpha format), some such as "Edit" (used by ControlType::Editbox)
366 // keep the alpha channel as transparent. Therefore the alpha is actually computed
367 // from the difference in the premultiplied red channels when drawn one black and on white.
368 // Alpha is computed as "alpha = 1.0 - abs(black.red - white.red)".
369 // I doubt this can be done using Skia, so do it manually here. Fortunately
370 // the bitmaps should be fairly small and are cached.
371 uint32_t* dest
= tmpBitmap
.getAddr32(0, 0);
372 assert(dest
== tmpBitmap
.getPixels());
373 const sal_uInt32
* src
= mpData
;
374 const sal_uInt32
* whiteSrc
= white
.mpData
;
375 uint32_t* end
= dest
+ tmpBitmap
.width() * tmpBitmap
.height();
378 uint32_t alpha
= 255 - abs(int(*src
& 0xff) - int(*whiteSrc
& 0xff));
379 *dest
= (*src
& 0x00ffffff) | (alpha
<< 24);
384 tmpBitmap
.notifyPixelsChanged();
385 tmpBitmap
.setImmutable();
386 sk_sp
<SkSurface
> surface
= createSkSurface(tmpBitmap
.width(), tmpBitmap
.height());
388 paint
.setBlendMode(SkBlendMode::kSrc
); // set as is, including alpha
389 SkCanvas
* canvas
= surface
->getCanvas();
391 // The data we got is upside-down.
393 matrix
.preTranslate(0, tmpBitmap
.height());
394 matrix
.setConcat(matrix
, SkMatrix::Scale(1, -1));
395 canvas
->concat(matrix
);
396 canvas
->drawImage(tmpBitmap
.asImage(), 0, 0, SkSamplingOptions(), &paint
);
398 return makeCheckedImageSnapshot(surface
);
401 SkiaControlsCache::SkiaControlsCache()
406 SkiaControlCacheType
& SkiaControlsCache::get()
408 SalData
* data
= GetSalData();
409 if (!data
->m_pSkiaControlsCache
)
410 data
->m_pSkiaControlsCache
.reset(new SkiaControlsCache
);
411 return data
->m_pSkiaControlsCache
->cache
;
416 std::unique_ptr
<skwindow::WindowContext
> createVulkanWindowContext(bool /*temporary*/)
419 skwindow::DisplayParams displayParams
;
420 return skwindow::MakeVulkanForWin(nullptr, displayParams
);
424 void WinSkiaSalGraphicsImpl::prepareSkia()
427 SkiaHelper::prepareSkia(createVulkanWindowContext
);
430 void WinSkiaSalGraphicsImpl::ClearNativeControlCache()
432 SalData
* data
= GetSalData();
433 data
->m_pSkiaControlsCache
.reset();
436 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */