resgen.swift/Sources/ResgenSwift/Images/Generator/XcassetsGenerator.swift
Thibaut Schmitt 279f13dbd5
Some checks failed
gitea-openium/resgen.swift/pipeline/head There was a failure building this commit
Add SwiftLint HARD rules
2025-04-30 17:05:02 +02:00

233 lines
9.0 KiB
Swift

//
// XcassetsGenerator.swift
//
//
// Created by Thibaut Schmitt on 24/01/2022.
//
import Foundation
import ToolCore
enum OutputImageExtension: String {
case png
case svg
}
class XcassetsGenerator {
// MARK: - Properties
let forceGeneration: Bool
// MARK: - Init
init(forceGeneration: Bool) {
self.forceGeneration = forceGeneration
}
// MARK: - Assets generation
func generateXcassets(inputPath: String, imagesToGenerate: [ParsedImage], xcassetsPath: String) {
let fileManager = FileManager()
let svgConverter = Images.getSvgConverterPath()
let allSubFiles = fileManager.getAllRegularFileIn(directory: inputPath)
var generatedAssetsPaths = [String]()
// Generate new assets
imagesToGenerate.forEach { parsedImage in // swiftlint:disable:this closure_body_length
// Get image path
let imageData: (path: String, ext: String) = {
for subfile in allSubFiles {
if subfile.hasSuffix("/" + parsedImage.name + ".svg") {
return (subfile, "svg")
}
if subfile.hasSuffix("/" + parsedImage.name + ".png") {
return (subfile, "png")
}
if subfile.hasSuffix("/" + parsedImage.name + ".jpg") {
return (subfile, "jpg")
}
if subfile.hasSuffix("/" + parsedImage.name + ".jepg") {
return (subfile, "jepg")
}
}
let error = ImagesError.unknownImageExtension(parsedImage.name)
print(error.description)
Images.exit(withError: error)
}()
// Create imageset folder name
let imagesetName = "\(parsedImage.name).imageset"
let imagesetPath = "\(xcassetsPath)/\(imagesetName)"
// Store managed images path
generatedAssetsPaths.append(imagesetName)
// Generate output images path
let output1x = "\(imagesetPath)/\(parsedImage.name).\(OutputImageExtension.png.rawValue)"
let output2x = "\(imagesetPath)/\(parsedImage.name)@2x.\(OutputImageExtension.png.rawValue)"
let output3x = "\(imagesetPath)/\(parsedImage.name)@3x.\(OutputImageExtension.png.rawValue)"
// Check if we need to convert image
var needToGenerateForSvg = false
if imageData.ext == "svg" && !parsedImage.imageExtensions.contains(.png) {
needToGenerateForSvg = true
}
guard self.shouldGenerate(inputImagePath: imageData.path, xcassetImagePath: output1x, needToGenerateForSvg: needToGenerateForSvg) else {
print("\(parsedImage.name) -> Not regenerating")
return
}
// Create imageset folder
if fileManager.fileExists(atPath: imagesetPath) == false {
do {
try fileManager.createDirectory(
atPath: imagesetPath,
withIntermediateDirectories: true
)
} catch {
let error = ImagesError.createAssetFolder(imagesetPath)
print(error.description)
Images.exit(withError: error)
}
} else {
do {
let documentsDirectory = try fileManager.contentsOfDirectory(atPath: imagesetPath)
for filePath in documentsDirectory {
try fileManager.removeItem(atPath: "\(imagesetPath)/\(filePath)")
}
} catch {
print("Error deleting previous assets")
}
}
let convertArguments = parsedImage.convertArguments
if imageData.ext == "svg" {
if parsedImage.imageExtensions.contains(.png) {
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -h 300 -o path/to/output.png
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -o path/to/output.png
// /usr/local/bin/rsvg-convert path/to/image.png -h 300 -o path/to/output.png
var command1x = ["\(svgConverter)", "\(imageData.path)"]
var command2x = ["\(svgConverter)", "\(imageData.path)"]
var command3x = ["\(svgConverter)", "\(imageData.path)"]
self.addConvertArgument(command: &command1x, convertArgument: convertArguments.x1)
self.addConvertArgument(command: &command2x, convertArgument: convertArguments.x2)
self.addConvertArgument(command: &command3x, convertArgument: convertArguments.x3)
command1x.append(contentsOf: ["-o", output1x])
command2x.append(contentsOf: ["-o", output2x])
command3x.append(contentsOf: ["-o", output3x])
Shell.shell(command1x)
Shell.shell(command2x)
Shell.shell(command3x)
} else {
let output = "\(imagesetPath)/\(parsedImage.name).\(OutputImageExtension.svg.rawValue)"
let tempURL = URL(fileURLWithPath: output)
do {
if FileManager.default.fileExists(atPath: tempURL.path) {
try FileManager.default.removeItem(atPath: tempURL.path)
}
try FileManager.default.copyItem(atPath: imageData.path, toPath: tempURL.path)
} catch {
print(error.localizedDescription)
}
}
} else {
// convert path/to/image.png -resize 200x300 path/to/output.png
// convert path/to/image.png -resize 200x path/to/output.png
// convert path/to/image.png -resize x300 path/to/output.png
Shell.shell(
[
"convert",
"\(imageData.path)",
"-resize",
"\(convertArguments.x1.width ?? "")x\(convertArguments.x1.height ?? "")",
output1x
]
)
Shell.shell(
[
"convert",
"\(imageData.path)",
"-resize",
"\(convertArguments.x2.width ?? "")x\(convertArguments.x2.height ?? "")",
output2x
]
)
Shell.shell(
[
"convert",
"\(imageData.path)",
"-resize",
"\(convertArguments.x3.width ?? "")x\(convertArguments.x3.height ?? "")",
output3x
]
)
}
// Write Content.json
guard let imagesetContentJson = parsedImage.generateContentJson(isVector: imageData.ext == "svg") else { return }
let contentJsonFilePath = "\(imagesetPath)/Contents.json"
let contentJsonFilePathURL = URL(fileURLWithPath: contentJsonFilePath)
try! imagesetContentJson.write( // swiftlint:disable:this force_try
to: contentJsonFilePathURL,
atomically: false,
encoding: .utf8
)
print("\(parsedImage.name) -> Generated")
}
// Success info
let generatedAssetsCount = generatedAssetsPaths.count
print("Images generated: \(generatedAssetsCount)")
// Delete old assets
let allImagesetName = Set(fileManager.getAllImageSetFolderIn(directory: xcassetsPath))
let imagesetToRemove = allImagesetName.subtracting(Set(generatedAssetsPaths))
imagesetToRemove.forEach {
print("Will remove: \($0)")
}
imagesetToRemove.forEach { itemToRemove in
try! fileManager.removeItem(atPath: "\(xcassetsPath)/\(itemToRemove)") // swiftlint:disable:this force_try
}
print("Removed \(imagesetToRemove.count) images")
}
// MARK: - Helpers: SVG command
private func addConvertArgument(command: inout [String], convertArgument: ConvertArgument) {
if let width = convertArgument.width, width.isEmpty == false {
command.append("-w")
command.append("\(width)")
}
if let height = convertArgument.height, height.isEmpty == false {
command.append("-h")
command.append("\(height)")
}
}
// MARK: - Helpers: bypass generation
private func shouldGenerate(inputImagePath: String, xcassetImagePath: String, needToGenerateForSvg: Bool) -> Bool {
if forceGeneration || needToGenerateForSvg {
return true
}
return GeneratorChecker.isFile(inputImagePath, moreRecenThan: xcassetImagePath)
}
}