Skip to content
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
58 changes: 55 additions & 3 deletions Sources/FigmaGen/Commands/ImagesCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,22 @@ final class ImagesCommand: AsyncExecutableCommand, GenerationConfigurableCommand
"""
)

let coloredResources = Key<String>(
"--coloredResources",
description: """
Optional path to folder to store generated ImageVector Kotlin files for
multi-color icons (components with "colored" in their Figma node name).
Requires --postProcessor to be set — the downloaded SVG is saved here and
converted to a .kt file by --postProcessor (invoked with --outputFormat kt).
"""
)

let postProcessor = Key<String>(
"--postProcessor",
"-p",
description: """
The path to the bash script to make operations with generated images.
Only executes for generated images from --resources folder.
Executes for generated images from --resources and --coloredResources folders.
"""
)

Expand Down Expand Up @@ -183,6 +193,30 @@ final class ImagesCommand: AsyncExecutableCommand, GenerationConfigurableCommand
"""
)

let sfSymbolKey = Key<String>(
"--sfSymbolKey",
description: """
Colored icons flag name from Figma.
By default, assets will be generated without processing colored info.
"""
)

let symbolRenderAs = Key<String>(
"--symbolRenderAs",
description: """
Set rendering mode in Xcode assets for SF Symbols, can be 'template`, `multicolor` or `hierarchical'.
By default, Xcode assets will be generated with automatic rendering mode.
"""
)

let sfSymbolTemplate = Key<String>(
"--sfSymbolTemplate",
description: """
Path to the SF Symbol template file.
If no template is passed SF Symbols won't be created.
"""
)

// MARK: - Initializers

init(generator: ImagesGenerator) {
Expand Down Expand Up @@ -225,7 +259,21 @@ final class ImagesCommand: AsyncExecutableCommand, GenerationConfigurableCommand

case let rawRenderingMode?:
guard let mode = ImageRenderingMode(rawValue: rawRenderingMode) else {
fail(message: "Failed to generated images: Invalid rendering mode (\(rawRenderingMode)")
fail(message: "Failed to generate images: Invalid rendering mode (\(rawRenderingMode))")
}

return mode
}
}

private func resolveSymbolRenderAs() -> SymbolRenderingMode? {
switch symbolRenderAs.value {
case nil:
return nil

case let rawSymbolRenderingMode?:
guard let mode = SymbolRenderingMode(rawValue: rawSymbolRenderingMode) else {
fail(message: "Failed to generate symbols: Invalid rendering mode (\(rawSymbolRenderingMode)")
}

return mode
Expand All @@ -251,6 +299,7 @@ final class ImagesCommand: AsyncExecutableCommand, GenerationConfigurableCommand
generatation: generationConfiguration,
assets: assets.value,
resources: resources.value,
coloredResources: coloredResources.value,
postProcessor: postProcessor.value,
format: resolveImageFormat(),
scales: resolveImageScales(),
Expand All @@ -260,7 +309,10 @@ final class ImagesCommand: AsyncExecutableCommand, GenerationConfigurableCommand
renderAs: resolveRenderingMode(),
groupByFrame: groupByFrame.value,
groupByComponentSet: groupByComponentSet.value,
namingStyle: resolveNamingStyle()
namingStyle: resolveNamingStyle(),
sfSymbolKey: sfSymbolKey.value,
symbolRenderAs: resolveSymbolRenderAs(),
sfSymbolTemplate: sfSymbolTemplate.value
)
}

Expand Down
8 changes: 7 additions & 1 deletion Sources/FigmaGen/Dependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ enum Dependencies {
// MARK: - Type Properties

static let dataProvider: DataProvider = DefaultDataProvider()
static let svgParser: SVGParser = DefaultSVGParser()
static let sfSymbolProvider: SFSymbolProvider = DefaultSFSymbolProvider(
svgParser: svgParser,
templateRenderer: templateRenderer
)

static let gitHubHTTPService: GitHubHTTPService = HTTPService()
static let gitHubAPIProvider: RemoteRepoProvider = GitHubAPIProvider(httpService: gitHubHTTPService)
Expand Down Expand Up @@ -37,7 +42,8 @@ enum Dependencies {

static let imageAssetsProvider: ImageAssetsProvider = DefaultImageAssetsProvider(
assetsProvider: assetsProvider,
dataProvider: dataProvider
dataProvider: dataProvider,
sfSymbolProvider: sfSymbolProvider
)

static let imageResourcesProvider: ImageResourcesProvider = DefaultImageResourcesProvider(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,18 @@ extension ImagesConfiguration {
scales: scales,
assets: assets,
resources: resources,
coloredResources: coloredResources,
postProcessor: postProcessor,
onlyExportables: onlyExportables,
useAbsoluteBounds: useAbsoluteBounds,
preserveVectorData: preserveVectorData,
renderAs: renderAs,
groupByFrame: groupByFrame,
groupByComponentSet: groupByComponentSet,
namingStyle: namingStyle
namingStyle: namingStyle,
sfSymbolKey: sfSymbolKey,
symbolRenderAs: symbolRenderAs,
sfSymbolTemplate: sfSymbolTemplate
)
}
}
28 changes: 26 additions & 2 deletions Sources/FigmaGen/Models/Configuration/ImagesConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ struct ImagesConfiguration: Decodable {
private enum CodingKeys: String, CodingKey {
case assets
case resources
case coloredResources
case postProcessor
case format
case scales
Expand All @@ -17,13 +18,17 @@ struct ImagesConfiguration: Decodable {
case groupByFrame
case groupByComponentSet
case namingStyle
case sfSymbolKey
case symbolRenderAs
case sfSymbolTemplate
}

// MARK: - Instance Properties

let generatation: GenerationConfiguration
let assets: String?
let resources: String?
let coloredResources: String?
let postProcessor: String?
let format: ImageFormat
let scales: [ImageScale]
Expand All @@ -34,13 +39,17 @@ struct ImagesConfiguration: Decodable {
let groupByFrame: Bool
let groupByComponentSet: Bool
let namingStyle: ImageNamingStyle
let sfSymbolKey: String?
let symbolRenderAs: SymbolRenderingMode?
let sfSymbolTemplate: String?

// MARK: - Initializers

init(
generatation: GenerationConfiguration,
assets: String?,
resources: String?,
coloredResources: String?,
postProcessor: String?,
format: ImageFormat,
scales: [ImageScale],
Expand All @@ -50,11 +59,15 @@ struct ImagesConfiguration: Decodable {
renderAs: ImageRenderingMode?,
groupByFrame: Bool,
groupByComponentSet: Bool,
namingStyle: ImageNamingStyle
namingStyle: ImageNamingStyle,
sfSymbolKey: String?,
symbolRenderAs: SymbolRenderingMode?,
sfSymbolTemplate: String?
) {
self.generatation = generatation
self.assets = assets
self.resources = resources
self.coloredResources = coloredResources
self.postProcessor = postProcessor
self.format = format
self.scales = scales
Expand All @@ -65,13 +78,17 @@ struct ImagesConfiguration: Decodable {
self.groupByFrame = groupByFrame
self.groupByComponentSet = groupByComponentSet
self.namingStyle = namingStyle
self.sfSymbolKey = sfSymbolKey
self.symbolRenderAs = symbolRenderAs
self.sfSymbolTemplate = sfSymbolTemplate
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

assets = try container.decodeIfPresent(forKey: .assets)
resources = try container.decodeIfPresent(forKey: .resources)
coloredResources = try container.decodeIfPresent(forKey: .coloredResources)

postProcessor = try container.decodeIfPresent(forKey: .postProcessor)
format = try container.decodeIfPresent(forKey: .format) ?? .pdf
Expand All @@ -83,6 +100,9 @@ struct ImagesConfiguration: Decodable {
groupByFrame = try container.decodeIfPresent(forKey: .groupByFrame) ?? false
groupByComponentSet = try container.decodeIfPresent(forKey: .groupByComponentSet) ?? false
namingStyle = try container.decodeIfPresent(forKey: .namingStyle) ?? .camelCase
sfSymbolKey = try container.decodeIfPresent(forKey: .sfSymbolKey)
symbolRenderAs = try container.decodeIfPresent(forKey: .symbolRenderAs)
sfSymbolTemplate = try container.decodeIfPresent(forKey: .sfSymbolTemplate)

generatation = try GenerationConfiguration(from: decoder)
}
Expand All @@ -94,6 +114,7 @@ struct ImagesConfiguration: Decodable {
generatation: generatation.resolve(base: base),
assets: assets,
resources: resources,
coloredResources: coloredResources,
postProcessor: postProcessor,
format: format,
scales: scales,
Expand All @@ -103,7 +124,10 @@ struct ImagesConfiguration: Decodable {
renderAs: renderAs,
groupByFrame: groupByFrame,
groupByComponentSet: groupByComponentSet,
namingStyle: namingStyle
namingStyle: namingStyle,
sfSymbolKey: sfSymbolKey,
symbolRenderAs: symbolRenderAs,
sfSymbolTemplate: sfSymbolTemplate
)
}
}
2 changes: 2 additions & 0 deletions Sources/FigmaGen/Models/Images/ImageAsset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@ struct ImageAsset: Encodable, Hashable {
let filePaths: [ImageScale: String]
let preserveVectorData: Bool
let renderAs: ImageRenderingMode?
let isSymbol: Bool
let symbolRenderAs: SymbolRenderingMode?
}
8 changes: 8 additions & 0 deletions Sources/FigmaGen/Models/Images/SymbolRenderingMode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation

enum SymbolRenderingMode: String, Codable {

case template
case multicolor
case hierarchical
}
4 changes: 4 additions & 0 deletions Sources/FigmaGen/Models/Parameters/ImagesParameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ struct ImagesParameters {
let scales: [ImageScale]
let assets: String?
let resources: String?
let coloredResources: String?
let postProcessor: String?
let onlyExportables: Bool
let useAbsoluteBounds: Bool
Expand All @@ -16,4 +17,7 @@ struct ImagesParameters {
let groupByFrame: Bool
let groupByComponentSet: Bool
let namingStyle: ImageNamingStyle
let sfSymbolKey: String?
let symbolRenderAs: SymbolRenderingMode?
let sfSymbolTemplate: String?
}
8 changes: 8 additions & 0 deletions Sources/FigmaGen/Models/SFSymbol/SFSymbolLayer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation

struct SFSymbolLayer {

let index: Int
let role: SFSymbolRole
let paths: [SFSymbolPathData]
}
8 changes: 8 additions & 0 deletions Sources/FigmaGen/Models/SFSymbol/SFSymbolPathData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation

struct SFSymbolPathData {

// Данные пути, уже переведённые в координатное пространство SF Symbols.
let data: String
Comment thread
Shedward marked this conversation as resolved.
let fillRule: String?
}
10 changes: 10 additions & 0 deletions Sources/FigmaGen/Models/SFSymbol/SFSymbolRole.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation

/// Роль пути в палитре слоёв. Определяется по id пути, а если его нет - по заливке:
/// чёрная заливка из Figma даёт primary, любая другая - secondary.
enum SFSymbolRole: String {

case primary
case secondary
case tertiary
}
10 changes: 10 additions & 0 deletions Sources/FigmaGen/Models/SVG/SVGCanvas.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Foundation

/// Размер корневого элемента `<svg>`, который Figma выгружает равным боксу компонента.
/// Геометрия SF Symbols строится именно от этого бокса, а не от границ видимых путей,
/// чтобы сохранить заложенные в Figma отступы.
struct SVGCanvas: Equatable {

let width: Double
let height: Double
}
7 changes: 7 additions & 0 deletions Sources/FigmaGen/Models/SVG/SVGColor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

enum SVGColor: Equatable {

case black
case other(String)
}
8 changes: 8 additions & 0 deletions Sources/FigmaGen/Models/SVG/SVGGroupContext.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Foundation

struct SVGGroupContext {

let id: String?
let fill: SVGColor?
let transform: String?
}
11 changes: 11 additions & 0 deletions Sources/FigmaGen/Models/SVG/SVGImageToken.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation

// Всё, что нужно SVG-шаблону, чтобы разложить рисунок из Figma в координатах SF Symbols.
struct SVGImageToken {

let name: String
let opticalSize: Int

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

А это Int почему? Не должно быть тоже Double или Width/Height?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

можно заменить, да, скорее всего Int тут, потому что изначально размеры 16х16 и 24х24 у икононок

let designWidth: Double

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Там выше была моделька размера, а тут раздельные проперти.
Мб заюзать CGSize или что-то такое из Foundation или сделать абстрактный Size чтобы натащить CoreGraphics

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

погляжу

let designHeight: Double
let layers: [SFSymbolLayer]
}
22 changes: 22 additions & 0 deletions Sources/FigmaGen/Models/SVG/SVGNumber.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Foundation

enum SVGNumber {

/// Форматирует число для атрибутов SVG: до шести знаков после запятой, без хвостовых нулей
/// и без отрицательного нуля.
static func string(from value: Double) -> String {
var text = String(format: "%.6f", value)

if text.contains(".") {
while text.hasSuffix("0") {
text.removeLast()
}

if text.hasSuffix(".") {
text.removeLast()
}
}

return text == "-0" ? "0" : text
}
}
Loading