diff --git a/build.gradle.kts b/build.gradle.kts index 548b4f6a..042e4c2f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,7 +1,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") version "1.5.21" + kotlin("jvm") version "1.6.10" id("maven-publish") } @@ -15,10 +15,10 @@ repositories { } dependencies { - implementation("com.google.guava:guava:23.0") + implementation("com.google.guava:guava:31.1-jre") implementation("com.android.tools:sdk-common:27.2.0-alpha16") implementation("com.android.tools:common:27.2.0-alpha16") - implementation("com.squareup:kotlinpoet:1.9.0") + implementation("com.squareup:kotlinpoet:1.10.2") implementation("org.ogce:xpp3:1.1.6") testImplementation(kotlin("test-junit")) diff --git a/src/main/kotlin/androidx/compose/material/icons/generator/IconParser.kt b/src/main/kotlin/androidx/compose/material/icons/generator/IconParser.kt index 3b10bdc7..d7f16a81 100644 --- a/src/main/kotlin/androidx/compose/material/icons/generator/IconParser.kt +++ b/src/main/kotlin/androidx/compose/material/icons/generator/IconParser.kt @@ -1,4 +1,3 @@ - /* * Copyright 2020 The Android Open Source Project * @@ -18,13 +17,16 @@ package androidx.compose.material.icons.generator import androidx.compose.material.icons.generator.vector.* +import br.com.devsrsouza.svg2compose.Size import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser.END_DOCUMENT import org.xmlpull.v1.XmlPullParser.END_TAG import org.xmlpull.v1.XmlPullParser.START_TAG import org.xmlpull.v1.XmlPullParserException import org.xmlpull.v1.XmlPullParserFactory -import kotlin.math.log10 + + +data class ScaleFactor(val x: Float = 1f, val y: Float = 1f) /** * Parser that converts [icon]s into [Vector]s @@ -34,7 +36,7 @@ class IconParser(private val icon: Icon) { /** * @return a [Vector] representing the provided [icon]. */ - fun parse(): Vector { + fun parse(defaultSize: Size? = null): Vector { val parser = XmlPullParserFactory.newInstance().newPullParser().apply { setInput(icon.fileContent.byteInputStream(), null) seekToStartTag() @@ -47,6 +49,13 @@ class IconParser(private val icon: Icon) { val viewportWidth = parser.getAttributeValue(null, VIEWPORT_WIDTH).toFloat() val viewportHeight = parser.getAttributeValue(null, VIEWPORT_HEIGHT).toFloat() + val scale = defaultSize?.let { requestedSize -> + ScaleFactor( + requestedSize.width / viewportWidth, + requestedSize.height / viewportHeight + ) + } ?: ScaleFactor() + parser.next() val nodes = mutableListOf() @@ -98,7 +107,7 @@ class IconParser(private val icon: Icon) { strokeLineJoin = strokeJoin ?: StrokeJoin.Miter, strokeLineMiter = strokeMiterLimit ?: 4.0f, fillType = fillType, - nodes = PathParser.parsePathString(pathData) + nodes = PathParser.parsePathString(pathData, scale) ) if (currentGroup != null) { currentGroup.paths.add(path) @@ -115,7 +124,7 @@ class IconParser(private val icon: Icon) { CLIP_PATH -> { /* TODO: b/147418351 - parse clipping paths */ } GRADIENT -> { - val gradient = when (parser.getAttributeValue(null, TYPE)){ + val gradient = when (parser.getAttributeValue(null, TYPE)) { LINEAR -> { val startX = parser.getValueAsFloat(START_X) ?: 0f val startY = parser.getValueAsFloat(START_Y) ?: 0f @@ -142,8 +151,8 @@ class IconParser(private val icon: Icon) { } val lastPath = currentGroup?.paths?.removeLast() ?: nodes.removeLast() - if (lastPath as? VectorNode.Path != null && lastPath.fill == null){ - val gradientPath = lastPath.copy (fill = gradient) + if (lastPath as? VectorNode.Path != null && lastPath.fill == null) { + val gradientPath = lastPath.copy(fill = gradient) if (currentGroup != null) { currentGroup.paths.add(gradientPath) } else { @@ -155,9 +164,9 @@ class IconParser(private val icon: Icon) { val offset = parser.getValueAsFloat(OFFSET) ?: 0f val colorHex = parser.getAttributeValue(null, COLOR).toHexColor() - val colorStop = Pair(offset,colorHex) + val colorStop = Pair(offset, colorHex) val lastPath = (currentGroup?.paths?.last() ?: nodes.last()) as? VectorNode.Path - when (lastPath?.fill){ + when (lastPath?.fill) { is Fill.LinearGradient -> lastPath.fill.colorStops.add(colorStop) is Fill.RadialGradient -> lastPath.fill.colorStops.add(colorStop) else -> {} @@ -172,8 +181,8 @@ class IconParser(private val icon: Icon) { return Vector( width, height, - viewportWidth, - viewportHeight, + viewportWidth * scale.x, + viewportHeight * scale.y, nodes ) } @@ -205,7 +214,7 @@ private val hexRegex = "^[0-9a-fA-F]{6,8}".toRegex() private fun String.toHexColor(): String { return removePrefix("#") .let { - if(hexRegex.matches(it)) { + if (hexRegex.matches(it)) { if (it.length > 6) it else "FF$it" } else { diff --git a/src/main/kotlin/androidx/compose/material/icons/generator/IconWriter.kt b/src/main/kotlin/androidx/compose/material/icons/generator/IconWriter.kt index e360ff9c..d5fe131e 100644 --- a/src/main/kotlin/androidx/compose/material/icons/generator/IconWriter.kt +++ b/src/main/kotlin/androidx/compose/material/icons/generator/IconWriter.kt @@ -16,7 +16,7 @@ package androidx.compose.material.icons.generator -import br.com.devsrsouza.svg2compose.IconNameTransformer +import br.com.devsrsouza.svg2compose.Size import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.MemberName import java.io.File @@ -32,6 +32,7 @@ class IconWriter( private val icons: Collection, private val groupClass: ClassName, private val groupPackage: String, + private val defaultSize: Size?, ) { /** * Generates icons and writes them to [outputSrcDirectory], using [iconNamePredicate] to @@ -55,10 +56,26 @@ class IconWriter( }.map { icon -> val iconName = icon.kotlinName - val vector = IconParser(icon).parse() + /**check [androidx.compose.material.icons.generator.vector.Vector]**/ + val vector = IconParser(icon).parse(defaultSize).let { parsedVector -> + defaultSize?.let { + parsedVector.copy( + width = when (parsedVector.width) { + is Pixel -> Pixel(defaultSize.width) + is Dp -> Dp(defaultSize.width) + }, + height = when (parsedVector.height) { + is Pixel -> Pixel(defaultSize.height) + is Dp -> Dp(defaultSize.height) + } + ) + } ?: parsedVector + } val (fileSpec, accessProperty) = VectorAssetGenerator( - iconName, + defaultSize?.let { + "$iconName${it.maxValue}" + } ?: iconName, groupPackage, vector ).createFileSpec(groupClass) @@ -66,6 +83,9 @@ class IconWriter( fileSpec.writeTo(outputSrcDirectory) MemberName(fileSpec.packageName, accessProperty) + } + } + } diff --git a/src/main/kotlin/androidx/compose/material/icons/generator/VectorAssetGenerator.kt b/src/main/kotlin/androidx/compose/material/icons/generator/VectorAssetGenerator.kt index 98918681..579f2505 100644 --- a/src/main/kotlin/androidx/compose/material/icons/generator/VectorAssetGenerator.kt +++ b/src/main/kotlin/androidx/compose/material/icons/generator/VectorAssetGenerator.kt @@ -56,8 +56,7 @@ class VectorAssetGenerator( // Kotlin 1.4) each property with the same name will be considered as a possible candidate // for resolution, regardless of the access modifier, so by using unique names we reduce // the size from ~6000 to 1, and speed up compilation time for these icons. - @OptIn(ExperimentalStdlibApi::class) - val backingPropertyName = "_" + iconName.decapitalize(Locale.ROOT) + val backingPropertyName = "_" + iconName.replaceFirstChar { it.lowercase(Locale.ROOT) } val backingProperty = backingPropertySpec(name = backingPropertyName, ClassNames.ImageVector) val generation = FileSpec.builder( diff --git a/src/main/kotlin/androidx/compose/material/icons/generator/vector/PathNode.kt b/src/main/kotlin/androidx/compose/material/icons/generator/vector/PathNode.kt index 325a45e2..4a2671d7 100644 --- a/src/main/kotlin/androidx/compose/material/icons/generator/vector/PathNode.kt +++ b/src/main/kotlin/androidx/compose/material/icons/generator/vector/PathNode.kt @@ -16,6 +16,9 @@ package androidx.compose.material.icons.generator.vector +import androidx.compose.material.icons.generator.ScaleFactor +import com.google.common.math.Quantiles.Scale + /** * Class representing a singular path command in a vector. * @@ -39,6 +42,7 @@ sealed class PathNode(val isCurve: Boolean = false, val isQuad: Boolean = false) data class RelativeMoveTo(val x: Float, val y: Float) : PathNode() { override fun asFunctionCall() = "moveToRelative(${x}f, ${y}f)" } + data class MoveTo(val x: Float, val y: Float) : PathNode() { override fun asFunctionCall() = "moveTo(${x}f, ${y}f)" } @@ -46,6 +50,7 @@ sealed class PathNode(val isCurve: Boolean = false, val isQuad: Boolean = false) data class RelativeLineTo(val x: Float, val y: Float) : PathNode() { override fun asFunctionCall() = "lineToRelative(${x}f, ${y}f)" } + data class LineTo(val x: Float, val y: Float) : PathNode() { override fun asFunctionCall() = "lineTo(${x}f, ${y}f)" } @@ -53,6 +58,7 @@ sealed class PathNode(val isCurve: Boolean = false, val isQuad: Boolean = false) data class RelativeHorizontalTo(val x: Float) : PathNode() { override fun asFunctionCall() = "horizontalLineToRelative(${x}f)" } + data class HorizontalTo(val x: Float) : PathNode() { override fun asFunctionCall() = "horizontalLineTo(${x}f)" } @@ -60,6 +66,7 @@ sealed class PathNode(val isCurve: Boolean = false, val isQuad: Boolean = false) data class RelativeVerticalTo(val y: Float) : PathNode() { override fun asFunctionCall() = "verticalLineToRelative(${y}f)" } + data class VerticalTo(val y: Float) : PathNode() { override fun asFunctionCall() = "verticalLineTo(${y}f)" } @@ -145,7 +152,8 @@ sealed class PathNode(val isCurve: Boolean = false, val isQuad: Boolean = false) val arcStartDx: Float, val arcStartDy: Float ) : PathNode() { - override fun asFunctionCall() = "arcToRelative(${horizontalEllipseRadius}f, ${verticalEllipseRadius}f, ${theta}f, $isMoreThanHalf, $isPositiveArc, ${arcStartDx}f, ${arcStartDy}f)" + override fun asFunctionCall() = + "arcToRelative(${horizontalEllipseRadius}f, ${verticalEllipseRadius}f, ${theta}f, $isMoreThanHalf, $isPositiveArc, ${arcStartDx}f, ${arcStartDy}f)" } data class ArcTo( @@ -157,7 +165,8 @@ sealed class PathNode(val isCurve: Boolean = false, val isQuad: Boolean = false) val arcStartX: Float, val arcStartY: Float ) : PathNode() { - override fun asFunctionCall() = "arcTo(${horizontalEllipseRadius}f, ${verticalEllipseRadius}f, ${theta}f, $isMoreThanHalf, $isPositiveArc, ${arcStartX}f, ${arcStartY}f)" + override fun asFunctionCall() = + "arcTo(${horizontalEllipseRadius}f, ${verticalEllipseRadius}f, ${theta}f, $isMoreThanHalf, $isPositiveArc, ${arcStartX}f, ${arcStartY}f)" } } /* ktlint-enable max-line-length */ @@ -168,7 +177,7 @@ sealed class PathNode(val isCurve: Boolean = false, val isQuad: Boolean = false) * @return [PathNode] that matches the key * @throws IllegalArgumentException */ -internal fun Char.toPathNodes(args: FloatArray): List = when (this) { +internal fun Char.toPathNodes(args: FloatArray, scale: ScaleFactor = ScaleFactor()): List = when (this) { RelativeCloseKey, CloseKey -> listOf( PathNode.Close ) @@ -177,8 +186,8 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { NUM_MOVE_TO_ARGS ) { array -> PathNode.RelativeMoveTo( - x = array[0], - y = array[1] + x = array[0] * scale.x, + y = array[1] * scale.y ) } @@ -187,8 +196,8 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { NUM_MOVE_TO_ARGS ) { array -> PathNode.MoveTo( - x = array[0], - y = array[1] + x = array[0] * scale.x, + y = array[1] * scale.y ) } @@ -197,8 +206,8 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { NUM_LINE_TO_ARGS ) { array -> PathNode.RelativeLineTo( - x = array[0], - y = array[1] + x = array[0] * scale.x, + y = array[1] * scale.y ) } @@ -207,8 +216,8 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { NUM_LINE_TO_ARGS ) { array -> PathNode.LineTo( - x = array[0], - y = array[1] + x = array[0] * scale.x, + y = array[1] * scale.y ) } @@ -217,7 +226,7 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { NUM_HORIZONTAL_TO_ARGS ) { array -> PathNode.RelativeHorizontalTo( - x = array[0] + x = array[0] * scale.x ) } @@ -225,21 +234,21 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { args, NUM_HORIZONTAL_TO_ARGS ) { array -> - PathNode.HorizontalTo(x = array[0]) + PathNode.HorizontalTo(x = array[0] * scale.x) } RelativeVerticalToKey -> pathNodesFromArgs( args, NUM_VERTICAL_TO_ARGS ) { array -> - PathNode.RelativeVerticalTo(y = array[0]) + PathNode.RelativeVerticalTo(y = array[0] * scale.y) } VerticalToKey -> pathNodesFromArgs( args, NUM_VERTICAL_TO_ARGS ) { array -> - PathNode.VerticalTo(y = array[0]) + PathNode.VerticalTo(y = array[0] * scale.y) } RelativeCurveToKey -> pathNodesFromArgs( @@ -247,12 +256,12 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { NUM_CURVE_TO_ARGS ) { array -> PathNode.RelativeCurveTo( - dx1 = array[0], - dy1 = array[1], - dx2 = array[2], - dy2 = array[3], - dx3 = array[4], - dy3 = array[5] + dx1 = array[0] * scale.x, + dy1 = array[1] * scale.y, + dx2 = array[2] * scale.x, + dy2 = array[3] * scale.y, + dx3 = array[4] * scale.x, + dy3 = array[5] * scale.y ) } @@ -261,12 +270,12 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { NUM_CURVE_TO_ARGS ) { array -> PathNode.CurveTo( - x1 = array[0], - y1 = array[1], - x2 = array[2], - y2 = array[3], - x3 = array[4], - y3 = array[5] + x1 = array[0] * scale.x, + y1 = array[1] * scale.y, + x2 = array[2] * scale.x, + y2 = array[3] * scale.y, + x3 = array[4] * scale.x, + y3 = array[5] * scale.y ) } @@ -275,10 +284,10 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { NUM_REFLECTIVE_CURVE_TO_ARGS ) { array -> PathNode.RelativeReflectiveCurveTo( - x1 = array[0], - y1 = array[1], - x2 = array[2], - y2 = array[3] + x1 = array[0] * scale.x, + y1 = array[1] * scale.y, + x2 = array[2] * scale.x, + y2 = array[3] * scale.y ) } @@ -287,10 +296,10 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { NUM_REFLECTIVE_CURVE_TO_ARGS ) { array -> PathNode.ReflectiveCurveTo( - x1 = array[0], - y1 = array[1], - x2 = array[2], - y2 = array[3] + x1 = array[0] * scale.x, + y1 = array[1] * scale.y, + x2 = array[2] * scale.x, + y2 = array[3] * scale.y ) } @@ -299,10 +308,10 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { NUM_QUAD_TO_ARGS ) { array -> PathNode.RelativeQuadTo( - x1 = array[0], - y1 = array[1], - x2 = array[2], - y2 = array[3] + x1 = array[0] * scale.x, + y1 = array[1] * scale.y, + x2 = array[2] * scale.x, + y2 = array[3] * scale.y ) } @@ -311,10 +320,10 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { NUM_QUAD_TO_ARGS ) { array -> PathNode.QuadTo( - x1 = array[0], - y1 = array[1], - x2 = array[2], - y2 = array[3] + x1 = array[0] * scale.x, + y1 = array[1] * scale.y, + x2 = array[2] * scale.x, + y2 = array[3] * scale.y ) } @@ -323,8 +332,8 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { NUM_REFLECTIVE_QUAD_TO_ARGS ) { array -> PathNode.RelativeReflectiveQuadTo( - x = array[0], - y = array[1] + x = array[0] * scale.x, + y = array[1] * scale.y ) } @@ -333,8 +342,8 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { NUM_REFLECTIVE_QUAD_TO_ARGS ) { array -> PathNode.ReflectiveQuadTo( - x = array[0], - y = array[1] + x = array[0] * scale.x, + y = array[1] * scale.y ) } @@ -343,13 +352,13 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { NUM_ARC_TO_ARGS ) { array -> PathNode.RelativeArcTo( - horizontalEllipseRadius = array[0], - verticalEllipseRadius = array[1], + horizontalEllipseRadius = array[0] * scale.x, + verticalEllipseRadius = array[1] * scale.y, theta = array[2], - isMoreThanHalf = array[3].compareTo(0.0f) != 0, - isPositiveArc = array[4].compareTo(0.0f) != 0, - arcStartDx = array[5], - arcStartDy = array[6] + isMoreThanHalf = (array[3] * scale.x).compareTo(0.0f) != 0, + isPositiveArc = (array[4] * scale.y).compareTo(0.0f) != 0, + arcStartDx = array[5] * scale.x, + arcStartDy = array[6] * scale.y ) } @@ -358,13 +367,13 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { NUM_ARC_TO_ARGS ) { array -> PathNode.ArcTo( - horizontalEllipseRadius = array[0], - verticalEllipseRadius = array[1], + horizontalEllipseRadius = array[0] * scale.x, + verticalEllipseRadius = array[1] * scale.y, theta = array[2], - isMoreThanHalf = array[3].compareTo(0.0f) != 0, - isPositiveArc = array[4].compareTo(0.0f) != 0, - arcStartX = array[5], - arcStartY = array[6] + isMoreThanHalf = (array[3] * scale.x).compareTo(0.0f) != 0, + isPositiveArc = (array[4] * scale.y).compareTo(0.0f) != 0, + arcStartX = array[5] * scale.x, + arcStartY = array[6] * scale.y ) } @@ -374,6 +383,7 @@ internal fun Char.toPathNodes(args: FloatArray): List = when (this) { private inline fun pathNodesFromArgs( args: FloatArray, numArgs: Int, + scale: ScaleFactor = ScaleFactor(), nodeFor: (subArray: FloatArray) -> PathNode ): List { return (0..args.size - numArgs step numArgs).map { index -> @@ -383,13 +393,13 @@ private inline fun pathNodesFromArgs( // According to the spec, if a MoveTo is followed by multiple pairs of coordinates, // the subsequent pairs are treated as implicit corresponding LineTo commands. node is PathNode.MoveTo && index > 0 -> PathNode.LineTo( - subArray[0], - subArray[1] + subArray[0] * scale.x, + subArray[1] * scale.y ) node is PathNode.RelativeMoveTo && index > 0 -> PathNode.RelativeLineTo( - subArray[0], - subArray[1] + subArray[0] * scale.x, + subArray[1] * scale.y ) else -> node } diff --git a/src/main/kotlin/androidx/compose/material/icons/generator/vector/PathParser.kt b/src/main/kotlin/androidx/compose/material/icons/generator/vector/PathParser.kt index e004a05f..9cd23ac4 100644 --- a/src/main/kotlin/androidx/compose/material/icons/generator/vector/PathParser.kt +++ b/src/main/kotlin/androidx/compose/material/icons/generator/vector/PathParser.kt @@ -16,6 +16,7 @@ package androidx.compose.material.icons.generator.vector +import androidx.compose.material.icons.generator.ScaleFactor import kotlin.math.min /** @@ -28,11 +29,11 @@ object PathParser { * arguments * throws an IllegalArgumentException or NumberFormatException if the parameters are invalid */ - fun parsePathString(pathData: String): List { + fun parsePathString(pathData: String, scale: ScaleFactor = ScaleFactor()): List { val nodes = mutableListOf() fun addNode(cmd: Char, args: FloatArray) { - nodes.addAll(cmd.toPathNodes(args)) + nodes.addAll(cmd.toPathNodes(args, scale)) } var start = 0 diff --git a/src/main/kotlin/androidx/compose/material/icons/generator/vector/Vector.kt b/src/main/kotlin/androidx/compose/material/icons/generator/vector/Vector.kt index 4c57ffdd..a5a77cb6 100644 --- a/src/main/kotlin/androidx/compose/material/icons/generator/vector/Vector.kt +++ b/src/main/kotlin/androidx/compose/material/icons/generator/vector/Vector.kt @@ -25,7 +25,7 @@ import java.awt.LinearGradientPaint * [nodes] may either be a singleton list of the root group, or a list of root paths / groups if * there are multiple top level declaration. */ -class Vector( +data class Vector( val width: GraphicUnit, val height: GraphicUnit, val viewportWidth: Float, @@ -60,12 +60,13 @@ sealed class Fill { val startX: Float, val endY: Float, val endX: Float, - val colorStops: MutableList> = mutableListOf() + val colorStops: MutableList> = mutableListOf() ) : Fill() + data class RadialGradient( val gradientRadius: Float, val centerX: Float, val centerY: Float, - val colorStops: MutableList> = mutableListOf() - ): Fill() + val colorStops: MutableList> = mutableListOf() + ) : Fill() } \ No newline at end of file diff --git a/src/main/kotlin/br/com/devsrsouza/svg2compose/Svg2Compose.kt b/src/main/kotlin/br/com/devsrsouza/svg2compose/Svg2Compose.kt index f012a7b4..ec3d0df3 100644 --- a/src/main/kotlin/br/com/devsrsouza/svg2compose/Svg2Compose.kt +++ b/src/main/kotlin/br/com/devsrsouza/svg2compose/Svg2Compose.kt @@ -7,9 +7,19 @@ import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.MemberName import java.io.File import java.util.* +import kotlin.io.path.createTempDirectory +import kotlin.math.max typealias IconNameTransformer = (iconName: String, group: String) -> String +data class Size(val height: Float, val width: Float) { + constructor(size: Float) : this(size, size) + constructor(size: Int) : this(size.toFloat()) + + val maxValue + get() = max(height, width).toInt() +} + object Svg2Compose { /** @@ -27,7 +37,8 @@ object Svg2Compose { vectorsDirectory: File, type: VectorType = VectorType.SVG, iconNameTransformer: IconNameTransformer = { it, _ -> it }, - allAssetsPropertyName: String = "AllAssets" + allAssetsPropertyName: String = "AllAssets", + size: Size? = null, ): ParsingResult { fun nameRelative(vectorFile: File) = vectorFile.relativeTo(vectorsDirectory).path @@ -38,17 +49,18 @@ object Svg2Compose { vectorsDirectory.walkTopDown() .maxDepth(10) .onEnter { file -> - val dirIcons = file.listFiles() + val dirIcons = (file.listFiles() ?: arrayOf()) + .filterNotNull() .filter { it.isDirectory.not() } .filter { it.extension.equals(type.extension, ignoreCase = true) } val previousGroup = groupStack.peekOrNull() // if there is no previous group, this is the root dir, and the group name should be the accessorName - val groupName = if(previousGroup == null) accessorName else file.name.toKotlinPropertyName() - val groupPackage = previousGroup?.let { group -> "${group.groupPackage}.${group.groupName.second.toLowerCase()}" } - ?: "$applicationIconPackage" - val iconsPackage = "$groupPackage.${groupName.toLowerCase()}" + val groupName = if (previousGroup == null) accessorName else file.name.toKotlinPropertyName() + val groupPackage = previousGroup?.let { group -> "${group.groupPackage}.${group.groupName.second.lowercase()}" } + ?: applicationIconPackage + val iconsPackage = "$groupPackage.${groupName.lowercase()}" val (groupFileSpec, groupClassName) = IconGroupGenerator( groupPackage, @@ -57,7 +69,7 @@ object Svg2Compose { val generatedIconsMemberNames: Map = - if(dirIcons.isNotEmpty()) { + if (dirIcons.isNotEmpty()) { val drawables: List> = when (type) { VectorType.SVG -> dirIcons.map { val iconName = nameRelative(it).withoutExtension @@ -87,15 +99,20 @@ object Svg2Compose { icons.values, groupClassName, iconsPackage, + size ) val memberNames = writer.generateTo(outputSourceDirectory) { true } icons.mapValues { entry -> - memberNames.first { it.simpleName == entry.value.kotlinName } + memberNames.first { + val name = (size?.let { setSize -> "${entry.value.kotlinName}${setSize.maxValue}" } + ?: entry.value.kotlinName) + it.simpleName == name + } } } else { - emptyMap() + emptyMap() } val result = GeneratedGroup( @@ -107,7 +124,7 @@ object Svg2Compose { childGroups = emptyList() ) - if(previousGroup != null) { + if (previousGroup != null) { groupStack.pop() groupStack.push(previousGroup.copy(childGroups = previousGroup.childGroups + result)) } @@ -117,7 +134,7 @@ object Svg2Compose { true } .onLeave { - val group = if(groupStack.size > 1) + val group = if (groupStack.size > 1) groupStack.pop() else groupStack.peek() @@ -140,7 +157,7 @@ object Svg2Compose { return groupStack.pop().asParsingResult() } - private fun drawableTempDirectory() = createTempDir(suffix = "svg2compose/") + private fun drawableTempDirectory() = createTempDirectory(prefix = "svg2compose").toFile() private val String.withoutExtension get() = substringBeforeLast(".") } diff --git a/src/test/kotlin/TestMain.kt b/src/test/kotlin/TestMain.kt new file mode 100644 index 00000000..15a04363 --- /dev/null +++ b/src/test/kotlin/TestMain.kt @@ -0,0 +1,92 @@ +package br.com.devsrsouza.svg2compose + +import org.junit.Test +import java.io.File +import kotlin.test.assertEquals + +class TestMain { + + @Test + fun traverseGeneratorTest() { + val iconsDir = File("src/test/resources/icons") + val destinationDir = File("src/test/resources/generated").apply { mkdirs() } + + assert(iconsDir.exists()) { + "Make sure to add icons into res dir. Default test icons have been provided" + } + + assert(destinationDir.exists()) { + "Icons destination dir wasn't created ${destinationDir.path}" + } + + Svg2Compose.parse( + applicationIconPackage = "br.com.compose.icons", + accessorName = "EvaIcons", + outputSourceDirectory = destinationDir, + vectorsDirectory = iconsDir, + iconNameTransformer = { name, group -> + name.removeSuffix(group, ignoreCase = true) + }, + size = Size(24) + ) + + val generatedIconsDir = File(destinationDir, "br/com/compose/icons") + + assert(generatedIconsDir.exists()) { + "Icons weren't generated" + } + + val requiredIcons = mutableListOf() + + iconsDir.walkTopDown().onEnter { it -> + if (it.isFile && it.isPlausibleIcon) { + requiredIcons.add(it.path.camelCase) + true + } else false + } + + val generatedIcons = mutableListOf() + + destinationDir.walkTopDown().onEnter { it -> + if (it.isFile) { + generatedIcons.add(it) + true + } else false + + } + + // assertEquals(requiredIcons.size, generatedIcons.size, "Error generating all icons.") + + // destinationDir.deleteRecursively() + + } + + val File.isPlausibleIcon + get() = extension.lowercase() == "xml" || extension.lowercase() == "svg" + + private fun String.removeSuffix(suffix: String, ignoreCase: Boolean): String { + return if (ignoreCase) { + val index = lastIndexOf(suffix, ignoreCase = true) + + if (index > -1) substring(0, index) else this + } else { + removeSuffix(suffix) + } + } + + @Test + fun testCamelCase() { + val iconName = "IconName" + + assertEquals(iconName, "icon-name".camelCase) + assertEquals(iconName, "icon name".camelCase) + assertEquals(iconName, "icon_name".camelCase) + + } + + val String.camelCase: String + get() = trim().split(" ", "-", "_").joinToString("") { + it[0].uppercase() + it.substring(1, it.length) + } +} + diff --git a/src/test/resources/icons/branch.svg b/src/test/resources/icons/branch.svg new file mode 100644 index 00000000..e468352e --- /dev/null +++ b/src/test/resources/icons/branch.svg @@ -0,0 +1,15 @@ + + + + + + + diff --git a/src/test/resources/icons/ic_balance_scale.svg b/src/test/resources/icons/ic_balance_scale.svg new file mode 100644 index 00000000..cb76ac43 --- /dev/null +++ b/src/test/resources/icons/ic_balance_scale.svg @@ -0,0 +1,25 @@ + + + + + + +