defer finding dialog parent until we need it
[LibreOffice.git] / vcl / skia / win / gdiimpl.cxx
blob8de57dba2b893dcc53276faf85bb7bde74160889
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
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>
23 #include <SkBitmap.h>
24 #include <SkCanvas.h>
25 #include <SkPaint.h>
26 #include <SkPixelRef.h>
27 #include <SkTypeface_win.h>
28 #include <SkFont.h>
29 #include <SkFontMgr.h>
30 #include <tools/window/win/WindowContextFactory_win.h>
31 #include <tools/window/WindowContext.h>
33 #include <windows.h>
35 #include <type_traits>
37 namespace
39 sal::systools::COMReference<IDWriteFontCollection>
40 getDWritePrivateFontCollection(IDWriteFontFace* fontFace)
42 UINT32 numberOfFiles;
43 sal::systools::ThrowIfFailed(fontFace->GetFiles(&numberOfFiles, nullptr), SAL_WHERE);
44 if (numberOfFiles != 1)
45 return {};
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);
54 return factory3;
55 }();
57 static sal::systools::COMReference<IDWriteFontSetBuilder> dwriteFontSetBuilder = [] {
58 sal::systools::COMReference<IDWriteFontSetBuilder> builder;
59 sal::systools::ThrowIfFailed(dwriteFactory3->CreateFontSetBuilder(&builder), SAL_WHERE);
60 return builder;
61 }();
63 BOOL isSupported;
64 DWRITE_FONT_FILE_TYPE fileType;
65 UINT32 numberOfFonts;
66 sal::systools::ThrowIfFailed(
67 fontFile->Analyze(&isSupported, &fileType, nullptr, &numberOfFonts), SAL_WHERE);
68 if (!isSupported)
69 return {};
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)))
77 continue;
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),
88 SAL_WHERE);
89 return { fc1.get() };
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);
105 assert(!mSurface);
106 SkiaZone zone;
107 skwindow::DisplayParams displayParams;
108 assert(GetWidth() > 0 && GetHeight() > 0);
109 displayParams.fSurfaceProps = *surfaceProps();
110 switch (forceRaster ? RenderRaster : renderMethodToUse())
112 case RenderRaster:
113 mWindowContext = skwindow::MakeRasterForWin(mWinParent.gethWnd(), displayParams);
114 if (mWindowContext)
115 mSurface = mWindowContext->getBackbufferSurface();
116 break;
117 case RenderVulkan:
118 mWindowContext = skwindow::MakeVulkanForWin(mWinParent.gethWnd(), displayParams);
119 // See flushSurfaceToWindowContext().
120 if (mWindowContext)
121 mSurface = createSkSurface(GetWidth(), GetHeight());
122 break;
123 case RenderMetal:
124 abort();
125 break;
129 void WinSkiaSalGraphicsImpl::Flush() { performFlush(); }
131 bool WinSkiaSalGraphicsImpl::TryRenderCachedNativeControl(ControlCacheKey const& rControlCacheKey,
132 int nX, int nY)
134 static bool gbCacheEnabled = !getenv("SAL_WITHOUT_WIDGET_CACHE");
135 if (!gbCacheEnabled)
136 return false;
138 auto& controlsCache = SkiaControlsCache::get();
139 SkiaControlCacheType::const_iterator iterator = controlsCache.find(rControlCacheKey);
140 if (iterator == controlsCache.end())
141 return false;
143 preDraw();
144 SAL_INFO("vcl.skia.trace", "tryrendercachednativecontrol("
145 << this << "): "
146 << SkIRect::MakeXYWH(nX, nY, iterator->second->width(),
147 iterator->second->height()));
148 addUpdateRegion(
149 SkRect::MakeXYWH(nX, nY, iterator->second->width(), iterator->second->height()));
150 mSurface->getCanvas()->drawImage(iterator->second, nX, nY);
151 postDraw();
152 return true;
155 bool WinSkiaSalGraphicsImpl::RenderAndCacheNativeControl(CompatibleDC& rWhite, CompatibleDC& rBlack,
156 int nX, int nY,
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));
164 preDraw();
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);
170 postDraw();
172 if (!aControlCacheKey.canCacheControl())
173 return true;
174 SkiaControlCachePair pair(aControlCacheKey, std::move(image));
175 SkiaControlsCache::get().insert(std::move(pair));
176 return true;
179 sk_sp<SkTypeface>
180 WinSkiaSalGraphicsImpl::createDirectWriteTypeface(const WinFontInstance* pWinFont) try
182 using sal::systools::ThrowIfFailed;
183 IDWriteFactory* dwriteFactory = WinSalGraphics::getDWriteFactory();
184 if (!dwriteDone)
186 dwriteFontMgr = SkFontMgr_New_DirectWrite(dwriteFactory);
187 dwriteDone = true;
189 if (!dwriteFontMgr)
190 return nullptr;
192 IDWriteFontFace* fontFace = pWinFont->GetDWFontFace();
193 if (!fontFace)
194 return nullptr;
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?
214 return nullptr;
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",
226 e.what(),
227 "HRESULT 0x" << OUString::number(e.GetHresult(), 16) << ": "
228 << WindowsErrorStringFromHRESULT(e.GetHresult()));
229 return nullptr;
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)
238 return false;
239 const HFONT hLayoutFont = rWinFont.GetHFONT();
240 LOGFONTW logFont;
241 if (GetObjectW(hLayoutFont, sizeof(logFont), &logFont) == 0)
243 assert(false);
244 return false;
246 sk_sp<SkTypeface> typeface = rWinFont.GetSkiaTypeface();
247 if (!typeface)
249 typeface = createDirectWriteTypeface(&rWinFont);
250 bool dwrite = true;
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.
258 logFont.lfWidth = 0;
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);
265 dwrite = false;
266 if (!typeface)
267 return false;
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);
301 return true;
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,
315 // others do not).
316 fontEdging = SkFont::Edging::kAlias;
317 SkPixelGeometry pixelGeometry = kUnknown_SkPixelGeometry;
318 BOOL set;
319 if (SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &set, 0) && set)
321 UINT set2;
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;
330 else
331 pixelGeometry = kRGB_H_SkPixelGeometry; // default
333 else
334 fontEdging = SkFont::Edging::kAntiAlias;
336 setPixelGeometry(pixelGeometry);
339 void WinSkiaSalGraphicsImpl::ClearDevFontCache()
341 dwriteFontMgr.reset();
342 dwritePrivateCollection.clear();
343 dwriteDone = false;
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
354 SkiaZone zone;
355 assert(maRects.mnSrcWidth == white.maRects.mnSrcWidth
356 || maRects.mnSrcHeight == white.maRects.mnSrcHeight);
357 SkBitmap tmpBitmap;
358 if (!tmpBitmap.tryAllocPixels(SkImageInfo::Make(maRects.mnSrcWidth, maRects.mnSrcHeight,
359 kBGRA_8888_SkColorType, kPremul_SkAlphaType),
360 maRects.mnSrcWidth * 4))
361 abort();
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();
376 while (dest < end)
378 uint32_t alpha = 255 - abs(int(*src & 0xff) - int(*whiteSrc & 0xff));
379 *dest = (*src & 0x00ffffff) | (alpha << 24);
380 ++dest;
381 ++src;
382 ++whiteSrc;
384 tmpBitmap.notifyPixelsChanged();
385 tmpBitmap.setImmutable();
386 sk_sp<SkSurface> surface = createSkSurface(tmpBitmap.width(), tmpBitmap.height());
387 SkPaint paint;
388 paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
389 SkCanvas* canvas = surface->getCanvas();
390 canvas->save();
391 // The data we got is upside-down.
392 SkMatrix matrix;
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);
397 canvas->restore();
398 return makeCheckedImageSnapshot(surface);
401 SkiaControlsCache::SkiaControlsCache()
402 : cache(200)
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;
414 namespace
416 std::unique_ptr<skwindow::WindowContext> createVulkanWindowContext(bool /*temporary*/)
418 SkiaZone zone;
419 skwindow::DisplayParams displayParams;
420 return skwindow::MakeVulkanForWin(nullptr, displayParams);
424 void WinSkiaSalGraphicsImpl::prepareSkia()
426 initFontInfo();
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: */