xref: /core/vcl/quartz/salgdi.cxx (revision c77a53433d9cfb0f03a3ebd04707ddccb11a3cca)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <sal/config.h>
21 
22 #include <memory>
23 
24 #include <sal/log.hxx>
25 #include <config_folders.h>
26 
27 #include <basegfx/matrix/b2dhommatrix.hxx>
28 #include <basegfx/matrix/b2dhommatrixtools.hxx>
29 #include <basegfx/polygon/b2dpolygon.hxx>
30 #include <basegfx/polygon/b2dpolygontools.hxx>
31 #include <basegfx/range/b2drectangle.hxx>
32 #include <osl/diagnose.h>
33 #include <osl/file.hxx>
34 #include <osl/process.h>
35 #include <rtl/bootstrap.h>
36 #include <rtl/strbuf.hxx>
37 #include <rtl/ustrbuf.hxx>
38 #include <tools/long.hxx>
39 #include <comphelper/lok.hxx>
40 
41 #include <vcl/metric.hxx>
42 #include <vcl/fontcharmap.hxx>
43 #include <vcl/svapp.hxx>
44 #include <vcl/sysdata.hxx>
45 
46 #include <fontsubset.hxx>
47 #include <impfont.hxx>
48 #include <font/FontMetricData.hxx>
49 #include <font/fontsubstitution.hxx>
50 #include <font/PhysicalFontCollection.hxx>
51 
52 #ifdef MACOSX
53 #include <osx/salframe.h>
54 #endif
55 #include <quartz/utils.h>
56 #ifdef IOS
57 #include <ios/iosinst.hxx>
58 #endif
59 #include <sallayout.hxx>
60 
61 #include <config_features.h>
62 #include <vcl/skia/SkiaHelper.hxx>
63 #if !HAVE_FEATURE_SKIA
64 static_assert(false, "skia is required on macOS");
65 #endif
66 #include <skia/osx/gdiimpl.hxx>
67 
68 #include <quartz/SystemFontList.hxx>
69 #include <quartz/CoreTextFont.hxx>
70 #include <quartz/CoreTextFontFace.hxx>
71 #include <quartz/salgdi.h>
72 
73 using namespace vcl;
74 
75 namespace {
76 
77 class CoreTextGlyphFallbackSubstititution
78 :    public vcl::font::GlyphFallbackFontSubstitution
79 {
80 public:
81     bool FindFontSubstitute(vcl::font::FontSelectPattern&, LogicalFontInstance* pLogicalFont, OUString&) const override;
82 };
83 
FontHasCharacter(CTFontRef pFont,const OUString & rString,sal_Int32 nIndex,sal_Int32 nLen)84 bool FontHasCharacter(CTFontRef pFont, const OUString& rString, sal_Int32 nIndex, sal_Int32 nLen)
85 {
86     auto const glyphs = std::make_unique<CGGlyph[]>(nLen);
87     return CTFontGetGlyphsForCharacters(pFont, reinterpret_cast<const UniChar*>(rString.getStr() + nIndex), glyphs.get(), nLen);
88 }
89 
90 }
91 
FindFontSubstitute(vcl::font::FontSelectPattern & rPattern,LogicalFontInstance * pLogicalFont,OUString & rMissingChars) const92 bool CoreTextGlyphFallbackSubstititution::FindFontSubstitute(vcl::font::FontSelectPattern& rPattern, LogicalFontInstance* pLogicalFont,
93     OUString& rMissingChars) const
94 {
95     bool bFound = false;
96     CoreTextFont* pFont = static_cast<CoreTextFont*>(pLogicalFont);
97     CFStringRef pStr = CreateCFString(rMissingChars);
98     if (pStr)
99     {
100         CTFontRef pFallback = CTFontCreateForString(pFont->GetCTFont(), pStr, CFRangeMake(0, CFStringGetLength(pStr)));
101         if (pFallback)
102         {
103             bFound = true;
104 
105             // tdf#148470 remove the resolved chars from rMissing to flag which ones are still missing
106             // for an attempt with another font
107             OUStringBuffer aStillMissingChars;
108             for (sal_Int32 nStrIndex = 0; nStrIndex < rMissingChars.getLength();)
109             {
110                 sal_Int32 nOldStrIndex = nStrIndex;
111                 rMissingChars.iterateCodePoints(&nStrIndex);
112                 sal_Int32 nCharLength = nStrIndex - nOldStrIndex;
113                 if (!FontHasCharacter(pFallback, rMissingChars, nOldStrIndex, nCharLength))
114                     aStillMissingChars.append(rMissingChars.getStr() + nOldStrIndex, nCharLength);
115             }
116             rMissingChars = aStillMissingChars.toString();
117 
118             CTFontDescriptorRef pDesc = CTFontCopyFontDescriptor(pFallback);
119             FontAttributes rAttr = DevFontFromCTFontDescriptor(pDesc, nullptr);
120 
121             rPattern.maSearchName = rAttr.GetFamilyName();
122 
123             CFRelease(pFallback);
124             CFRelease(pDesc);
125         }
126         CFRelease(pStr);
127     }
128 
129     return bFound;
130 }
131 
AquaSalGraphics(bool bPrinter)132 AquaSalGraphics::AquaSalGraphics(bool bPrinter)
133     : mnRealDPIX( 0 )
134     , mnRealDPIY( 0 )
135 {
136     SAL_INFO( "vcl.quartz", "AquaSalGraphics::AquaSalGraphics() this=" << this );
137 
138     // tdf#146842 Do not use Skia for printing
139     // Skia does not work with a native print graphics contexts. I am not sure
140     // why but from what I can see, the Skia implementation drawing to a bitmap
141     // buffer. However, in an NSPrintOperation, the print view's backing buffer
142     // is CGPDFContext so even if this bug could be solved by blitting the
143     // Skia bitmap buffer, the printed PDF would not have selectable text so
144     // always disable Skia for print graphics contexts.
145     if(bPrinter)
146         mpBackend.reset(new AquaGraphicsBackend(maShared));
147     else
148     {
149         assert(SkiaHelper::isVCLSkiaEnabled() && "skia is required on macOS");
150         mpBackend.reset(new AquaSkiaSalGraphicsImpl(*this, maShared));
151     }
152 
153     for (int i = 0; i < MAX_FALLBACK; ++i)
154         mpFont[i] = nullptr;
155 
156     if (comphelper::LibreOfficeKit::isActive())
157         initWidgetDrawBackends(true);
158 }
159 
~AquaSalGraphics()160 AquaSalGraphics::~AquaSalGraphics()
161 {
162     SAL_INFO( "vcl.quartz", "AquaSalGraphics::~AquaSalGraphics() this=" << this );
163 
164     maShared.unsetClipPath();
165 
166     ReleaseFonts();
167 
168     maShared.mpXorEmulation.reset();
169 
170 #ifdef IOS
171     if (maShared.mbForeignContext)
172         return;
173 #endif
174     if (maShared.maLayer.isSet())
175     {
176         CGLayerRelease(maShared.maLayer.get());
177     }
178     else if (maShared.maContextHolder.isSet()
179 #ifdef MACOSX
180              && maShared.mbWindow
181 #endif
182              )
183     {
184         // destroy backbuffer bitmap context that we created ourself
185         CGContextRelease(maShared.maContextHolder.get());
186         maShared.maContextHolder.set(nullptr);
187     }
188 }
189 
GetImpl() const190 SalGraphicsImpl* AquaSalGraphics::GetImpl() const
191 {
192     return mpBackend->GetImpl();
193 }
194 
SetTextColor(Color nColor)195 void AquaSalGraphics::SetTextColor( Color nColor )
196 {
197     maShared.maTextColor = nColor;
198 }
199 
GetFontMetric(FontMetricDataRef & rxFontMetric,int nFallbackLevel)200 void AquaSalGraphics::GetFontMetric(FontMetricDataRef& rxFontMetric, int nFallbackLevel)
201 {
202     if (nFallbackLevel < MAX_FALLBACK && mpFont[nFallbackLevel])
203     {
204         mpFont[nFallbackLevel]->GetFontMetric(rxFontMetric);
205     }
206 }
207 
AddTempDevFont(const OUString & rFontFileURL)208 static bool AddTempDevFont(const OUString& rFontFileURL)
209 {
210     OUString aUSystemPath;
211     OSL_VERIFY( !osl::FileBase::getSystemPathFromFileURL( rFontFileURL, aUSystemPath ) );
212     OString aCFileName = OUStringToOString( aUSystemPath, RTL_TEXTENCODING_UTF8 );
213 
214     CFStringRef rFontPath = CFStringCreateWithCString(nullptr, aCFileName.getStr(), kCFStringEncodingUTF8);
215     CFURLRef rFontURL = CFURLCreateWithFileSystemPath(nullptr, rFontPath, kCFURLPOSIXPathStyle, true);
216 
217     CFErrorRef error;
218     bool success = CTFontManagerRegisterFontsForURL(rFontURL, kCTFontManagerScopeProcess, &error);
219     if (success)
220     {
221         // tdf155212 clear the cached system font list after loading a font
222         // If the system font is not cached in SalData, loading embedded
223         // fonts will be extremely slow and will trigger each frame and each
224         // of its internal subframes to reload the system font list when
225         // loading documents with embedded fonts.
226         // So instead, reenable caching of the system font list in SalData
227         // by reverting commit 3b6e9582ce43242a2304047561116bb26808408b.
228         // Then, to prevent tdf#72456 from reoccurring, clear the cached
229         // system font list after a font has been loaded or unloaded.
230         // This should cause the first frame's request to reload the cached
231         // system font list and all subsequent frames will avoid doing
232         // duplicate font reloads.
233         SalData* pSalData = GetSalData();
234         pSalData->mpFontList.reset();
235     }
236     else
237     {
238         CFRelease(error);
239     }
240     CFRelease(rFontPath);
241     CFRelease(rFontURL);
242 
243     return success;
244 }
245 
RemoveTempDevFont(const OUString & rFontFileURL)246 static bool RemoveTempDevFont(const OUString& rFontFileURL)
247 {
248     OUString aUSystemPath;
249     OSL_VERIFY(!osl::FileBase::getSystemPathFromFileURL(rFontFileURL, aUSystemPath));
250     OString aCFileName = OUStringToOString(aUSystemPath, RTL_TEXTENCODING_UTF8);
251 
252     CFStringRef rFontPath
253         = CFStringCreateWithCString(nullptr, aCFileName.getStr(), kCFStringEncodingUTF8);
254     CFURLRef rFontURL
255         = CFURLCreateWithFileSystemPath(nullptr, rFontPath, kCFURLPOSIXPathStyle, true);
256 
257     CTFontManagerUnregisterFontsForURL(rFontURL, kCTFontManagerScopeProcess, nullptr);
258 
259     CFRelease(rFontPath);
260     CFRelease(rFontURL);
261 
262     return true; // Assume that even errors meant that there was nothing to remove
263 }
264 
AddTempFontDir(const OUString & rFontDirUrl)265 static void AddTempFontDir( const OUString &rFontDirUrl )
266 {
267     osl::Directory aFontDir( rFontDirUrl );
268     osl::FileBase::RC rcOSL = aFontDir.open();
269     if( rcOSL == osl::FileBase::E_None )
270     {
271         osl::DirectoryItem aDirItem;
272 
273         while( aFontDir.getNextItem( aDirItem, 10 ) == osl::FileBase::E_None )
274         {
275             osl::FileStatus aFileStatus( osl_FileStatus_Mask_FileURL );
276             rcOSL = aDirItem.getFileStatus( aFileStatus );
277             if ( rcOSL == osl::FileBase::E_None )
278             {
279                 AddTempDevFont(aFileStatus.getFileURL());
280             }
281         }
282     }
283 }
284 
AddLocalTempFontDirs()285 static void AddLocalTempFontDirs()
286 {
287     static bool bFirst = true;
288     if( !bFirst )
289         return;
290 
291     bFirst = false;
292 
293     // add private font files
294 
295     OUString aBrandStr( "$BRAND_BASE_DIR" );
296     rtl_bootstrap_expandMacros( &aBrandStr.pData );
297 
298     // internal font resources, required for normal operation, like OpenSymbol
299     AddTempFontDir( aBrandStr + "/" LIBO_SHARE_RESOURCE_FOLDER "/common/fonts/" );
300 
301     AddTempFontDir( aBrandStr + "/" LIBO_SHARE_FOLDER "/fonts/truetype/" );
302 }
303 
GetDevFontList(vcl::font::PhysicalFontCollection * pFontCollection)304 void AquaSalGraphics::GetDevFontList(vcl::font::PhysicalFontCollection* pFontCollection)
305 {
306     SAL_WARN_IF( !pFontCollection, "vcl", "AquaSalGraphics::GetDevFontList(NULL) !");
307 
308     AddLocalTempFontDirs();
309 
310     // The idea is to cache the list of system fonts once it has been generated.
311     // SalData seems to be a good place for this caching. However we have to
312     // carefully make the access to the font list thread-safe. If we register
313     // a font-change event handler to update the font list in case fonts have
314     // changed on the system we have to lock access to the list. The right
315     // way to do that is the solar mutex since GetDevFontList is protected
316     // through it as should be all event handlers
317 
318     // Related tdf#155212: the system font list needs to be cached but that
319     // should not cause tdf#72456 to reoccur now that the cached system font
320     // is cleared immediately after a font has been loaded
321     SalData* pSalData = GetSalData();
322     if( !pSalData->mpFontList )
323         pSalData->mpFontList = GetCoretextFontList();
324 
325     // Copy all PhysicalFontFace objects contained in the SystemFontList
326     pSalData->mpFontList->AnnounceFonts( *pFontCollection );
327 
328     static CoreTextGlyphFallbackSubstititution aSubstFallback;
329     pFontCollection->SetFallbackHook(&aSubstFallback);
330 }
331 
ClearDevFontCache()332 void AquaSalGraphics::ClearDevFontCache()
333 {
334     SalData* pSalData = GetSalData();
335     pSalData->mpFontList.reset();
336 }
337 
AddTempDevFont(vcl::font::PhysicalFontCollection *,const OUString & rFontFileURL,const OUString &)338 bool AquaSalGraphics::AddTempDevFont(vcl::font::PhysicalFontCollection*,
339     const OUString& rFontFileURL, const OUString& /*rFontName*/)
340 {
341     return ::AddTempDevFont(rFontFileURL);
342 }
343 
RemoveTempDevFont(const OUString & rFontFileURL,const OUString &)344 bool AquaSalGraphics::RemoveTempDevFont(const OUString& rFontFileURL, const OUString& /*rFontName*/)
345 {
346     return ::RemoveTempDevFont(rFontFileURL);
347 }
348 
DrawTextLayout(const GenericSalLayout & rLayout)349 void AquaSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
350 {
351     mpBackend->drawTextLayout(rLayout);
352 }
353 
354 #ifdef MACOSX
355 
ShouldDownscaleIconsAtSurface(double & rScaleOut) const356 bool AquaSalGraphics::ShouldDownscaleIconsAtSurface(double& rScaleOut) const
357 {
358     if (comphelper::LibreOfficeKit::isActive())
359         return SalGraphics::ShouldDownscaleIconsAtSurface(rScaleOut);
360     rScaleOut = sal::aqua::getWindowScaling();
361     return true;
362 }
363 
364 #endif
365 
drawTextLayout(const GenericSalLayout & rLayout)366 void AquaGraphicsBackend::drawTextLayout(const GenericSalLayout& rLayout)
367 {
368 #ifdef IOS
369     if (!mrShared.checkContext())
370     {
371         SAL_WARN("vcl.quartz", "AquaSalGraphics::DrawTextLayout() without context");
372         return;
373     }
374 #endif
375 
376     const CoreTextFont& rFont = *static_cast<const CoreTextFont*>(&rLayout.GetFont());
377     const vcl::font::FontSelectPattern& rFontSelect = rFont.GetFontSelectPattern();
378     if (rFontSelect.mnHeight == 0)
379     {
380         SAL_WARN("vcl.quartz", "AquaSalGraphics::DrawTextLayout(): rFontSelect.mnHeight is zero!?");
381         return;
382     }
383 
384     CTFontRef pCTFont = rFont.GetCTFont();
385     CGAffineTransform aRotMatrix = CGAffineTransformMakeRotation(-rFont.mfFontRotation);
386 
387     basegfx::B2DPoint aPos;
388     const GlyphItem* pGlyph;
389     std::vector<CGGlyph> aGlyphIds;
390     std::vector<CGPoint> aGlyphPos;
391     std::vector<bool> aGlyphOrientation;
392     int nStart = 0;
393     while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
394     {
395         CGPoint aGCPos = CGPointMake(aPos.getX(), -aPos.getY());
396 
397         // Whether the glyph should be upright in vertical mode or not
398         bool bUprightGlyph = false;
399 
400         if (rFont.mfFontRotation)
401         {
402             if (pGlyph->IsVertical())
403                 bUprightGlyph = true;
404             else
405                 // Transform the position of rotated glyphs.
406                 aGCPos = CGPointApplyAffineTransform(aGCPos, aRotMatrix);
407         }
408 
409         aGlyphIds.push_back(pGlyph->glyphId());
410         aGlyphPos.push_back(aGCPos);
411         aGlyphOrientation.push_back(bUprightGlyph);
412     }
413 
414     if (aGlyphIds.empty())
415         return;
416 
417     assert(aGlyphIds.size() == aGlyphPos.size());
418 #if 0
419     std::cerr << "aGlyphIds:[";
420     for (unsigned i = 0; i < aGlyphIds.size(); i++)
421     {
422         if (i > 0)
423             std::cerr << ",";
424         std::cerr << aGlyphIds[i];
425     }
426     std::cerr << "]\n";
427     std::cerr << "aGlyphPos:[";
428     for (unsigned i = 0; i < aGlyphPos.size(); i++)
429     {
430         if (i > 0)
431             std::cerr << ",";
432         std::cerr << aGlyphPos[i];
433     }
434     std::cerr << "]\n";
435 #endif
436 
437     mrShared.maContextHolder.saveState();
438     RGBAColor textColor( mrShared.maTextColor );
439 
440     // The view is vertically flipped (no idea why), flip it back.
441     CGContextScaleCTM(mrShared.maContextHolder.get(), 1.0, -1.0);
442     CGContextSetShouldAntialias(mrShared.maContextHolder.get(), !mrShared.mbNonAntialiasedText);
443     CGContextSetFillColor(mrShared.maContextHolder.get(), textColor.AsArray());
444 
445     if (rFont.NeedsArtificialBold())
446     {
447 
448         float fSize = rFontSelect.mnHeight / 23.0f;
449         CGContextSetStrokeColor(mrShared.maContextHolder.get(), textColor.AsArray());
450         CGContextSetLineWidth(mrShared.maContextHolder.get(), fSize);
451         CGContextSetTextDrawingMode(mrShared.maContextHolder.get(), kCGTextFillStroke);
452     }
453 
454     if (rLayout.GetSubpixelPositioning())
455     {
456         CGContextSetAllowsFontSubpixelQuantization(mrShared.maContextHolder.get(), false);
457         CGContextSetShouldSubpixelQuantizeFonts(mrShared.maContextHolder.get(), false);
458         CGContextSetAllowsFontSubpixelPositioning(mrShared.maContextHolder.get(), true);
459         CGContextSetShouldSubpixelPositionFonts(mrShared.maContextHolder.get(), true);
460     }
461 
462     auto aIt = aGlyphOrientation.cbegin();
463     while (aIt != aGlyphOrientation.cend())
464     {
465         bool bUprightGlyph = *aIt;
466         // Find the boundary of the run of glyphs with the same rotation, to be
467         // drawn together.
468         auto aNext = std::find(aIt, aGlyphOrientation.cend(), !bUprightGlyph);
469         size_t nStartIndex = std::distance(aGlyphOrientation.cbegin(), aIt);
470         size_t nLen = std::distance(aIt, aNext);
471 
472         mrShared.maContextHolder.saveState();
473         if (rFont.mfFontRotation && !bUprightGlyph)
474         {
475             CGContextRotateCTM(mrShared.maContextHolder.get(), rFont.mfFontRotation);
476         }
477         CTFontDrawGlyphs(pCTFont, &aGlyphIds[nStartIndex], &aGlyphPos[nStartIndex], nLen, mrShared.maContextHolder.get());
478         mrShared.maContextHolder.restoreState();
479 
480         aIt = aNext;
481     }
482 
483     mrShared.maContextHolder.restoreState();
484 }
485 
SetFont(LogicalFontInstance * pReqFont,int nFallbackLevel)486 void AquaSalGraphics::SetFont(LogicalFontInstance* pReqFont, int nFallbackLevel)
487 {
488     // release the font
489     for (int i = nFallbackLevel; i < MAX_FALLBACK; ++i)
490     {
491         if (!mpFont[i])
492             break;
493         mpFont[i].clear();
494     }
495 
496     if (!pReqFont)
497         return;
498 
499     // update the font
500     mpFont[nFallbackLevel] = static_cast<CoreTextFont*>(pReqFont);
501 }
502 
GetTextLayout(int nFallbackLevel)503 std::unique_ptr<GenericSalLayout> AquaSalGraphics::GetTextLayout(int nFallbackLevel)
504 {
505     assert(mpFont[nFallbackLevel]);
506     if (!mpFont[nFallbackLevel])
507         return nullptr;
508     return std::make_unique<GenericSalLayout>(*mpFont[nFallbackLevel]);
509 }
510 
GetFontCharMap() const511 FontCharMapRef AquaSalGraphics::GetFontCharMap() const
512 {
513     if (!mpFont[0])
514     {
515         return FontCharMapRef( new FontCharMap() );
516     }
517 
518     return mpFont[0]->GetFontFace()->GetFontCharMap();
519 }
520 
GetFontCapabilities(vcl::FontCapabilities & rFontCapabilities) const521 bool AquaSalGraphics::GetFontCapabilities(vcl::FontCapabilities &rFontCapabilities) const
522 {
523     if (!mpFont[0])
524         return false;
525 
526     return mpFont[0]->GetFontFace()->GetFontCapabilities(rFontCapabilities);
527 }
528 
Flush()529 void AquaSalGraphics::Flush()
530 {
531     mpBackend->Flush();
532 }
533 
Flush(const tools::Rectangle & rRect)534 void AquaSalGraphics::Flush( const tools::Rectangle& rRect )
535 {
536     mpBackend->Flush( rRect );
537 }
538 
WindowBackingPropertiesChanged()539 void AquaSalGraphics::WindowBackingPropertiesChanged()
540 {
541     mpBackend->WindowBackingPropertiesChanged();
542 }
543 
544 #ifdef IOS
545 
checkContext()546 bool AquaSharedAttributes::checkContext()
547 {
548     if (mbForeignContext)
549     {
550         SAL_INFO("vcl.ios", "CheckContext() this=" << this << ", mbForeignContext, return true");
551         return true;
552     }
553 
554     SAL_INFO( "vcl.ios", "CheckContext() this=" << this << ",  not foreign, return false");
555     return false;
556 }
557 
558 #endif
559 
560 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
561