Strange interaction between CTFontManager and NSFontManager

On macOS 10.14 there seems to be a bug in the Core Text framework. When I use NSFontManager to check the availability of a font before loading it using CTFontManager, I get a strange behavior and possibly a memory leak. When using a Cocoa port of thii/FontAwesome.swift for VimR, I noticed that the FontAwesome font files are loaded over and over again. Even after the main window instance is deinited, the memory used to load the font files seems to stay as shown in the following Instruments screenshot

Example image

To test this, I used the following

extension NSFontManager {

  func postscriptNamesOfAvailableMembers(
    ofFontFamily family: String
  ) -> [String] {
    return self
      .availableMembers(ofFontFamily: family)?
      .compactMap { entry in return entry[0] as? String } ?? []
  }

  func loadFont(fromUrl url: URL) throws {
    var error: Unmanaged<CFError>?
    CTFontManagerRegisterFontsForURL(url as NSURL, .process, &error)

    guard let e = error?.takeRetainedValue() else { return }
    throw e
  }
}

When I load the regular and the solid FontAwesome fonts without calling availableMembers(ofFontFamily:) of NSFontManager to check the availability, then the two fonts load without any problems. The following

let familyName = "Font Awesome 5 Free"
let solidUrl = URL(fileURLWithPath: "...")
let regularUrl = URL(fileURLWithPath: "...")

let fm = NSFontManager.shared
do {
  try fm.loadFont(fromUrl: solidUrl)
  try fm.loadFont(fromUrl: regularUrl)
} catch {
  Swift.print(error)
}

print(fm.postscriptNamesOfAvailableMembers(ofFontFamily: familyName))

prints

["FontAwesome5FreeRegular", "FontAwesome5FreeSolid"]

However, if I call availableMembers(ofFontFamily:) before loading each font,

print(fm.postscriptNamesOfAvailableMembers(ofFontFamily: familyName))
do {
  try fm.loadFont(fromUrl: solidUrl)
  print(fm.postscriptNamesOfAvailableMembers(ofFontFamily: familyName))

  try fm.loadFont(fromUrl: regularUrl)
  print(fm.postscriptNamesOfAvailableMembers(ofFontFamily: familyName))

  try fm.loadFont(fromUrl: regularUrl)
  print(fm.postscriptNamesOfAvailableMembers(ofFontFamily: familyName))
} catch {
  print(error)
}
print(fm.postscriptNamesOfAvailableMembers(ofFontFamily: familyName))

the result is

[]
["FontAwesome5FreeSolid"]
["FontAwesome5FreeSolid"]
Error Domain=com.apple.CoreText.CTFontManagerErrorDomain Code=105 "The file has already been registered in the specified scope." UserInfo={NSLocalizedDescription=The file has already been registered in the specified scope., CTFontManagerErrorFontURLs=(
    "file:///..."
), NSLocalizedFailureReason=Font registration was unsuccessful.}
["FontAwesome5FreeSolid"]

In addition to the wrong behavior I get the same memory leak shown above.

comments powered by Disqus