Skip to content

Commit

Permalink
Added support for gradients (#6)
Browse files Browse the repository at this point in the history
* Added different types of fill

* Added new testing script

* The parser detects now gradient tags

* The parser reads now Linear and Radial gradient tags

* The parser reads now gradient color stops

* The parser correctly parses now linear gradients

* Parser now parses radial gradients

* Fixed issues with the parsing arguments

* The parser now supports linear and radial gradients

* Adjusted the gradient imports
  • Loading branch information
MarioFerreiraStorytel authored Jul 23, 2021
1 parent 1a67b5b commit 7e1323e
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,13 @@ class IconParser(private val icon: Icon) {
val fillColor = parser.getAttributeValue(null, FILL_COLOR)
?.toHexColor()

val fill = when {
fillColor != null -> Fill.Color(fillColor)
else -> null
}

val path = VectorNode.Path(
fillColorHex = fillColor,
fill = fill,
strokeColorHex = strokeColor,
strokeAlpha = strokeAlpha ?: 1f,
fillAlpha = fillAlpha ?: 1f,
Expand All @@ -110,6 +114,55 @@ class IconParser(private val icon: Icon) {
}
CLIP_PATH -> { /* TODO: b/147418351 - parse clipping paths */
}
GRADIENT -> {
val gradient = when (parser.getAttributeValue(null, TYPE)){
LINEAR -> {
val startX = parser.getValueAsFloat(START_X) ?: 0f
val startY = parser.getValueAsFloat(START_Y) ?: 0f
val endX = parser.getValueAsFloat(END_X) ?: 0f
val endY = parser.getValueAsFloat(END_Y) ?: 0f
Fill.LinearGradient(
startY = startY,
startX = startX,
endX = endX,
endY = endY
)
}
RADIAL -> {
val gradientRadius = parser.getValueAsFloat(GRADIENT_RADIUS) ?: 0f
val centerX = parser.getValueAsFloat(CENTER_X) ?: 0f
val centerY = parser.getValueAsFloat(CENTER_Y) ?: 0f
Fill.RadialGradient(
gradientRadius = gradientRadius,
centerX = centerX,
centerY = centerY
)
}
else -> null
}

val lastPath = currentGroup?.paths?.removeLast() ?: nodes.removeLast()
if (lastPath as? VectorNode.Path != null && lastPath.fill == null){
val gradientPath = lastPath.copy (fill = gradient)
if (currentGroup != null) {
currentGroup.paths.add(gradientPath)
} else {
nodes.add(gradientPath)
}
}
}
ITEM -> {
val offset = parser.getValueAsFloat(OFFSET) ?: 0f
val colorHex = parser.getAttributeValue(null, COLOR).toHexColor()

val colorStop = Pair(offset,colorHex)
val lastPath = (currentGroup?.paths?.last() ?: nodes.last()) as? VectorNode.Path
when (lastPath?.fill){
is Fill.LinearGradient -> lastPath.fill.colorStops.add(colorStop)
is Fill.RadialGradient -> lastPath.fill.colorStops.add(colorStop)
else -> {}
}
}
}
}
}
Expand Down Expand Up @@ -149,7 +202,7 @@ private fun XmlPullParser.isAtEnd() =

private val hexRegex = "^[0-9a-fA-F]{6,8}".toRegex()

private fun String.toHexColor(): String? {
private fun String.toHexColor(): String {
return removePrefix("#")
.let {
if(hexRegex.matches(it)) {
Expand All @@ -165,6 +218,12 @@ private fun String.toHexColor(): String? {
private const val CLIP_PATH = "clip-path"
private const val GROUP = "group"
private const val PATH = "path"
private const val GRADIENT = "gradient"
private const val ITEM = "item"

// XML names
private const val LINEAR = "linear"
private const val RADIAL = "radial"

// Path XML attribute names
private const val PATH_DATA = "android:pathData"
Expand All @@ -178,12 +237,25 @@ private const val STROKE_MITER_LIMIT = "android:strokeMiterLimit"
private const val STROKE_COLOR = "android:strokeColor"
private const val FILL_COLOR = "android:fillColor"

// Gradient XML attribute names
private const val TYPE = "android:type"
private const val START_Y = "android:startY"
private const val START_X = "android:startX"
private const val END_Y = "android:endY"
private const val END_X = "android:endX"
private const val GRADIENT_RADIUS = "android:gradientRadius"
private const val CENTER_X = "android:centerX"
private const val CENTER_Y = "android:centerY"

// Item XML attribute names
private const val OFFSET = "android:offset"
private const val COLOR = "android:color"

// Vector XML attribute names
private const val WIDTH = "android:width"
private const val HEIGHT = "android:height"
private const val VIEWPORT_WIDTH = "android:viewportWidth"
private const val VIEWPORT_HEIGHT = "android:viewportHeight"


// XML attribute values
private const val EVEN_ODD = "evenOdd"
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum class PackageNames(val packageName: String) {
MaterialIconsPackage("androidx.compose.material.icons"),
GraphicsPackage("androidx.compose.ui.graphics"),
VectorPackage(GraphicsPackage.packageName + ".vector"),
GeometryPackage("androidx.compose.ui.geometry"),
Unit("androidx.compose.ui.unit"),
}

Expand All @@ -38,6 +39,7 @@ object ClassNames {
val PathFillType = PackageNames.GraphicsPackage.className("PathFillType", CompanionImportName)
val StrokeCap = PackageNames.GraphicsPackage.className("StrokeCap", CompanionImportName)
val StrokeJoin = PackageNames.GraphicsPackage.className("StrokeJoin", CompanionImportName)
val Brush = PackageNames.GraphicsPackage.className("Brush", CompanionImportName)
}

/**
Expand Down Expand Up @@ -65,6 +67,11 @@ object MemberNames {

val Color = MemberName(PackageNames.GraphicsPackage.packageName, "Color")
val SolidColor = MemberName(PackageNames.GraphicsPackage.packageName, "SolidColor")

val LinearGradient = MemberName(ClassNames.Brush, "linearGradient")
val RadialGradient = MemberName(ClassNames.Brush, "radialGradient")

val Offset = MemberName(PackageNames.GeometryPackage.packageName, "Offset")
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package androidx.compose.material.icons.generator

import androidx.compose.material.icons.generator.util.backingPropertySpec
import androidx.compose.material.icons.generator.util.withBackingProperty
import androidx.compose.material.icons.generator.vector.Fill
import androidx.compose.material.icons.generator.vector.Vector
import androidx.compose.material.icons.generator.vector.VectorNode
import com.squareup.kotlinpoet.*
Expand Down Expand Up @@ -149,12 +150,11 @@ private fun CodeBlock.Builder.addPath(
path: VectorNode.Path,
pathBody: CodeBlock.Builder.() -> Unit
) {
val hasFillColor = path.fillColorHex != null
val hasStrokeColor = path.strokeColorHex != null

val parameterList = with(path) {
listOfNotNull(
"fill = ${if(hasFillColor) "%M(%M(0x$fillColorHex))" else "null"}",
"fill = ${getPathFill(path)}",
"stroke = ${if(hasStrokeColor) "%M(%M(0x$strokeColorHex))" else "null"}",
"fillAlpha = ${fillAlpha}f".takeIf { fillAlpha != 1f },
"strokeAlpha = ${strokeAlpha}f".takeIf { strokeAlpha != 1f },
Expand All @@ -170,15 +170,37 @@ private fun CodeBlock.Builder.addPath(

val members: Array<Any> = listOfNotNull(
MemberNames.Path,
MemberNames.SolidColor.takeIf { hasFillColor },
MemberNames.Color.takeIf { hasFillColor },
MemberNames.SolidColor.takeIf { hasStrokeColor },
MemberNames.Color.takeIf { hasStrokeColor },
path.strokeLineWidth.memberName,
path.strokeLineCap.memberName,
path.strokeLineJoin.memberName,
path.fillType.memberName
).toTypedArray()
).toMutableList().apply {
var fillIndex = 1
when (path.fill){
is Fill.Color -> {
add(fillIndex, MemberNames.SolidColor)
add(++fillIndex, MemberNames.Color)
}
is Fill.LinearGradient -> {
add(fillIndex, MemberNames.LinearGradient)
path.fill.colorStops.forEach { _ ->
add(++fillIndex, MemberNames.Color)
}
add(++fillIndex, MemberNames.Offset)
add(++fillIndex, MemberNames.Offset)
}
is Fill.RadialGradient -> {
add(fillIndex, MemberNames.RadialGradient)
path.fill.colorStops.forEach { _ ->
add(++fillIndex, MemberNames.Color)
}
add(++fillIndex, MemberNames.Offset)
}
null -> {}
}
}.toTypedArray()

beginControlFlow(
"%M$parameters",
Expand All @@ -189,4 +211,55 @@ private fun CodeBlock.Builder.addPath(
endControlFlow()
}

private fun getPathFill (
path: VectorNode.Path
) = when (path.fill){
is Fill.Color -> "%M(%M(0x${path.fill.colorHex}))"
is Fill.LinearGradient -> {
with (path.fill){
"%M(" +
"${getGradientStops(path.fill.colorStops).toString().removeSurrounding("[","]")}, " +
"start = %M(${startX}f,${startY}f), " +
"end = %M(${endX}f,${endY}f))"
}
}
is Fill.RadialGradient -> {
with (path.fill){
"%M(${getGradientStops(path.fill.colorStops).toString().removeSurrounding("[","]")}, " +
"center = %M(${centerX}f,${centerY}f), " +
"radius = ${gradientRadius}f)"
}
}
else -> "null"
}

private fun getGradientStops(
stops: List<Pair<Float, String>>
) = stops.map { stop ->
"${stop.first}f to %M(0x${stop.second})"
}

private fun CodeBlock.Builder.addLinearGradient(
gradient: Fill.LinearGradient,
pathBody: CodeBlock.Builder.() -> Unit
){
//"0.0f to Color.Red"
val parameterList = with(gradient) {
listOfNotNull(
"start = %M(${gradient.startX},${gradient.startY})",
"end = %M(${gradient.endX},${gradient.endY})"
)
}

val parameters = parameterList.joinToString(prefix = "(", postfix = ")")

val members: Array<Any> = listOfNotNull(
MemberNames.LinearGradient,
MemberNames.Offset,
MemberNames.Offset
).toTypedArray()


}

private val GraphicUnit.withMemberIfNotNull: String get() = "${value}${if (memberName != null) ".%M" else "f"}"
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package androidx.compose.material.icons.generator.vector

import androidx.compose.material.icons.generator.GraphicUnit
import java.awt.LinearGradientPaint

/**
* Simplified representation of a vector, with root [nodes].
Expand All @@ -38,8 +39,8 @@ class Vector(
*/
sealed class VectorNode {
class Group(val paths: MutableList<Path> = mutableListOf()) : VectorNode()
class Path(
val fillColorHex: String?,
data class Path(
val fill: Fill?,
val strokeColorHex: String?,
val strokeAlpha: Float,
val fillAlpha: Float,
Expand All @@ -50,4 +51,21 @@ sealed class VectorNode {
val fillType: FillType,
val nodes: List<PathNode>
) : VectorNode()
}

sealed class Fill {
data class Color(val colorHex: String) : Fill()
data class LinearGradient(
val startY: Float,
val startX: Float,
val endY: Float,
val endX: Float,
val colorStops: MutableList<Pair<Float,String>> = mutableListOf()
) : Fill()
data class RadialGradient(
val gradientRadius: Float,
val centerX: Float,
val centerY: Float,
val colorStops: MutableList<Pair<Float,String>> = mutableListOf()
): Fill()
}
8 changes: 4 additions & 4 deletions src/main/kotlin/br/com/devsrsouza/svg2compose/Svg2Compose.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ object Svg2Compose {

vectorsDirectory.walkTopDown()
.maxDepth(10)
.onEnter {
val dirIcons = it.listFiles()
.onEnter { file ->
val dirIcons = file.listFiles()
.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 it.name.toKotlinPropertyName()
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()}"
Expand Down Expand Up @@ -100,7 +100,7 @@ object Svg2Compose {

val result = GeneratedGroup(
groupPackage,
it to groupName,
file to groupName,
generatedIconsMemberNames,
groupClassName,
groupFileSpec,
Expand Down
21 changes: 21 additions & 0 deletions src/test/kotlin/EmojiTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package br.com.devsrsouza.svg2compose

import java.io.File


fun main(){
val iconTest = File("/Users/marioferreiravilanova/Documents/Workspace/kotlin/svg-to-compose/src/test/icons")
val src = File("/Users/marioferreiravilanova/Documents/Workspace/kotlin/svg-to-compose/src/test/results").apply { mkdirs() }

Svg2Compose.parse(
applicationIconPackage = "com.test",
accessorName = "Icons",
outputSourceDirectory = src,
vectorsDirectory = iconTest,
type = VectorType.SVG,
iconNameTransformer = { name, group ->
name.split("-").joinToString(separator = "").removePrefix(group)
},
allAssetsPropertyName = "AllIcons"
)
}

0 comments on commit 7e1323e

Please sign in to comment.