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>
25 #include <SkPixelRef.h>
26 #include <SkTypeface_win.h>
28 #include <SkFontMgr.h>
29 #include <tools/sk_app/win/WindowContextFactory_win.h>
30 #include <tools/sk_app/WindowContext.h>
34 using namespace SkiaHelper
;
36 WinSkiaSalGraphicsImpl::WinSkiaSalGraphicsImpl(WinSalGraphics
& rGraphics
,
37 SalGeometryProvider
* mpProvider
)
38 : SkiaSalGraphicsImpl(rGraphics
, mpProvider
)
39 , mWinParent(rGraphics
)
43 void WinSkiaSalGraphicsImpl::createWindowSurfaceInternal(bool forceRaster
)
45 assert(!mWindowContext
);
48 sk_app::DisplayParams displayParams
;
49 assert(GetWidth() > 0 && GetHeight() > 0);
50 displayParams
.fSurfaceProps
= *surfaceProps();
51 switch (forceRaster
? RenderRaster
: renderMethodToUse())
54 mWindowContext
= sk_app::window_context_factory::MakeRasterForWin(mWinParent
.gethWnd(),
57 mSurface
= mWindowContext
->getBackbufferSurface();
60 mWindowContext
= sk_app::window_context_factory::MakeVulkanForWin(mWinParent
.gethWnd(),
62 // See flushSurfaceToWindowContext().
64 mSurface
= createSkSurface(GetWidth(), GetHeight());
72 void WinSkiaSalGraphicsImpl::freeResources() {}
74 void WinSkiaSalGraphicsImpl::Flush() { performFlush(); }
76 bool WinSkiaSalGraphicsImpl::TryRenderCachedNativeControl(ControlCacheKey
const& rControlCacheKey
,
79 static bool gbCacheEnabled
= !getenv("SAL_WITHOUT_WIDGET_CACHE");
83 auto& controlsCache
= SkiaControlsCache::get();
84 SkiaControlCacheType::const_iterator iterator
= controlsCache
.find(rControlCacheKey
);
85 if (iterator
== controlsCache
.end())
89 SAL_INFO("vcl.skia.trace", "tryrendercachednativecontrol("
91 << SkIRect::MakeXYWH(nX
, nY
, iterator
->second
->width(),
92 iterator
->second
->height()));
94 SkRect::MakeXYWH(nX
, nY
, iterator
->second
->width(), iterator
->second
->height()));
95 mSurface
->getCanvas()->drawImage(iterator
->second
, nX
, nY
);
100 bool WinSkiaSalGraphicsImpl::RenderAndCacheNativeControl(CompatibleDC
& rWhite
, CompatibleDC
& rBlack
,
102 ControlCacheKey
& aControlCacheKey
)
104 assert(dynamic_cast<SkiaCompatibleDC
*>(&rWhite
));
105 assert(dynamic_cast<SkiaCompatibleDC
*>(&rBlack
));
107 sk_sp
<SkImage
> image
= static_cast<SkiaCompatibleDC
&>(rBlack
).getAsImageDiff(
108 static_cast<SkiaCompatibleDC
&>(rWhite
));
110 SAL_INFO("vcl.skia.trace",
111 "renderandcachednativecontrol("
112 << this << "): " << SkIRect::MakeXYWH(nX
, nY
, image
->width(), image
->height()));
113 addUpdateRegion(SkRect::MakeXYWH(nX
, nY
, image
->width(), image
->height()));
114 mSurface
->getCanvas()->drawImage(image
, nX
, nY
);
117 if (!aControlCacheKey
.canCacheControl())
119 SkiaControlCachePair
pair(aControlCacheKey
, std::move(image
));
120 SkiaControlsCache::get().insert(std::move(pair
));
125 WinSkiaSalGraphicsImpl::createDirectWriteTypeface(const WinFontInstance
* pWinFont
) try
127 using sal::systools::ThrowIfFailed
;
128 IDWriteFactory
* dwriteFactory
;
129 WinSalGraphics::getDWriteFactory(&dwriteFactory
);
132 dwriteFontMgr
= SkFontMgr_New_DirectWrite(dwriteFactory
);
138 IDWriteFontFace
* fontFace
= pWinFont
->GetDWFontFace();
142 sal::systools::COMReference
<IDWriteFontCollection
> collection
;
143 ThrowIfFailed(dwriteFactory
->GetSystemFontCollection(&collection
), SAL_WHERE
);
144 sal::systools::COMReference
<IDWriteFont
> font
;
145 // As said above, this fails for our fonts.
146 if (FAILED(collection
->GetFontFromFontFace(fontFace
, &font
)))
148 // If not found in system collection, try our private font collection.
149 // If that's not possible we'll fall back to Skia's GDI-based font rendering.
150 if (!dwritePrivateCollection
151 || FAILED(dwritePrivateCollection
->GetFontFromFontFace(fontFace
, &font
)))
153 // Our private fonts are installed using AddFontResourceExW( FR_PRIVATE )
154 // and that does not make them available to the DWrite system font
155 // collection. For such cases attempt to update a collection of
156 // private fonts with this newly used font.
158 sal::systools::COMReference
<IDWriteFactory3
> dwriteFactory3
;
159 ThrowIfFailed(dwriteFactory
->QueryInterface(&dwriteFactory3
), SAL_WHERE
);
161 if (!dwriteFontSetBuilder
)
162 ThrowIfFailed(dwriteFactory3
->CreateFontSetBuilder(&dwriteFontSetBuilder
),
165 UINT32 numberOfFiles
;
166 ThrowIfFailed(fontFace
->GetFiles(&numberOfFiles
, nullptr), SAL_WHERE
);
167 if (numberOfFiles
!= 1)
170 sal::systools::COMReference
<IDWriteFontFile
> fontFile
;
171 ThrowIfFailed(fontFace
->GetFiles(&numberOfFiles
, &fontFile
), SAL_WHERE
);
174 DWRITE_FONT_FILE_TYPE fileType
;
175 UINT32 numberOfFonts
;
176 ThrowIfFailed(fontFile
->Analyze(&isSupported
, &fileType
, nullptr, &numberOfFonts
),
181 // For each font within the font file, get a font face reference and add to the builder.
182 for (UINT32 fontIndex
= 0; fontIndex
< numberOfFonts
; ++fontIndex
)
184 sal::systools::COMReference
<IDWriteFontFaceReference
> fontFaceReference
;
185 if (FAILED(dwriteFactory3
->CreateFontFaceReference(fontFile
.get(), fontIndex
,
186 DWRITE_FONT_SIMULATIONS_NONE
,
187 &fontFaceReference
)))
190 // Leave it to DirectWrite to read properties directly out of the font files
191 dwriteFontSetBuilder
->AddFontFaceReference(fontFaceReference
.get());
194 sal::systools::COMReference
<IDWriteFontSet
> fontSet
;
195 ThrowIfFailed(dwriteFontSetBuilder
->CreateFontSet(&fontSet
), SAL_WHERE
);
196 ThrowIfFailed(dwriteFactory3
->CreateFontCollectionFromFontSet(fontSet
.get(),
197 &dwritePrivateCollection
),
199 ThrowIfFailed(dwritePrivateCollection
->GetFontFromFontFace(fontFace
, &font
), SAL_WHERE
);
202 sal::systools::COMReference
<IDWriteFontFamily
> fontFamily
;
203 ThrowIfFailed(font
->GetFontFamily(&fontFamily
), SAL_WHERE
);
204 return sk_sp
<SkTypeface
>(
205 SkCreateTypefaceDirectWrite(dwriteFontMgr
, fontFace
, font
.get(), fontFamily
.get()));
207 catch (const sal::systools::ComError
& e
)
209 SAL_DETAIL_LOG_STREAM(SAL_DETAIL_ENABLE_LOG_INFO
, ::SAL_DETAIL_LOG_LEVEL_INFO
, "vcl.skia",
211 "HRESULT 0x" << OUString::number(e
.GetHresult(), 16) << ": "
212 << WindowsErrorStringFromHRESULT(e
.GetHresult()));
216 bool WinSkiaSalGraphicsImpl::DrawTextLayout(const GenericSalLayout
& rLayout
)
218 assert(dynamic_cast<const SkiaWinFontInstance
*>(&rLayout
.GetFont()));
219 const SkiaWinFontInstance
* pWinFont
220 = static_cast<const SkiaWinFontInstance
*>(&rLayout
.GetFont());
221 const HFONT hLayoutFont
= pWinFont
->GetHFONT();
222 double hScale
= pWinFont
->getHScale();
224 if (GetObjectW(hLayoutFont
, sizeof(logFont
), &logFont
) == 0)
229 sk_sp
<SkTypeface
> typeface
= pWinFont
->GetSkiaTypeface();
232 typeface
= createDirectWriteTypeface(pWinFont
);
234 if (!typeface
) // fall back to GDI text rendering
236 // If lfWidth is kept, then with hScale != 1 characters get too wide, presumably
237 // because the horizontal scaling gets applied twice if GDI is used for drawing (tdf#141715).
238 // Using lfWidth /= hScale gives slightly incorrect sizes, for a reason I don't understand.
239 // LOGFONT docs say that 0 means GDI will find out the right value on its own somehow,
240 // and it apparently works.
242 // Reset LOGFONT orientation, the proper orientation is applied by drawGenericLayout(),
243 // and keeping this would make it get applied once more when doing the actual GDI drawing.
244 // Resetting it here does not seem to cause any problem.
245 logFont
.lfOrientation
= 0;
246 logFont
.lfEscapement
= 0;
247 typeface
.reset(SkCreateTypefaceFromLOGFONT(logFont
));
252 // Cache the typeface.
253 const_cast<SkiaWinFontInstance
*>(pWinFont
)->SetSkiaTypeface(typeface
, dwrite
);
256 SkFont
font(typeface
);
258 bool bSubpixelPositioning
= rLayout
.GetTextRenderModeForResolutionIndependentLayout();
259 SkFont::Edging ePreferredAliasing
260 = bSubpixelPositioning
? SkFont::Edging::kSubpixelAntiAlias
: fontEdging
;
261 if (bSubpixelPositioning
)
263 // note that SkFont defaults to a BaselineSnap of true, so I think really only
264 // subpixel in text direction
265 font
.setSubpixel(true);
267 font
.setEdging(logFont
.lfQuality
== NONANTIALIASED_QUALITY
? SkFont::Edging::kAlias
268 : ePreferredAliasing
);
270 const vcl::font::FontSelectPattern
& rFSD
= pWinFont
->GetFontSelectPattern();
271 int nHeight
= rFSD
.mnHeight
;
272 int nWidth
= rFSD
.mnWidth
? rFSD
.mnWidth
: nHeight
;
273 if (nWidth
== 0 || nHeight
== 0)
275 font
.setSize(nHeight
);
276 font
.setScaleX(hScale
);
278 // Unlike with Freetype-based font handling, use height even in vertical mode,
279 // additionally multiply it by horizontal scale to get the proper
280 // size and then scale the width back, otherwise the height would
281 // not be correct. I don't know why this is inconsistent.
282 SkFont
verticalFont(font
);
283 verticalFont
.setSize(nHeight
* hScale
);
284 verticalFont
.setScaleX(1.0 / hScale
);
286 assert(dynamic_cast<SkiaSalGraphicsImpl
*>(mWinParent
.GetImpl()));
287 SkiaSalGraphicsImpl
* impl
= static_cast<SkiaSalGraphicsImpl
*>(mWinParent
.GetImpl());
288 COLORREF color
= ::GetTextColor(mWinParent
.getHDC());
289 Color
salColor(GetRValue(color
), GetGValue(color
), GetBValue(color
));
290 impl
->drawGenericLayout(rLayout
, salColor
, font
, verticalFont
);
294 SkFont::Edging
WinSkiaSalGraphicsImpl::fontEdging
;
296 void WinSkiaSalGraphicsImpl::initFontInfo()
298 // Skia needs to be explicitly told what kind of antialiasing should be used,
299 // get it from system settings. This does not actually matter for the text
300 // rendering itself, since Skia has been patched to simply use the setting
301 // from the LOGFONT, which gets set by VCL's ImplGetLogFontFromFontSelect()
302 // and that one normally uses DEFAULT_QUALITY, so Windows will select
303 // the appropriate AA setting. But Skia internally chooses the format to which
304 // the glyphs will be rendered based on this setting (subpixel AA requires colors,
306 fontEdging
= SkFont::Edging::kAlias
;
307 SkPixelGeometry pixelGeometry
= kUnknown_SkPixelGeometry
;
309 if (SystemParametersInfo(SPI_GETFONTSMOOTHING
, 0, &set
, 0) && set
)
312 if (SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE
, 0, &set2
, 0)
313 && set2
== FE_FONTSMOOTHINGCLEARTYPE
)
315 fontEdging
= SkFont::Edging::kSubpixelAntiAlias
;
316 if (SystemParametersInfo(SPI_GETFONTSMOOTHINGORIENTATION
, 0, &set2
, 0)
317 && set2
== FE_FONTSMOOTHINGORIENTATIONBGR
)
318 // No idea how to tell if it's horizontal or vertical.
319 pixelGeometry
= kBGR_H_SkPixelGeometry
;
321 pixelGeometry
= kRGB_H_SkPixelGeometry
; // default
324 fontEdging
= SkFont::Edging::kAntiAlias
;
326 setPixelGeometry(pixelGeometry
);
329 void WinSkiaSalGraphicsImpl::ClearDevFontCache()
331 dwriteFontMgr
.reset();
332 dwriteFontSetBuilder
.clear();
333 dwritePrivateCollection
.clear();
335 initFontInfo(); // get font info again, just in case
338 SkiaCompatibleDC::SkiaCompatibleDC(SalGraphics
& rGraphics
, int x
, int y
, int width
, int height
)
339 : CompatibleDC(rGraphics
, x
, y
, width
, height
, false)
343 sk_sp
<SkImage
> SkiaCompatibleDC::getAsImageDiff(const SkiaCompatibleDC
& white
) const
346 assert(maRects
.mnSrcWidth
== white
.maRects
.mnSrcWidth
347 || maRects
.mnSrcHeight
== white
.maRects
.mnSrcHeight
);
349 if (!tmpBitmap
.tryAllocPixels(SkImageInfo::Make(maRects
.mnSrcWidth
, maRects
.mnSrcHeight
,
350 kBGRA_8888_SkColorType
, kPremul_SkAlphaType
),
351 maRects
.mnSrcWidth
* 4))
353 // Native widgets are drawn twice on black/white background to synthetize alpha
354 // (commit c6b66646870cb2bffaa73565affcf80bf74e0b5c). The problem is that
355 // most widgets when drawn on transparent background are drawn properly (and the result
356 // is in premultiplied alpha format), some such as "Edit" (used by ControlType::Editbox)
357 // keep the alpha channel as transparent. Therefore the alpha is actually computed
358 // from the difference in the premultiplied red channels when drawn one black and on white.
359 // Alpha is computed as "alpha = 1.0 - abs(black.red - white.red)".
360 // I doubt this can be done using Skia, so do it manually here. Fortunately
361 // the bitmaps should be fairly small and are cached.
362 uint32_t* dest
= tmpBitmap
.getAddr32(0, 0);
363 assert(dest
== tmpBitmap
.getPixels());
364 const sal_uInt32
* src
= mpData
;
365 const sal_uInt32
* whiteSrc
= white
.mpData
;
366 uint32_t* end
= dest
+ tmpBitmap
.width() * tmpBitmap
.height();
369 uint32_t alpha
= 255 - abs(int(*src
& 0xff) - int(*whiteSrc
& 0xff));
370 *dest
= (*src
& 0x00ffffff) | (alpha
<< 24);
375 tmpBitmap
.notifyPixelsChanged();
376 tmpBitmap
.setImmutable();
377 sk_sp
<SkSurface
> surface
= createSkSurface(tmpBitmap
.width(), tmpBitmap
.height());
379 paint
.setBlendMode(SkBlendMode::kSrc
); // set as is, including alpha
380 SkCanvas
* canvas
= surface
->getCanvas();
382 // The data we got is upside-down.
384 matrix
.preTranslate(0, tmpBitmap
.height());
385 matrix
.setConcat(matrix
, SkMatrix::Scale(1, -1));
386 canvas
->concat(matrix
);
387 canvas
->drawImage(tmpBitmap
.asImage(), 0, 0, SkSamplingOptions(), &paint
);
389 return makeCheckedImageSnapshot(surface
);
392 SkiaControlsCache::SkiaControlsCache()
397 SkiaControlCacheType
& SkiaControlsCache::get()
399 SalData
* data
= GetSalData();
400 if (!data
->m_pSkiaControlsCache
)
401 data
->m_pSkiaControlsCache
.reset(new SkiaControlsCache
);
402 return data
->m_pSkiaControlsCache
->cache
;
407 std::unique_ptr
<sk_app::WindowContext
> createVulkanWindowContext(bool /*temporary*/)
410 sk_app::DisplayParams displayParams
;
411 return sk_app::window_context_factory::MakeVulkanForWin(nullptr, displayParams
);
415 void WinSkiaSalGraphicsImpl::prepareSkia()
418 SkiaHelper::prepareSkia(createVulkanWindowContext
);
421 void WinSkiaSalGraphicsImpl::ClearNativeControlCache()
423 SalData
* data
= GetSalData();
424 data
->m_pSkiaControlsCache
.reset();
427 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */