android: Update app-specific/MIME type icons
[LibreOffice.git] / vcl / skia / win / gdiimpl.cxx
blobd063b440cf31cc64fc8201625d90b9384fff2f9c
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 <SkCanvas.h>
24 #include <SkPaint.h>
25 #include <SkPixelRef.h>
26 #include <SkTypeface_win.h>
27 #include <SkFont.h>
28 #include <SkFontMgr.h>
29 #include <tools/sk_app/win/WindowContextFactory_win.h>
30 #include <tools/sk_app/WindowContext.h>
32 #include <windows.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);
46 assert(!mSurface);
47 SkiaZone zone;
48 sk_app::DisplayParams displayParams;
49 assert(GetWidth() > 0 && GetHeight() > 0);
50 displayParams.fSurfaceProps = *surfaceProps();
51 switch (forceRaster ? RenderRaster : renderMethodToUse())
53 case RenderRaster:
54 mWindowContext = sk_app::window_context_factory::MakeRasterForWin(mWinParent.gethWnd(),
55 displayParams);
56 if (mWindowContext)
57 mSurface = mWindowContext->getBackbufferSurface();
58 break;
59 case RenderVulkan:
60 mWindowContext = sk_app::window_context_factory::MakeVulkanForWin(mWinParent.gethWnd(),
61 displayParams);
62 // See flushSurfaceToWindowContext().
63 if (mWindowContext)
64 mSurface = createSkSurface(GetWidth(), GetHeight());
65 break;
66 case RenderMetal:
67 abort();
68 break;
72 void WinSkiaSalGraphicsImpl::freeResources() {}
74 void WinSkiaSalGraphicsImpl::Flush() { performFlush(); }
76 bool WinSkiaSalGraphicsImpl::TryRenderCachedNativeControl(ControlCacheKey const& rControlCacheKey,
77 int nX, int nY)
79 static bool gbCacheEnabled = !getenv("SAL_WITHOUT_WIDGET_CACHE");
80 if (!gbCacheEnabled)
81 return false;
83 auto& controlsCache = SkiaControlsCache::get();
84 SkiaControlCacheType::const_iterator iterator = controlsCache.find(rControlCacheKey);
85 if (iterator == controlsCache.end())
86 return false;
88 preDraw();
89 SAL_INFO("vcl.skia.trace", "tryrendercachednativecontrol("
90 << this << "): "
91 << SkIRect::MakeXYWH(nX, nY, iterator->second->width(),
92 iterator->second->height()));
93 addUpdateRegion(
94 SkRect::MakeXYWH(nX, nY, iterator->second->width(), iterator->second->height()));
95 mSurface->getCanvas()->drawImage(iterator->second, nX, nY);
96 postDraw();
97 return true;
100 bool WinSkiaSalGraphicsImpl::RenderAndCacheNativeControl(CompatibleDC& rWhite, CompatibleDC& rBlack,
101 int nX, int nY,
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));
109 preDraw();
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);
115 postDraw();
117 if (!aControlCacheKey.canCacheControl())
118 return true;
119 SkiaControlCachePair pair(aControlCacheKey, std::move(image));
120 SkiaControlsCache::get().insert(std::move(pair));
121 return true;
124 sk_sp<SkTypeface>
125 WinSkiaSalGraphicsImpl::createDirectWriteTypeface(const WinFontInstance* pWinFont) try
127 using sal::systools::ThrowIfFailed;
128 IDWriteFactory* dwriteFactory;
129 WinSalGraphics::getDWriteFactory(&dwriteFactory);
130 if (!dwriteDone)
132 dwriteFontMgr = SkFontMgr_New_DirectWrite(dwriteFactory);
133 dwriteDone = true;
135 if (!dwriteFontMgr)
136 return nullptr;
138 IDWriteFontFace* fontFace = pWinFont->GetDWFontFace();
139 if (!fontFace)
140 return nullptr;
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),
163 SAL_WHERE);
165 UINT32 numberOfFiles;
166 ThrowIfFailed(fontFace->GetFiles(&numberOfFiles, nullptr), SAL_WHERE);
167 if (numberOfFiles != 1)
168 return nullptr;
170 sal::systools::COMReference<IDWriteFontFile> fontFile;
171 ThrowIfFailed(fontFace->GetFiles(&numberOfFiles, &fontFile), SAL_WHERE);
173 BOOL isSupported;
174 DWRITE_FONT_FILE_TYPE fileType;
175 UINT32 numberOfFonts;
176 ThrowIfFailed(fontFile->Analyze(&isSupported, &fileType, nullptr, &numberOfFonts),
177 SAL_WHERE);
178 if (!isSupported)
179 return nullptr;
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)))
188 continue;
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),
198 SAL_WHERE);
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",
210 e.what(),
211 "HRESULT 0x" << OUString::number(e.GetHresult(), 16) << ": "
212 << WindowsErrorStringFromHRESULT(e.GetHresult()));
213 return nullptr;
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();
223 LOGFONTW logFont;
224 if (GetObjectW(hLayoutFont, sizeof(logFont), &logFont) == 0)
226 assert(false);
227 return false;
229 sk_sp<SkTypeface> typeface = pWinFont->GetSkiaTypeface();
230 if (!typeface)
232 typeface = createDirectWriteTypeface(pWinFont);
233 bool dwrite = true;
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.
241 logFont.lfWidth = 0;
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));
248 dwrite = false;
249 if (!typeface)
250 return false;
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)
274 return false;
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);
291 return true;
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,
305 // others do not).
306 fontEdging = SkFont::Edging::kAlias;
307 SkPixelGeometry pixelGeometry = kUnknown_SkPixelGeometry;
308 BOOL set;
309 if (SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &set, 0) && set)
311 UINT set2;
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;
320 else
321 pixelGeometry = kRGB_H_SkPixelGeometry; // default
323 else
324 fontEdging = SkFont::Edging::kAntiAlias;
326 setPixelGeometry(pixelGeometry);
329 void WinSkiaSalGraphicsImpl::ClearDevFontCache()
331 dwriteFontMgr.reset();
332 dwriteFontSetBuilder.clear();
333 dwritePrivateCollection.clear();
334 dwriteDone = false;
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
345 SkiaZone zone;
346 assert(maRects.mnSrcWidth == white.maRects.mnSrcWidth
347 || maRects.mnSrcHeight == white.maRects.mnSrcHeight);
348 SkBitmap tmpBitmap;
349 if (!tmpBitmap.tryAllocPixels(SkImageInfo::Make(maRects.mnSrcWidth, maRects.mnSrcHeight,
350 kBGRA_8888_SkColorType, kPremul_SkAlphaType),
351 maRects.mnSrcWidth * 4))
352 abort();
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();
367 while (dest < end)
369 uint32_t alpha = 255 - abs(int(*src & 0xff) - int(*whiteSrc & 0xff));
370 *dest = (*src & 0x00ffffff) | (alpha << 24);
371 ++dest;
372 ++src;
373 ++whiteSrc;
375 tmpBitmap.notifyPixelsChanged();
376 tmpBitmap.setImmutable();
377 sk_sp<SkSurface> surface = createSkSurface(tmpBitmap.width(), tmpBitmap.height());
378 SkPaint paint;
379 paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha
380 SkCanvas* canvas = surface->getCanvas();
381 canvas->save();
382 // The data we got is upside-down.
383 SkMatrix matrix;
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);
388 canvas->restore();
389 return makeCheckedImageSnapshot(surface);
392 SkiaControlsCache::SkiaControlsCache()
393 : cache(200)
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;
405 namespace
407 std::unique_ptr<sk_app::WindowContext> createVulkanWindowContext(bool /*temporary*/)
409 SkiaZone zone;
410 sk_app::DisplayParams displayParams;
411 return sk_app::window_context_factory::MakeVulkanForWin(nullptr, displayParams);
415 void WinSkiaSalGraphicsImpl::prepareSkia()
417 initFontInfo();
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: */