DEVTOOLS-186 Exporter les images de resgen en svg #12

Merged
t.schmitt merged 5 commits from feature/DEVTOOLS-186-Resgen-Svg into master 2024-07-17 15:18:13 +02:00
8 changed files with 252 additions and 72 deletions

2
Jenkinsfile vendored
View File

@ -1,6 +1,6 @@
library "openiumpipeline" library "openiumpipeline"
env.DEVELOPER_DIR="/Applications/Xcode-15.3.0.app/Contents/Developer" env.DEVELOPER_DIR="/Applications/Xcode-15.4.0.app/Contents/Developer"
//env.SIMULATOR_DEVICE_TYPES="iPad--7th-generation-" //env.SIMULATOR_DEVICE_TYPES="iPad--7th-generation-"
env.IS_PACKAGE_SWIFT=1 env.IS_PACKAGE_SWIFT=1
env.TARGETS_MACOS=1 env.TARGETS_MACOS=1

View File

@ -202,7 +202,7 @@ You need to put `_` + `NAME OF THE PARAMETER` + `_` in the target and which targ
## Images ## Images
Images generator will generate images assets along with extensions to access those images easily. Images generator will generate images assets along with extensions to access those images easily.
```sh ```sh
swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \ swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \
@ -225,6 +225,7 @@ swift run -c release ResgenSwift images $FORCE_FLAG "./Images/images.txt" \
6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppImage+GreatApp.swift`) 6. `--extension-suffix` *(optional)* : additional text which is added to filename (ex: `AppImage+GreatApp.swift`)
7. `--static-members` *(optional)*: generate static properties or not 7. `--static-members` *(optional)*: generate static properties or not
> ⚠️ Svg images will be copied in the assets and rendered as "Original", however if those images are not rendered correctly you can force the png generation by adding the key word "png" like this: id arrow_back 15 ? png
## All at once ## All at once

View File

@ -8,10 +8,13 @@
import Foundation import Foundation
import ToolCore import ToolCore
enum OutputImageExtension: String {
case png
case svg
}
class XcassetsGenerator { class XcassetsGenerator {
static let outputImageExtension = "png"
let forceGeneration: Bool let forceGeneration: Bool
// MARK: - Init // MARK: - Init
@ -60,13 +63,20 @@ class XcassetsGenerator {
generatedAssetsPaths.append(imagesetName) generatedAssetsPaths.append(imagesetName)
// Generate output images path // Generate output images path
let output1x = "\(imagesetPath)/\(parsedImage.name).\(XcassetsGenerator.outputImageExtension)" let output1x = "\(imagesetPath)/\(parsedImage.name).\(OutputImageExtension.png.rawValue)"
let output2x = "\(imagesetPath)/\(parsedImage.name)@2x.\(XcassetsGenerator.outputImageExtension)" let output2x = "\(imagesetPath)/\(parsedImage.name)@2x.\(OutputImageExtension.png.rawValue)"
let output3x = "\(imagesetPath)/\(parsedImage.name)@3x.\(XcassetsGenerator.outputImageExtension)" let output3x = "\(imagesetPath)/\(parsedImage.name)@3x.\(OutputImageExtension.png.rawValue)"
// Check if we need to convert image // Check if we need to convert image
guard self.shouldGenerate(inputImagePath: imageData.path, xcassetImagePath: output1x) else {
//print("\(parsedImage.name) -> Not regenerating") 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 return
} }
@ -80,29 +90,55 @@ class XcassetsGenerator {
print(error.description) print(error.description)
Images.exit(withError: error) 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")
}
} }
// Convert image
let convertArguments = parsedImage.convertArguments let convertArguments = parsedImage.convertArguments
if imageData.ext == "svg" { if imageData.ext == "svg" {
// /usr/local/bin/rsvg-convert path/to/image.png -w 200 -h 300 -o path/to/output.png if parsedImage.imageExtensions.contains(.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 // /usr/local/bin/rsvg-convert path/to/image.png -w 200 -h 300 -o path/to/output.png
var command1x = ["\(svgConverter)", "\(imageData.path)"] // /usr/local/bin/rsvg-convert path/to/image.png -w 200 -o path/to/output.png
var command2x = ["\(svgConverter)", "\(imageData.path)"] // /usr/local/bin/rsvg-convert path/to/image.png -h 300 -o path/to/output.png
var command3x = ["\(svgConverter)", "\(imageData.path)"] var command1x = ["\(svgConverter)", "\(imageData.path)"]
var command2x = ["\(svgConverter)", "\(imageData.path)"]
self.addConvertArgument(command: &command1x, convertArgument: convertArguments.x1) var command3x = ["\(svgConverter)", "\(imageData.path)"]
self.addConvertArgument(command: &command2x, convertArgument: convertArguments.x2)
self.addConvertArgument(command: &command3x, convertArgument: convertArguments.x3) self.addConvertArgument(command: &command1x, convertArgument: convertArguments.x1)
self.addConvertArgument(command: &command2x, convertArgument: convertArguments.x2)
command1x.append(contentsOf: ["-o", output1x]) self.addConvertArgument(command: &command3x, convertArgument: convertArguments.x3)
command2x.append(contentsOf: ["-o", output2x])
command3x.append(contentsOf: ["-o", output3x]) command1x.append(contentsOf: ["-o", output1x])
command2x.append(contentsOf: ["-o", output2x])
Shell.shell(command1x) command3x.append(contentsOf: ["-o", output3x])
Shell.shell(command2x)
Shell.shell(command3x) 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 { } else {
// convert path/to/image.png -resize 200x300 path/to/output.png // 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 200x path/to/output.png
@ -119,7 +155,7 @@ class XcassetsGenerator {
} }
// Write Content.json // Write Content.json
guard let imagesetContentJson = parsedImage.contentJson else { return } guard let imagesetContentJson = parsedImage.generateContentJson(isVector: imageData.ext == "svg") else { return }
let contentJsonFilePath = "\(imagesetPath)/Contents.json" let contentJsonFilePath = "\(imagesetPath)/Contents.json"
let contentJsonFilePathURL = URL(fileURLWithPath: contentJsonFilePath) let contentJsonFilePathURL = URL(fileURLWithPath: contentJsonFilePath)
@ -161,8 +197,8 @@ class XcassetsGenerator {
// MARK: - Helpers: bypass generation // MARK: - Helpers: bypass generation
private func shouldGenerate(inputImagePath: String, xcassetImagePath: String) -> Bool { private func shouldGenerate(inputImagePath: String, xcassetImagePath: String, needToGenerateForSvg: Bool) -> Bool {
if forceGeneration { if forceGeneration || needToGenerateForSvg {
return true return true
} }

View File

@ -7,14 +7,30 @@
import Foundation import Foundation
enum TemplateRenderingIntent: String, Codable {
case template
case original
}
struct AssetContent: Codable, Equatable { struct AssetContent: Codable, Equatable {
let images: [AssetImageDescription] let images: [AssetImageDescription]
let info: AssetInfo let info: AssetInfo
let properties: AssetProperties?
init(
images: [AssetImageDescription],
info: AssetInfo,
properties: AssetProperties? = nil
) {
self.images = images
self.info = info
self.properties = properties
}
static func == (lhs: AssetContent, rhs: AssetContent) -> Bool { static func == (lhs: AssetContent, rhs: AssetContent) -> Bool {
guard lhs.images.count == rhs.images.count else { return false } guard lhs.images.count == rhs.images.count else { return false }
let lhsImages = lhs.images.sorted(by: { $0.scale < $1.scale }) let lhsImages = lhs.images.sorted(by: { $0.filename < $1.filename })
let rhsImages = rhs.images.sorted(by: { $0.scale < $1.scale }) let rhsImages = rhs.images.sorted(by: { $0.filename < $1.filename })
return lhsImages == rhsImages return lhsImages == rhsImages
} }
@ -22,11 +38,39 @@ struct AssetContent: Codable, Equatable {
struct AssetImageDescription: Codable, Equatable { struct AssetImageDescription: Codable, Equatable {
let idiom: String let idiom: String
let scale: String let scale: String?
let filename: String let filename: String
init(
idiom: String,
scale: String? = nil,
filename: String
) {
self.idiom = idiom
self.scale = scale
self.filename = filename
}
} }
struct AssetInfo: Codable, Equatable { struct AssetInfo: Codable, Equatable {
let version: Int let version: Int
let author: String let author: String
} }
struct AssetProperties: Codable, Equatable {
let preservesVectorRepresentation: Bool
let templateRenderingIntent: TemplateRenderingIntent?
init(
preservesVectorRepresentation: Bool,
templateRenderingIntent: TemplateRenderingIntent? = nil
) {
self.preservesVectorRepresentation = preservesVectorRepresentation
self.templateRenderingIntent = templateRenderingIntent
}
enum CodingKeys: String, CodingKey {
case preservesVectorRepresentation = "preserves-vector-representation"
case templateRenderingIntent = "template-rendering-intent"
}
}

View File

@ -7,12 +7,31 @@
import Foundation import Foundation
enum ImageExtension: String {
case png
}
struct ParsedImage { struct ParsedImage {
let name: String let name: String
let tags: String let tags: String
let width: Int let width: Int
let height: Int let height: Int
let imageExtensions: [ImageExtension]
init(
name: String,
tags: String,
width: Int,
height: Int,
imageExtensions: [ImageExtension] = []
) {
self.name = name
self.tags = tags
self.width = width
self.height = height
self.imageExtensions = imageExtensions
}
// MARK: - Convert // MARK: - Convert
var convertArguments: (x1: ConvertArgument, x2: ConvertArgument, x3: ConvertArgument) { var convertArguments: (x1: ConvertArgument, x2: ConvertArgument, x3: ConvertArgument) {
@ -42,10 +61,12 @@ struct ParsedImage {
// MARK: - Assets // MARK: - Assets
var contentJson: String? { func generateContentJson(isVector: Bool) -> String? {
let encoder = JSONEncoder() let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted encoder.outputFormatting = .prettyPrinted
let imageContent = generateImageContent(isVector: isVector)
guard let data = try? encoder.encode(imageContent) else { guard let data = try? encoder.encode(imageContent) else {
let error = ImagesError.writeFile("Contents.json", "Error encoding json file") let error = ImagesError.writeFile("Contents.json", "Error encoding json file")
print(error.description) print(error.description)
@ -55,30 +76,52 @@ struct ParsedImage {
return String(data: data, encoding: .utf8) return String(data: data, encoding: .utf8)
} }
var imageContent: AssetContent { func generateImageContent(isVector: Bool) -> AssetContent {
return AssetContent(
images: [ if !imageExtensions.contains(.png) && isVector {
AssetImageDescription( //Generate svg
idiom: "universal", return AssetContent(
scale: "1x", images: [
filename: "\(name).\(XcassetsGenerator.outputImageExtension)" AssetImageDescription(
idiom: "universal",
filename: "\(name).\(OutputImageExtension.svg.rawValue)"
)
],
info: AssetInfo(
version: 1,
author: "ResgenSwift-Imagium"
), ),
AssetImageDescription( properties: AssetProperties(
idiom: "universal", preservesVectorRepresentation: true,
scale: "2x", templateRenderingIntent: .original
filename: "\(name)@2x.\(XcassetsGenerator.outputImageExtension)"
),
AssetImageDescription(
idiom: "universal",
scale: "3x",
filename: "\(name)@3x.\(XcassetsGenerator.outputImageExtension)"
) )
],
info: AssetInfo(
version: 1,
author: "ResgenSwift-Imagium"
) )
) } else {
//Generate png
return AssetContent(
images: [
AssetImageDescription(
idiom: "universal",
scale: "1x",
filename: "\(name).\(OutputImageExtension.png.rawValue)"
),
AssetImageDescription(
idiom: "universal",
scale: "2x",
filename: "\(name)@2x.\(OutputImageExtension.png.rawValue)"
),
AssetImageDescription(
idiom: "universal",
scale: "3x",
filename: "\(name)@3x.\(OutputImageExtension.png.rawValue)"
)
],
info: AssetInfo(
version: 1,
author: "ResgenSwift-Imagium"
)
)
}
} }
// MARK: - Extension property // MARK: - Extension property

View File

@ -38,11 +38,21 @@ class ImageFileParser {
} }
return Int(splittedLine[3])! return Int(splittedLine[3])!
}() }()
let image = ParsedImage(name: String(splittedLine[1]), tags: String(splittedLine[0]), width: width, height: height) var imageExtensions: [ImageExtension] = []
splittedLine.forEach { stringExtension in
if let imageExtension = ImageExtension(rawValue: String(stringExtension)) {
imageExtensions.append(imageExtension)
}
}
let image = ParsedImage(name: String(splittedLine[1]), tags: String(splittedLine[0]), width: width, height: height, imageExtensions: imageExtensions)
imagesToGenerate.append(image) imagesToGenerate.append(image)
} }
print(imagesToGenerate)
return imagesToGenerate.filter { return imagesToGenerate.filter {
$0.tags.contains(platform.rawValue) $0.tags.contains(platform.rawValue)
} }

View File

@ -17,8 +17,8 @@ class ImageFileParserTests: XCTestCase {
# #
# SMAAS Support # SMAAS Support
# #
id image_one 25 ? id image_one 25 ? png
di image_two ? 50 di image_two ? 50 webp png
d image_three 25 ? d image_three 25 ?
d image_four 75 ? d image_four 75 ?
""" """
@ -38,13 +38,15 @@ class ImageFileParserTests: XCTestCase {
XCTAssertEqual(firstImage!.tags, "id") XCTAssertEqual(firstImage!.tags, "id")
XCTAssertEqual(firstImage!.width, 25) XCTAssertEqual(firstImage!.width, 25)
XCTAssertEqual(firstImage!.height, -1) XCTAssertEqual(firstImage!.height, -1)
XCTAssertEqual(firstImage!.imageExtensions, [.png])
let secondImage = parsedImages.first { let secondImage = parsedImages.first {
$0.name == "image_two" $0.name == "image_two"
} }
XCTAssertEqual(secondImage!.name, "image_two") XCTAssertEqual(secondImage!.name, "image_two")
XCTAssertEqual(secondImage!.tags, "di") XCTAssertEqual(secondImage!.tags, "di")
XCTAssertEqual(secondImage!.width, -1) XCTAssertEqual(secondImage!.width, -1)
XCTAssertEqual(secondImage!.height, 50) XCTAssertEqual(secondImage!.height, 50)
XCTAssertEqual(firstImage!.imageExtensions, [.png])
} }
} }

View File

@ -127,7 +127,7 @@ final class ParsedImageTests: XCTestCase {
height: 10) height: 10)
// When // When
let property = parsedImage.imageContent let property = parsedImage.generateImageContent(isVector: false)
// Expect // Expect
let expect = AssetContent( let expect = AssetContent(
@ -135,17 +135,17 @@ final class ParsedImageTests: XCTestCase {
AssetImageDescription( AssetImageDescription(
idiom: "universal", idiom: "universal",
scale: "1x", scale: "1x",
filename: "\(parsedImage.name).\(XcassetsGenerator.outputImageExtension)" filename: "\(parsedImage.name).\(OutputImageExtension.png.rawValue)"
), ),
AssetImageDescription( AssetImageDescription(
idiom: "universal", idiom: "universal",
scale: "2x", scale: "2x",
filename: "\(parsedImage.name)@2x.\(XcassetsGenerator.outputImageExtension)" filename: "\(parsedImage.name)@2x.\(OutputImageExtension.png.rawValue)"
), ),
AssetImageDescription( AssetImageDescription(
idiom: "universal", idiom: "universal",
scale: "3x", scale: "3x",
filename: "\(parsedImage.name)@3x.\(XcassetsGenerator.outputImageExtension)" filename: "\(parsedImage.name)@3x.\(OutputImageExtension.png.rawValue)"
) )
], ],
info: AssetInfo( info: AssetInfo(
@ -156,4 +156,48 @@ final class ParsedImageTests: XCTestCase {
XCTAssertEqual(property, expect) XCTAssertEqual(property, expect)
} }
func testAssetPng() {
// Given
let imageName = "the_name"
let parsedImage = ParsedImage(name: imageName,
tags: "id",
width: 10,
height: 10,
imageExtensions: [.png])
// When
let property = parsedImage.generateImageContent(isVector: false)
// Expect
let expect = AssetContent(
images: [
AssetImageDescription(
idiom: "universal",
scale: "1x",
filename: "\(parsedImage.name).\(OutputImageExtension.png.rawValue)"
),
AssetImageDescription(
idiom: "universal",
scale: "2x",
filename: "\(parsedImage.name)@2x.\(OutputImageExtension.png.rawValue)"
),
AssetImageDescription(
idiom: "universal",
scale: "3x",
filename: "\(parsedImage.name)@3x.\(OutputImageExtension.png.rawValue)"
)
],
info: AssetInfo(
version: 1,
author: "ResgenSwift-Imagium"
)
)
debugPrint(property)
debugPrint(expect)
XCTAssertEqual(property, expect)
}
} }