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