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
Fonts: add support for extracting code points #638
base: develop
Are you sure you want to change the base?
Conversation
|
Thanks for the PR (and sorry for the late reply) Could you update the contexts files with this new code so we can see how the updated YAML contexts look like, and also so that the unit tests are updated accordingly? It's just a matter of using Cmd-U on the "SwiftGenKit - Generate Contexts" scheme in Xcode I have two main concerns with this change:
So in the end I'm not sure if this makes sense to generate those lists. I understand the use case, but that won't work for regular fonts though. Maybe we could make use of parser options so that the user can specify when they want those list of glyph names generated for which fonts, and default to not generate them? That way users can opt-in for this behaviour only for their icon-fonts and avoid the list of glyphs for regular fonts? |
Ideally we'd have a mechanism for lazily calculating certain context properties (+ cache, so |
…ful for icon fonts
20e76d5
to
9b62847
Compare
Hey 👋 I'm Eve, the friendly bot watching over SwiftGen 🤖 Thanks a lot for your contribution! Seems like everything is in order 👍 You did a good job here! 🤝 Generated by 🚫 Danger |
@AliSoftware I've gone ahead and added a option for extracting code points. Not sure what I need to do in the tests to get it actually testing though. |
Never mind, all figured out! Think this is good to go? |
76afd6c
to
ef703ea
Compare
Now that I'm actually integrating this, I've realized that this can't yet be merged. For one thing, the option values need to be String so it works with CLI. But now I'm facing this issue and unsure how to get past it. Just pushed the font that has this code point at 7132bca. Suggestions welcome! let point: UInt32 = 0xF0000
let scal = Unicode.Scalar(point)!
let char = UniChar(scal.value) // Fatal error: Not enough bits to represent the passed value |
@robbiet480 Not sure what you're trying to do there, but that code point |
Yeah its a codepoint that came out of the test icon font I'm using, MaterialDesignIcons. Guess they have so many icons the code points go out of UInt16 space? |
You can write a unicode character in a Swift string like this: let dollarSign = "\u{24}" // $, Unicode scalar U+0024
let blackHeart = "\u{2665}" // ♥, Unicode scalar U+2665
let sparklingHeart = "\u{1F496}" // 💖, Unicode scalar U+1F496 |
Yeah what you probably want is to convert your https://developer.apple.com/documentation/swift/character/2906502-init in Unicode, code points are not limited to 2^16 so it's not logical to constrain them to |
@AliSoftware That's hopefully the pointer I needed, thanks!!! |
Okay, think it's now all good. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quick review (from phone so might have missed things?) of those latest changes, looking good!
Left a couple of suggestions, and one or two interrogations about things we might want to just conform to be sure, but otherwise LGTM 👍
@@ -53,6 +53,9 @@ | |||
[#609](https://github.com/SwiftGen/SwiftGen/issue/609) | |||
[#593](https://github.com/SwiftGen/SwiftGen/pull/593) | |||
[#610](https://github.com/SwiftGen/SwiftGen/pull/610) | |||
* Fonts: extract all codepoints of a font and make available to templates. Useful for icon fonts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth adjusting that CHANGELOG entry to reflect that this is opt-in
Would also rather mention that it extract "glyph names" rather than talking about codepoints, as glyph names are what are more relevant to users here
return descRefs.compactMap { desc -> Fonts.Font? in | ||
let font = CTFontCreateWithFontDescriptorAndOptions(desc, 0.0, nil, [.preventAutoActivation]) | ||
let postScriptName = CTFontCopyPostScriptName(font) as String | ||
guard let familyName = CTFontCopyAttribute(font, kCTFontFamilyNameAttribute) as? String, | ||
let style = CTFontCopyAttribute(font, kCTFontStyleNameAttribute) as? String else { return nil } | ||
|
||
let relPath = parent.flatMap { file.relative(to: $0) } ?? file | ||
|
||
let characterSet = CTFontCopyCharacterSet(font) as CharacterSet | ||
let cgFont = CTFontCopyGraphicsFont(font, nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not put these 2 inside the if extractCodePoints
?
That might not take that long to run (though maybe that does depending on the font file size?) but better avoid needless calls when the flag is off 🙃
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, not completely sure because maybe the bridging of that API to Swift takes care of that, but worth double-checking: since those two calls are …Copy…
don't they need a balancing CTRelease
(or CFRelease
?) call or similar to avoid leaks?
for unicode in characterSet.codePoints { | ||
guard let unicodeScalar = UnicodeScalar(unicode) else { continue } | ||
|
||
let utf16codepoints = Array(Character(unicodeScalar).utf16) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wondering if there isn't a more efficient way to go from codepoint to utf16 than CodePoint>UnicodeScalar>Character>.utf16 🤔
Will have to look at the various String/Character/Unicode APIs to double check, as this is often tricky stuff with edge cases so better be sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually there is, and it's more correct: Array(unicodeScalar.utf16)
|
||
// Gets the Glyph | ||
guard CTFontGetGlyphsForCharacters(font, utf16codepoints, &glyphs, utf16codepoints.count) == true else { | ||
print("Failed to get glyph for character", unicode, utf16codepoints, unicodeScalar) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a logMessage
function to generate warnings and errors during parsing, you should use that instead of print
here
} | ||
|
||
if glyphs.isEmpty { | ||
print("Glyph set was empty!") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto re:logMessage
static let codePoints = ParserOption( | ||
key: "codePoints", | ||
defaultValue: "false", | ||
help: "If enabled, will extract code points from the font. Only useful for icon fonts." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
help: "If enabled, will extract code points from the font. Only useful for icon fonts." | |
help: "If enabled, will extract the glyph names from the font. Only useful for icon fonts." |
@@ -9,6 +9,14 @@ import Foundation | |||
import PathKit | |||
|
|||
public enum Fonts { | |||
public enum Option { | |||
static let codePoints = ParserOption( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe name the option glyphNames
instead of codePoints
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, this parser option needs to be documented in Documentation/Parsers/fonts.md
in the "Supported Options" section (you might need to merge develop into your branch first, as we renamed that documentation folder in a recent PR that was open and merged after you opened yours)
@@ -32,7 +41,13 @@ public enum Fonts { | |||
return | |||
} | |||
|
|||
let fonts = CTFont.parse(file: path, relativeTo: parent) | |||
let getCodePoints: Bool = self.options[Option.codePoints] == "true" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to check how we do string-to-boolean for other config flags. I don't think we have many Boolean Parser Options that we can use as reference but we do have some template parameters that are Boolean (e.g. forceNameSpaces
for the Assets templates); if there is similar cases, better ensure we do the conversion the same way (== "true"
vs != "false"
, case-insensitivity, etc)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might actually be a nice addition to add to ParserOptionValues
by overloading the subscript si that if the return type is inverted to Bool
, that ParserOptionValues
is the one doing the string-to-Bool conversion. That would allow us to ensure that all future Boolean parser options are treated the same way.
"style": font.style | ||
"style": font.style, | ||
// swiftlint:disable:next force_unwrapping | ||
"icons": font.icons.map { ["name": $0.key, "unicode": $0.value] }.sorted { $0["name"]! < $1["name"]! } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if the force-unwrap is technically never supposed to be nil in that specific case, you could avoid the SwiftLint violation using
($0["name"] ?? "") < ($1["name"] ?? "")
"style": font.style | ||
"style": font.style, | ||
// swiftlint:disable:next force_unwrapping | ||
"icons": font.icons.map { ["name": $0.key, "unicode": $0.value] }.sorted { $0["name"]! < $1["name"]! } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll need to document that additional key in the documentation for contexts (in Documentation/StencilSwiftKit Contexts/fonts.md
)
Also, you mention in the PR description that you have a template that includes those glyph names but kept it as a gist and didn't include it in the PR, but I think it might be worth it to make some use of it. I think we should either:
So I'd vote for 1 or 2, using solution 2 only if it's a small and localized change to the existing templates that won't make them too convoluted or to hard to maintain. Otherwise or if the code for icon fonts makes more sense to be very specific and different from normal fonts, go with 1 and have a dedicated template for them, which could actually make more sense in practice and would be easier to do given you already have a gist for it. (Note that we could keep that addition of your template for icon fonts as a separate PR if you prefer to keep things separate and easier to review) |
Once SwiftGen/SwiftGen#638 lands, we can update Iconic to use a newer version and everything becomes better. Fixes #663.
Once SwiftGen/SwiftGen#638 lands, we can update Iconic to use a newer version and everything becomes better. Fixes #663.
So… I tested the code with I've actually managed to get the unicode code point of one of the SFSymbols (drag & drop one of the icon from the SF Symbols macOS app into a new file in a text editor, save in utf16, use |
This adds support to the SwiftGen fonts context for extracting code points of a font to make them available to the template. This is useful for generating templates for icon fonts. I am doing this work to modernize Iconic which until this point used a old, modified, Swift 2.3 only version of SwiftGen which meant that recent users have been unable to update their icon fonts. I have adapted the Iconic SwiftGen template but have not submitted it as part of this PR because I was unsure if it was out of scope. If you don't think it will be I will be happy to submit it. I quickly put it up as a Gist if you want to take a look.
Related PR (required to run the linked template, not required for this PR to be merged): SwiftGen/StencilSwiftKit#121
develop
branch (gitflow)develop
as the "base" branch for this Pull Request I'm about to createCHANGELOG.md
file to explain my changes and credit myself(or added
#trivial
to the PR title if I think it doesn't warrant a mention in the CHANGELOG)