// // XcassetsGenerator.swift // // // Created by Thibaut Schmitt on 24/01/2022. // import Foundation import ToolCore enum OutputImageExtension: String { case png case svg } class XcassetsGenerator { 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 // 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 guard self.shouldGenerate(inputImagePath: imageData.path, xcassetImagePath: output1x) 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) } } let convertArguments = parsedImage.convertArguments if parsedImage.imageExtensions.contains(.vector) { 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 if imageData.ext == "svg" { // /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 { // 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.contentJson else { return } let contentJsonFilePath = "\(imagesetPath)/Contents.json" let contentJsonFilePathURL = URL(fileURLWithPath: contentJsonFilePath) try! imagesetContentJson.write(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)") } 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) -> Bool { if forceGeneration { return true } return GeneratorChecker.isFile(inputImagePath, moreRecenThan: xcassetImagePath) } }