Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add registerFont to Apple platforms #598

Merged

Conversation

darronschall
Copy link
Contributor

@darronschall darronschall commented Nov 15, 2023

I was running into a SwiftUI issue where the .fontWeight modifier stopped working when I moved fonts out of my iOS project into and into my shared library via moko-resources.

For example:

extension Font {
    public static var body: Font {
        return Font(MR.fontsOpenSans().regular.uiFont(
            withSize: UIFont.preferredFont(forTextStyle: .body).pointSize
        ))
    }
}
Text("Example")
    .font(.body)
    .fontWeight(.bold) // My project also has `MR.fontsOpenSans().bold` but this wasn't switching to it

It took a little bit to track down the problem. After some research, I found CTFontManagerRegisterGraphicsFont and later CTFontManagerRegisterFontsForURL

This PR does a few things:

  • Moves iOS / macOS common code to appleMain FontResource to maximize code sharing, and creates platform-specific FontResourceExt for UIFont / NSFont usage.
  • Adds an NSErrorException throwable that wraps an NSError to make it easier for Swift to inspect the nested NSError thrown.
  • Adds registerFont to the Apple FontResource, meant to be called during App startup. Once a font is registered, then .fontWeight works as expected again.
  • Moves the fontRef to a lazy delegate instead of always creating it in the class initializer. With the new registerFont, there are scenarios in which it is not necessary to create this reference.
  • Improves memory handling when creating the fontRef

To use registerFont(), enumerate the list of fonts and call it on each one during application startup. Mine looks like this:

public static func loadSharedFonts() {
    let fonts = [
        MR.fontsOpenSans().regular,
        MR.fontsOpenSans().italic,
        MR.fontsOpenSans().light,
        MR.fontsOpenSans().semiBold,
        MR.fontsOpenSans().bold,
        MR.fontsOpenSans().extraBold,
        MR.fontsOpenSansCondensed().regular,
        MR.fontsOpenSansCondensed().semiBold
    ]
    
    fonts.forEach { fontResource in
        do {
            try fontResource.registerFont()
        } catch {
            // Example error inspection; some errors are safe to ignore. Check the error code
            // against CTFontManagerError values per https://developer.apple.com/documentation/coretext/ctfontmanagererror
            if let wrappedError = error.wrappedNSError {
                print("wrappedError domain: \(wrappedError.domain)")
                print("wrappedError code: \(wrappedError.code)")
                print("wrappedError userInfo: \(wrappedError.userInfo)")
            }
        }
    }
}

@darronschall darronschall force-pushed the ios-font-resource-register-font branch from 74acb81 to a6e6222 Compare November 15, 2023 17:22
@Alex009 Alex009 changed the base branch from master to develop January 14, 2024 13:52
This makes the macOS `FontResource` consistent with the iOS `FontResource`.
The `FontResource` code is the same on both iOS and macOS, except for the helper to create either a `UIFont` or an `NSFont`. Move those helpers to platform-specific code, while keeping the common code shared for easier maintenance.

This follows the same approach that we use for `ColorResource`.
Kotlin/Native can't throw an `NSError::class`. So, we make an `NSErrorException` wrapper that is `Throwable` that contains the underlying `NSError`.

This helps us propagate `NSError`s to calling code. Callers an inspect the underlying error, instead of being stuck with just a `KotlinException`.

In your Swift code, use this `Error` extension to make it easy to inspect the underlying error:

```
extension Error {
    var wrappedNSError: NSError? {
        let kotlinException = (self as NSError).userInfo["KotlinException"] as? KotlinException
        let nsErrorException = kotlinException as? NSErrorException
        return nsErrorException?.nsError as? NSError
    }
}
```

Example usage:

```
do {
    try kotlinMethodThatThrowsNSErrorException()
} catch {
    if let wrappedError = error.wrappedNSError {
        print("wrappedError domain: \(wrappedError.domain)")
        print("wrappedError code: \(wrappedError.code)")
        print("wrappedError userInfo: \(wrappedError.userInfo)")
    }
}
```
This uses `CTFontManagerRegisterGraphicsFont` in order to register the font with the font manager. See: https://developer.apple.com/documentation/coretext/1499499-ctfontmanagerregistergraphicsfon

Calling this method during application startup fixes an issue in SwiftUI where the `.fontWeight` call would fail to find the correct font.

In the event this method fails and throws, see https://developer.apple.com/documentation/coretext/ctfontmanagererror for the list of possible error codes.
The documentation for `CTFontManagerRegisterGraphicsFont` at https://developer.apple.com/documentation/coretext/1499499-ctfontmanagerregistergraphicsfon hints that we should use `CTFontManagerRegisterFontsForURL` instead (since our fonts are backed by files in our bundle):

> Fonts that are backed by files should be registered using CTFontManagerRegisterFontsForURL(_:_:_:).
Instead of allocating the `CGFontRef` during initialization, delay until first use.

This is helpful for scenarios that use `registerFont` to add the font to the Font Manager and then access the font by name. In this situation, bypassing the `uiFont` or `nsFont` helpers, the `fontRef` is never accessed.
Follow "The Create Rule" for Core Foundation functions per https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html#//apple_ref/doc/uid/20001148-103029

We keep the `CGFontRef` around that we created, but the interim data structures along the way can be released.
@darronschall darronschall force-pushed the ios-font-resource-register-font branch from 1229487 to fec1854 Compare February 22, 2024 21:02
@darronschall
Copy link
Contributor Author

I'm sorry, I originally wrote this PR based off of master. I've since rebased off of develop and cleaned up the commit history to make it easier to review.

@Alex009 Alex009 added this to the 0.24.0 milestone Mar 24, 2024
ExNDY added a commit that referenced this pull request Apr 17, 2024
@Alex009 Alex009 merged commit c6f96a3 into icerockdev:develop Apr 18, 2024
7 of 8 checks passed
@darronschall darronschall deleted the ios-font-resource-register-font branch April 18, 2024 14:03
@Alex009 Alex009 mentioned this pull request Jun 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants