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

feat(design-system/DT-6): allow generation of both UIKit and SwiftUI font files #3

Open
wants to merge 1 commit into
base: fueled-typography
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,18 +147,28 @@ ios:

# [optional] Parameters for exporting typography
typography:
# [optional] Absolute or relative path to swift file where to export UIKit fonts (UIFont extension).
# Choose what font system you want to use SwiftUI or UIKit
fontSystem: UIKit
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you allow both at the same time?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I was wondering if we could just remove the fontSystem param (See my question 1 above), wondering your thoughts on this ?

# Typography name style: camelCase or snake_case
nameStyle: camelCase

# Absolute or relative path to swift file where to export UIKit fonts (UIFont extension).
fontSwift: "./Source/UIComponents/UIFont+extension.swift"
# [optional] Absolute or relative path to swift file where to generate TextStyle extensions for each style (TextStyle extension).
textStyleSwift: "./Source/UIComponents/TextStyle+extension.swift"
# [optional] Absolute or relative path to swift file where to generate LabelStyle extensions for each style (LabelStyle extension).
labelStyleSwift: "./Source/UIComponents/LabelStyle+extension.swift"
# Should FigmaExport generate UILabel for each text style (font)? E.g. HeaderLabel, BodyLabel, CaptionLabel
generateLabels: true
# Relative or absolute path to directory where to place UILabel for each text style (font) (Required if generateLabels = true)
labelsDirectory: "./Source/UIComponents/"

# [optional] Absolute or relative path to swift file where to export SwiftUI fonts (Font extension).
swiftUIFontSwift: "./Source/View/Common/Font+extension.swift"
# [optional] Absolute or relative path to swift file where to generate TextStyle extensions for each style (TextStyle extension).
textStyleSwift: "./Source/UIComponents/TextStyle+extension.swift"
# Should FigmaExport generate TextStyles for each text style (font)? E.g. Header, Body, Caption
generateTextStyles: true
# Relative or absolute path to directory where to place TextStyles for each text style (font) (Required if generateTextStyles = true)
textStylesDirectory: "./Source/UIComponents/"
# Typography name style: camelCase or snake_case
nameStyle: camelCase

# [optional] Android export parameters
android:
Expand Down
8 changes: 4 additions & 4 deletions Examples/Example/Pods/FigmaExport/Release/figma-export.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,13 @@ If name of an image contains idiom at the end (e.g. ~ipad), it will be exported

#### Typography

When your execute `figma-export typography` command `figma-export` generates 3 files:
1. `TextStyle.swift` struct for generating TextStyles for SwiftUI with custom line spacing, kerning (letter spacing) and text case.
2. `TextStyle+extension.swift` extension including all the custom TextStyles from the Figma file.
3. `Font+extension` extension for Font that declares your custom fonts.
4. `UIFont+extension.swift` extension for UIFont that declares your custom fonts. Mainly used to get the default font lineHeight.
When your execute `figma-export typography` command `figma-export` generates 6 files:
1. `UIFont+extension.swift` extension for UIFont that declares your custom fonts. Mainly used to get the default font lineHeight.
2. `Font+extension` extension for Font that declares your custom fonts.
3. `TextStyle.swift` struct for generating TextStyles for SwiftUI with custom line spacing, kerning (letter spacing) and text case.
4. `TextStyle+extension.swift` extension including all the custom TextStyles from the Figma file.
5. `LabelStyle.swift` struct for generating attributes for NSAttributesString with custom lineHeight and tracking (letter spacing).
6. `Label.swift` file that contains base Label class and class for each text style. E.g. HeaderLabel, BodyLabel, Caption1Label. Specify these classes in xib files on in code.

Example of these files:
- [./Examples/Example/UIComponents/Source/Typography/TextStyle.swift](./Examples/Example/UIComponents/Source/Typography/TextStyle.swift)
Expand Down
14 changes: 8 additions & 6 deletions Sources/FigmaExport/Input/Params.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,16 @@ struct Params: Decodable {
}

struct Typography: Decodable {
let fontSystem: FontSystem?
let fontSystem: FontSystem?
let nameStyle: NameStyle
let fontSwift: URL?
let textStyleSwift: URL?
let labelStyleSwift: URL?
let labelStyleSwift: URL?
let generateLabels: Bool
let labelsDirectory: URL?
let swiftUIFontSwift: URL?
let generateStyles: Bool
let stylesDirectory: URL?
let nameStyle: NameStyle
let textStyleSwift: URL?
let generateTextStyles: Bool
let textStylesDirectory: URL?
}

let xcodeprojPath: String
Expand Down
17 changes: 12 additions & 5 deletions Sources/FigmaExport/Subcommands/ExportTypography.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,25 @@ extension FigmaExportCommand {
fontExtensionURL: iosParams.typography?.fontSwift,
swiftUIFontExtensionURL: iosParams.typography?.swiftUIFontSwift
)
let styleUrls = XcodeTypographyOutput.StyleURLs(
directory: iosParams.typography?.stylesDirectory,
extensionsURL: iosParams.typography?.textStyleSwift
let labelUrls = XcodeTypographyOutput.LabelURLs(
labelsDirectory: iosParams.typography?.labelsDirectory,
labelStyleExtensionsURL: iosParams.typography?.labelStyleSwift
)

let textstyleUrls = XcodeTypographyOutput.TextStyleURLs(
textStyleDirectory: iosParams.typography?.textStylesDirectory,
textStyleExtensionsURL: iosParams.typography?.textStyleSwift
)
let urls = XcodeTypographyOutput.URLs(
fonts: fontUrls,
styles: styleUrls
labels: labelUrls,
textStyles: textstyleUrls
)
return XcodeTypographyOutput(
fontSystem: iosParams.typography?.fontSystem,
urls: urls,
generateStyles: iosParams.typography?.generateStyles,
generateLabels: iosParams.typography?.generateLabels,
generateTextStyles: iosParams.typography?.generateTextStyles,
addObjcAttribute: iosParams.addObjcAttribute,
templatesPath: iosParams.templatesPath
)
Expand Down
49 changes: 34 additions & 15 deletions Sources/XcodeExport/Model/XcodeTypographyOutput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ public enum FontSystem: String, Decodable {
public struct XcodeTypographyOutput {
let fontSystem: FontSystem
let urls: URLs
let generateStyles: Bool
let generateLabels: Bool
let generateTextStyles: Bool
let addObjcAttribute: Bool
let templatesPath: URL?

Expand All @@ -50,43 +51,61 @@ public struct XcodeTypographyOutput {
self.fontExtensionURL = fontExtensionURL
}
}
public struct StyleURLs {
let directory: URL?
let extensionsURL: URL?

public struct LabelURLs {
let labelsDirectory: URL?
let labelStyleExtensionsURL: URL?

public init(
directory: URL? = nil,
extensionsURL: URL? = nil
labelsDirectory: URL? = nil,
labelStyleExtensionsURL: URL? = nil
) {
self.directory = directory
self.extensionsURL = extensionsURL
self.labelsDirectory = labelsDirectory
self.labelStyleExtensionsURL = labelStyleExtensionsURL
}
}


public struct TextStyleURLs {
let textStyleDirectory: URL?
let textStyleExtensionsURL: URL?

public init(
textStyleDirectory: URL? = nil,
textStyleExtensionsURL: URL? = nil
) {
self.textStyleDirectory = textStyleDirectory
self.textStyleExtensionsURL = textStyleExtensionsURL
}
}

public struct URLs {
public let fonts: FontURLs
public let styles: StyleURLs
public let labels: LabelURLs
public let textStyles: TextStyleURLs

public init(
fonts: FontURLs,
styles: StyleURLs
labels: LabelURLs,
textStyles: TextStyleURLs
) {
self.fonts = fonts
self.styles = styles
self.labels = labels
self.textStyles = textStyles
}
}

public init(
fontSystem: FontSystem? = .swiftUI,
urls: URLs,
generateStyles: Bool? = false,
generateLabels: Bool? = false,
generateTextStyles: Bool? = false,
addObjcAttribute: Bool? = false,
templatesPath: URL? = nil
) {
self.fontSystem = fontSystem ?? .swiftUI
self.urls = urls
self.generateStyles = generateStyles ?? false
self.generateLabels = generateLabels ?? false
self.generateTextStyles = generateTextStyles ?? false
self.addObjcAttribute = addObjcAttribute ?? false
self.templatesPath = templatesPath
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ public extension LabelStyle {
{% for style in styles %}
static func {{ style.varName }}() -> LabelStyle {
LabelStyle(
font: UIFont.{{ style.varName }}(){% if style.supportsDynamicType %},
font: UIFont.{{ style.varName }}{% if style.supportsDynamicType %},
fontMetrics: UIFontMetrics(forTextStyle: .{{ style.type }}){% endif %}{% if style.lineHeight != 0 %},
lineHeight: {{ style.lineHeight }}{% endif %}{% if style.tracking != 0 %},
tracking: {{ style.tracking }}{% endif %}{% if style.textCase != "original" %},
textCase: .{{ style.textCase }}{% endif %}
)
}
{% endfor %}
}
}
75 changes: 63 additions & 12 deletions Sources/XcodeExport/XcodeTypographyExporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final public class XcodeTypographyExporter: XcodeExporterBase {
public func export(textStyles: [TextStyle]) throws -> [FileContents] {
var files: [FileContents] = []

// UIKit & SwiftUI UIFont extension
// UIKit UIFont extension
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we not need UIFont anymore when only using SwiftUI? I did mention it here for clarity since we needed it to calculate the lineSpacing.

Copy link
Author

@ada10086 ada10086 Dec 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see your intention here, I thought we simply wanted to note what extension file we are creating. I will add additional comments here to clarify!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also maybe the parameter fontSwift, i.e. UIFont extension, should be non-optional, since it'll be required for not only UIKit but also SwiftUI?

guard let url = output.urls.fonts.fontExtensionURL else {
throw Error.missingUIFont
}
Expand All @@ -40,15 +40,39 @@ final public class XcodeTypographyExporter: XcodeExporterBase {
files.append(try makeFontExtension(textStyles: textStyles, swiftUIFontExtensionURL: url))
}

// Styles
if output.generateStyles, let stylesDirectory = output.urls.styles.directory {
files.append(try makeStyle(fontSystem: output.fontSystem, directory: stylesDirectory))
// UIKit Labels
if output.generateLabels, let labelsDirectory = output.urls.labels.labelsDirectory {
// Label.swift
files.append(try makeLabel(
textStyles: textStyles,
labelsDirectory: labelsDirectory,
separateStyles: output.urls.labels.labelStyleExtensionsURL != nil
))

if let url = output.urls.styles.extensionsURL {
// LabelStyle.swift
files.append(try makeLabelStyle(labelsDirectory: labelsDirectory))

// LabelStyle extensions
if let url = output.urls.labels.labelStyleExtensionsURL {
files.append(try makeStyleExtensionFileContents(
fontSystem: .uiKit,
textStyles: textStyles,
styleExtensionURL: url
))
}
}

// TextStyles
if output.generateTextStyles, let textStylesDirectory = output.urls.textStyles.textStyleDirectory {
// TextStyle.swift
files.append(try makeTextStyle(fontSystem: output.fontSystem, directory: textStylesDirectory))

// TextStyle extensions
if let url = output.urls.textStyles.textStyleExtensionsURL {
files.append(try makeStyleExtensionFileContents(
fontSystem: output.fontSystem,
fontSystem: .swiftUI,
textStyles: textStyles,
textStyleExtensionURL: url
styleExtensionURL: url
))
}
}
Expand All @@ -73,7 +97,34 @@ final public class XcodeTypographyExporter: XcodeExporterBase {
])
return try makeFileContents(for: contents, url: fontExtensionURL)
}


private func makeLabel(textStyles: [TextStyle], labelsDirectory: URL, separateStyles: Bool) throws -> FileContents {
let dict = textStyles.map { style -> [String: Any] in
let type: String = style.fontStyle?.textStyleName ?? ""
return [
"className": style.name.first!.uppercased() + style.name.dropFirst(),
"varName": style.name,
"size": style.fontSize,
"supportsDynamicType": style.fontStyle != nil,
"type": type,
"tracking": style.letterSpacing.floatingPointFixed,
"lineHeight": style.lineHeight ?? 0,
"textCase": style.textCase.rawValue
]}
let env = makeEnvironment(templatesPath: output.templatesPath)
let contents = try env.renderTemplate(name: "Label.swift.stencil", context: [
"styles": dict,
"separateStyles": separateStyles
])
return try makeFileContents(for: contents, directory: labelsDirectory, file: URL(string: "Label.swift")!)
}

private func makeLabelStyle(labelsDirectory: URL) throws -> FileContents {
let env = makeEnvironment(templatesPath: output.templatesPath)
let labelStyleSwiftContents = try env.renderTemplate(name: "LabelStyle.swift.stencil")
return try makeFileContents(for: labelStyleSwiftContents, directory: labelsDirectory, file: URL(string: "LabelStyle.swift")!)
}

private func makeFontExtension(textStyles: [TextStyle], swiftUIFontExtensionURL: URL) throws -> FileContents {
let textStyles: [[String: Any]] = textStyles.map {
[
Expand All @@ -91,7 +142,7 @@ final public class XcodeTypographyExporter: XcodeExporterBase {
return try makeFileContents(for: contents, url: swiftUIFontExtensionURL)
}

private func makeStyleExtensionFileContents(fontSystem: FontSystem, textStyles: [TextStyle], textStyleExtensionURL: URL) throws -> FileContents {
private func makeStyleExtensionFileContents(fontSystem: FontSystem, textStyles: [TextStyle], styleExtensionURL: URL) throws -> FileContents {
let dict = textStyles.map { style -> [String: Any] in
let type: String = style.fontStyle?.textStyleName ?? ""
let textCase: String = {
Expand Down Expand Up @@ -123,11 +174,11 @@ final public class XcodeTypographyExporter: XcodeExporterBase {
let env = makeEnvironment(templatesPath: output.templatesPath)
let contents = try env.renderTemplate(name: fontSystem.styleExtensionStencilFile, context: ["styles": dict])

let textStylesSwiftExtension = try makeFileContents(for: contents, url: textStyleExtensionURL)
return textStylesSwiftExtension
let stylesSwiftExtension = try makeFileContents(for: contents, url: styleExtensionURL)
return stylesSwiftExtension
}

private func makeStyle(fontSystem: FontSystem, directory: URL) throws -> FileContents {
private func makeTextStyle(fontSystem: FontSystem, directory: URL) throws -> FileContents {
let env = makeEnvironment(templatesPath: output.templatesPath)
let textStyleSwiftContents = try env.renderTemplate(name: fontSystem.styleStencilFile)
return try makeFileContents(for: textStyleSwiftContents, directory: directory, file: URL(string: fontSystem.styleFile)!)
Expand Down
Loading