tdf#130857 qt weld: Implement QtInstanceWidget::strip_mnemonic
[LibreOffice.git] / vcl / skia / osx / gdiimpl.cxx
blob7fa95e8d9fab7c383edf9c4ab5dad7d2e77683a5
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/.
9 * Some of this code is based on Skia source code, covered by the following
10 * license notice (see readlicense_oo for the full license):
12 * Copyright 2016 Google Inc.
14 * Use of this source code is governed by a BSD-style license that can be
15 * found in the LICENSE file.
19 #include <sal/config.h>
21 #include <skia/osx/gdiimpl.hxx>
23 #include <skia/utils.hxx>
24 #include <skia/zone.hxx>
26 //#include <tools/window/mac/WindowContextFactory_mac.h>
28 #include <quartz/CoreTextFont.hxx>
29 #include <quartz/SystemFontList.hxx>
30 #include <osx/salnativewidgets.h>
31 #include <skia/quartz/cgutils.h>
32 #include <tools/window/mac/MacWindowInfo.h>
33 #include <tools/window/mac/GaneshMetalWindowContext_mac.h>
35 #include <SkBitmap.h>
36 #include <SkCanvas.h>
37 #include <SkFont.h>
38 #include <SkFontMgr_mac_ct.h>
39 #include <SkTypeface_mac.h>
41 using namespace SkiaHelper;
43 namespace
45 struct SnapshotImageData
47 sk_sp<SkImage> image;
48 SkPixmap pixmap;
52 static void SnapshotImageDataCallback(void* pInfo, const void*, size_t)
54 if (pInfo)
55 delete static_cast<SnapshotImageData*>(pInfo);
58 AquaSkiaSalGraphicsImpl::AquaSkiaSalGraphicsImpl(AquaSalGraphics& rParent,
59 AquaSharedAttributes& rShared)
60 : SkiaSalGraphicsImpl(rParent, rShared.mpFrame)
61 , AquaGraphicsBackendBase(rShared, this)
65 AquaSkiaSalGraphicsImpl::~AquaSkiaSalGraphicsImpl()
67 DeInit(); // mac code doesn't call DeInit()
70 void AquaSkiaSalGraphicsImpl::createWindowSurfaceInternal(bool forceRaster)
72 assert(!mWindowContext);
73 assert(!mSurface);
74 SkiaZone zone;
75 skwindow::DisplayParams displayParams;
76 displayParams.fColorType = kN32_SkColorType;
77 skwindow::MacWindowInfo macWindow;
78 macWindow.fMainView = mrShared.mpFrame->mpNSView;
79 mScaling = getWindowScaling();
80 RenderMethod renderMethod = forceRaster ? RenderRaster : renderMethodToUse();
81 switch (renderMethod)
83 case RenderRaster:
84 // RasterWindowContext_mac uses OpenGL internally, which we don't want,
85 // so use our own surface and do blitting to the screen ourselves.
86 mSurface = createSkSurface(GetWidth() * mScaling, GetHeight() * mScaling);
87 break;
88 case RenderMetal:
89 mWindowContext = skwindow::MakeGaneshMetalForMac(macWindow, displayParams);
90 // Like with other GPU contexts, create a proxy offscreen surface (see
91 // flushSurfaceToWindowContext()). Here it's additionally needed because
92 // it appears that Metal surfaces cannot be read from, which would break things
93 // like copyArea().
94 if (mWindowContext)
95 mSurface = createSkSurface(GetWidth() * mScaling, GetHeight() * mScaling);
96 break;
97 case RenderVulkan:
98 abort();
99 break;
103 int AquaSkiaSalGraphicsImpl::getWindowScaling() const
105 // The system function returns float, but only integer multiples realistically make sense.
106 return sal::aqua::getWindowScaling();
109 void AquaSkiaSalGraphicsImpl::Flush() { performFlush(); }
111 void AquaSkiaSalGraphicsImpl::Flush(const tools::Rectangle&) { performFlush(); }
113 void AquaSkiaSalGraphicsImpl::WindowBackingPropertiesChanged() { windowBackingPropertiesChanged(); }
115 void AquaSkiaSalGraphicsImpl::flushSurfaceToWindowContext()
117 if (!isGPU())
119 // tdf159175 mark dirty area in NSWindow for redrawing
120 // This will cause -[SalFrameView drawRect:] to be called. That,
121 // in turn, will draw a CGImageRef of the surface fetched from
122 // AquaSkiaSalGraphicsImpl::createCGImageFromRasterSurface().
123 mrShared.refreshRect(mDirtyRect.x(), mDirtyRect.y(), mDirtyRect.width(),
124 mDirtyRect.height());
126 else
128 SkiaSalGraphicsImpl::flushSurfaceToWindowContext();
132 // For Raster we use our own screen blitting (see above).
133 CGImageRef AquaSkiaSalGraphicsImpl::createCGImageFromRasterSurface(const NSRect& rDirtyRect,
134 CGPoint& rImageOrigin,
135 bool& rImageFlipped)
137 if (isGPU() || !mSurface)
138 return nullptr;
140 // Based on AquaGraphicsBackend::drawBitmap().
141 if (!mrShared.checkContext())
142 return nullptr;
144 NSRect aIntegralRect = NSIntegralRect(rDirtyRect);
145 if (NSIsEmptyRect(aIntegralRect))
146 return nullptr;
148 // Do not use sub-rect, it creates copies of the data.
149 SnapshotImageData* pInfo = new SnapshotImageData;
150 pInfo->image = makeCheckedImageSnapshot(mSurface);
151 if (!pInfo->image->peekPixels(&pInfo->pixmap))
152 abort();
154 SkIRect aDirtyRect = SkIRect::MakeXYWH(
155 aIntegralRect.origin.x * mScaling, aIntegralRect.origin.y * mScaling,
156 aIntegralRect.size.width * mScaling, aIntegralRect.size.height * mScaling);
157 if (mrShared.isFlipped())
158 aDirtyRect = SkIRect::MakeXYWH(
159 aDirtyRect.x(), pInfo->pixmap.bounds().height() - aDirtyRect.y() - aDirtyRect.height(),
160 aDirtyRect.width(), aDirtyRect.height());
161 if (!aDirtyRect.intersect(pInfo->pixmap.bounds()))
163 delete pInfo;
164 return nullptr;
167 // If window scaling, then aDirtyRect is in scaled VCL coordinates and mSurface has
168 // screen size (=points,HiDPI).
169 // This creates the bitmap context from the cropped part, writable_addr32() will get
170 // the first pixel of aDirtyRect.topLeft(), and using pixmap.rowBytes() ensures the following
171 // pixel lines will be read from correct positions.
172 if (pInfo->pixmap.bounds() != aDirtyRect
173 && pInfo->pixmap.bounds().bottom() == aDirtyRect.bottom())
175 // HACK for tdf#145843: If aDirtyRect includes the last line but not the first pixel of it,
176 // then the rowBytes() trick would lead to the CG* functions thinking that even pixels after
177 // the pixmap data belong to the area (since the shifted x()+rowBytes() points there) and
178 // at least on Intel Mac they would actually read those data, even though I see no good reason
179 // to do that, as that's beyond the x()+width() for the last line. That could be handled
180 // by creating a subset SkImage (which as is said above copies data), or set the x coordinate
181 // to 0, which will then make rowBytes() match the actual data.
182 aDirtyRect.fLeft = 0;
183 // Related tdf#156630 pixmaps can be wider than the dirty rectangle
184 // This seems to most commonly occur when SAL_FORCE_HIDPI_SCALING=1
185 // and the native window scale is 2.
186 assert(aDirtyRect.width() <= pInfo->pixmap.bounds().width());
189 // tdf#145843 Do not use CGBitmapContextCreate() to create a bitmap context
190 // As described in the comment in the above code, CGBitmapContextCreate()
191 // and CGBitmapContextCreateWithData() will try to access pixels up to
192 // aDirtyRect.x() + pixmap.bounds.width() for each row. When reading the
193 // last line in the SkPixmap, the buffer allocated for the SkPixmap ends at
194 // aDirtyRect.x() + aDirtyRect.width() and aDirtyRect.width() is clamped to
195 // pixmap.bounds.width() - aDirtyRect.x().
196 // This behavior looks like an optimization within CGBitmapContextCreate()
197 // to draw with a single memcpy() so fix this bug by chaining the
198 // CGDataProvider(), CGImageCreate(), and CGImageCreateWithImageInRect()
199 // functions to create the screen image.
200 CGDataProviderRef dataProvider
201 = CGDataProviderCreateWithData(pInfo, pInfo->pixmap.writable_addr32(0, 0),
202 pInfo->pixmap.computeByteSize(), SnapshotImageDataCallback);
203 if (!dataProvider)
205 delete pInfo;
206 SAL_WARN("vcl.skia", "flushSurfaceToScreenGC(): Failed to allocate data provider");
207 return nullptr;
210 CGImageRef fullImage
211 = CGImageCreate(pInfo->pixmap.bounds().width(), pInfo->pixmap.bounds().height(), 8,
212 8 * pInfo->image->imageInfo().bytesPerPixel(), pInfo->pixmap.rowBytes(),
213 GetSalData()->mxRGBSpace,
214 SkiaToCGBitmapType(pInfo->image->colorType(), pInfo->image->alphaType()),
215 dataProvider, nullptr, false, kCGRenderingIntentDefault);
216 if (!fullImage)
218 CGDataProviderRelease(dataProvider);
219 SAL_WARN("vcl.skia", "flushSurfaceToScreenGC(): Failed to allocate full image");
220 return nullptr;
223 CGImageRef screenImage = CGImageCreateWithImageInRect(
224 fullImage,
225 CGRectMake(aDirtyRect.x(), aDirtyRect.y(), aDirtyRect.width(), aDirtyRect.height()));
226 if (!screenImage)
228 CGImageRelease(fullImage);
229 CGDataProviderRelease(dataProvider);
230 SAL_WARN("vcl.skia", "createCGImageFromRasterSurface(): Failed to allocate screen image");
231 return nullptr;
234 rImageOrigin = CGPointMake(aDirtyRect.x(), aDirtyRect.y());
235 rImageFlipped = mrShared.isFlipped();
237 CGImageRelease(fullImage);
238 CGDataProviderRelease(dataProvider);
240 return screenImage;
243 bool AquaSkiaSalGraphicsImpl::drawNativeControl(ControlType nType, ControlPart nPart,
244 const tools::Rectangle& rControlRegion,
245 ControlState nState, const ImplControlValue& aValue)
247 // tdf#157613 make sure surface is not a nullptr
248 checkSurface();
249 if (!mSurface)
250 return false;
252 // rControlRegion is not the whole area that the control should be painted to (e.g. highlight
253 // around focused lineedit is outside of it). Since we draw to a temporary bitmap, we need to find out
254 // the real size.
255 // Related tdf#163945 Reduce the size of the temporary bitmap
256 // Previously, the temporary bitmap was set to the control region
257 // expanded by 50 * mScaling (e.g. both width and height were
258 // increased by 200 pixels when running on a Retina display). This
259 // caused temporary bitmaps to be up to several times larger than
260 // needed. Also, drawing NSBox objects to a CGBitmapContext is
261 // noticeably slow so filling all that unneeded temporary bitmap
262 // area can slow down performance when a large number of NSBox
263 // objects like the status bar are redrawn in quick succession.
264 // Using getNativeControlRegion() isn't perfect, but it does try to
265 // account for the focus ring as well as the minimum width and/or
266 // height of the native control so union the two regions set by
267 // getNativeControlRegion() and add double the focus ring width on
268 // each side just to be safe. In most cases, this should ensure
269 // that the temporary bitmap is large enough to draw the entire
270 // native control and a focus ring.
271 tools::Rectangle boundingRegion(rControlRegion);
272 tools::Rectangle rNativeBoundingRegion;
273 tools::Rectangle rNativeContentRegion;
274 AquaSalGraphics* pGraphics = mrShared.mpFrame->mpGraphics;
275 if (pGraphics
276 && pGraphics->getNativeControlRegion(nType, nPart, rControlRegion, nState, aValue,
277 OUString(), rNativeBoundingRegion,
278 rNativeContentRegion))
280 boundingRegion.Union(rNativeBoundingRegion);
281 boundingRegion.Union(rNativeContentRegion);
283 boundingRegion.expand(2 * FOCUS_RING_WIDTH * mScaling);
285 // Do a scaled bitmap in HiDPI in order not to lose precision.
286 const tools::Long width = boundingRegion.GetWidth() * mScaling;
287 const tools::Long height = boundingRegion.GetHeight() * mScaling;
288 const size_t bytes = width * height * 4;
289 // Let Skia own the CGBitmapContext's buffer so that an SkImage
290 // can be created without Skia making a copy of the buffer
291 sk_sp<SkData> data = SkData::MakeZeroInitialized(bytes);
292 CGContextRef context = CGBitmapContextCreate(
293 data->writable_data(), width, height, 8, width * 4, GetSalData()->mxRGBSpace,
294 SkiaToCGBitmapType(mSurface->imageInfo().colorType(), kPremul_SkAlphaType));
295 if (!context)
297 SAL_WARN("vcl.skia", "drawNativeControl(): Failed to allocate bitmap context");
298 return false;
300 // Setup context state for drawing (performDrawNativeControl() e.g. fills background in some cases).
301 CGContextSetFillColorSpace(context, GetSalData()->mxRGBSpace);
302 CGContextSetStrokeColorSpace(context, GetSalData()->mxRGBSpace);
303 if (moLineColor)
305 RGBAColor lineColor(*moLineColor);
306 CGContextSetRGBStrokeColor(context, lineColor.GetRed(), lineColor.GetGreen(),
307 lineColor.GetBlue(), lineColor.GetAlpha());
309 if (moFillColor)
311 RGBAColor fillColor(*moFillColor);
312 CGContextSetRGBFillColor(context, fillColor.GetRed(), fillColor.GetGreen(),
313 fillColor.GetBlue(), fillColor.GetAlpha());
315 // Adjust for our drawn-to coordinates in the bitmap.
316 tools::Rectangle movedRegion(Point(rControlRegion.getX() - boundingRegion.getX(),
317 rControlRegion.getY() - boundingRegion.getY()),
318 rControlRegion.GetSize());
319 // Flip drawing upside down.
320 CGContextTranslateCTM(context, 0, height);
321 CGContextScaleCTM(context, 1, -1);
322 // And possibly scale the native drawing.
323 CGContextScaleCTM(context, mScaling, mScaling);
324 bool bOK = performDrawNativeControl(nType, nPart, movedRegion, nState, aValue, context,
325 mrShared.mpFrame);
326 CGContextRelease(context);
327 if (bOK)
329 preDraw();
330 SAL_INFO("vcl.skia.trace", "drawnativecontrol(" << this << "): " << rControlRegion << ":"
331 << int(nType) << "/" << int(nPart));
332 tools::Rectangle updateRect = boundingRegion;
333 // For background update only part that is not clipped, the same
334 // as in AquaGraphicsBackend::drawNativeControl().
335 if (nType == ControlType::WindowBackground)
336 updateRect.Intersection(mClipRegion.GetBoundRect());
337 addUpdateRegion(SkRect::MakeXYWH(updateRect.getX(), updateRect.getY(),
338 updateRect.GetWidth(), updateRect.GetHeight()));
339 SkRect drawRect = SkRect::MakeXYWH(boundingRegion.getX(), boundingRegion.getY(),
340 boundingRegion.GetWidth(), boundingRegion.GetHeight());
341 sk_sp<SkImage> image = SkImages::RasterFromData(
342 SkImageInfo::Make(width, height, mSurface->imageInfo().colorType(),
343 kPremul_SkAlphaType),
344 data, width * 4);
345 assert(image
346 && drawRect.width() * mScaling == image->width()); // no scaling should be needed
347 getDrawCanvas()->drawImageRect(image, drawRect, SkSamplingOptions());
348 // Related: tdf#156881 flush the canvas after drawing the pixel buffer
349 if (auto dContext = GrAsDirectContext(getDrawCanvas()->recordingContext()))
350 dContext->flushAndSubmit();
351 ++pendingOperationsToFlush; // tdf#136369
352 postDraw();
354 return bOK;
357 void AquaSkiaSalGraphicsImpl::drawTextLayout(const GenericSalLayout& rLayout)
359 const bool bSubpixelPositioning = rLayout.GetSubpixelPositioning();
360 const CoreTextFont& rFont = *static_cast<const CoreTextFont*>(&rLayout.GetFont());
361 const vcl::font::FontSelectPattern& rFontSelect = rFont.GetFontSelectPattern();
362 int nHeight = rFontSelect.mnHeight;
363 int nWidth = rFontSelect.mnWidth ? rFontSelect.mnWidth : nHeight;
364 if (nWidth == 0 || nHeight == 0)
366 SAL_WARN("vcl.skia", "DrawTextLayout(): rFontSelect.mnHeight is zero!?");
367 return;
370 if (!fontManager)
372 std::unique_ptr<SystemFontList> fontList = GetCoretextFontList();
373 if (fontList == nullptr)
375 SAL_WARN("vcl.skia", "DrawTextLayout(): No coretext font list");
376 fontManager = SkFontMgr_New_CoreText(nullptr);
378 else
380 fontManager = SkFontMgr_New_CoreText(fontList->fontCollection());
384 sk_sp<SkTypeface> typeface = SkMakeTypefaceFromCTFont(rFont.GetCTFont());
385 SkFont font(typeface);
386 font.setSize(nHeight);
387 // font.setScaleX(rFont.mfFontStretch); TODO
388 if (rFont.NeedsArtificialBold())
389 font.setEmbolden(true);
391 SkFont::Edging ePreferredAliasing
392 = bSubpixelPositioning ? SkFont::Edging::kSubpixelAntiAlias : SkFont::Edging::kAntiAlias;
393 if (bSubpixelPositioning)
395 // note that SkFont defaults to a BaselineSnap of true, so I think really only
396 // subpixel in text direction
397 font.setSubpixel(true);
399 font.setEdging(mrShared.mbNonAntialiasedText ? SkFont::Edging::kAlias : ePreferredAliasing);
401 // Vertical font, use width as "height".
402 SkFont verticalFont(font);
403 verticalFont.setSize(nHeight);
404 // verticalFont.setSize(nWidth); TODO
405 // verticalFont.setScaleX(1.0 * nHeight / nWidth);
407 drawGenericLayout(rLayout, mrShared.maTextColor, font, verticalFont);
410 namespace
412 std::unique_ptr<skwindow::WindowContext> createMetalWindowContext(bool /*temporary*/)
414 skwindow::DisplayParams displayParams;
415 skwindow::MacWindowInfo macWindow;
416 macWindow.fMainView = nullptr;
417 return skwindow::MakeGaneshMetalForMac(macWindow, displayParams);
421 void AquaSkiaSalGraphicsImpl::prepareSkia() { SkiaHelper::prepareSkia(createMetalWindowContext); }
423 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */