-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 1c95113
Showing
21 changed files
with
1,071 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
name: Release | ||
|
||
on: | ||
workflow_dispatch: | ||
push: | ||
branches: | ||
- master | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
permissions: | ||
contents: write | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Set up JDK | ||
uses: actions/setup-java@v4 | ||
with: | ||
distribution: 'temurin' | ||
java-version: 16 | ||
cache: 'gradle' | ||
- name: Grant execute permission for gradlew | ||
run: chmod +x gradlew | ||
- name: Validate Gradle wrapper | ||
uses: gradle/wrapper-validation-action@v2 | ||
- name: Setup Gradle | ||
uses: gradle/actions/setup-gradle@v3 | ||
- name: Build JAR | ||
run: ./gradlew build | ||
- name: Release | ||
uses: softprops/action-gh-release@v2 | ||
with: | ||
draft: true | ||
files: ./app/build/libs/vektr.jar |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
*.iml | ||
.gradle | ||
**/build/ | ||
!src/**/build/ | ||
local.properties | ||
.idea | ||
captures |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
## Vektr | ||
Vektr is a tool for converting [Material icons](https://fonts.google.com/icons?icon.set=Material+Icons) | ||
`.xml` files (Android Vector Drawable) into the code | ||
in a way Material icons are delivered with Jetpack Compose / Compose Multiplatform. | ||
This tool basically parses icon XML file, parses path data of icon and splits it into | ||
separate path elements and finally generates .kt file containing path drawing instructions. | ||
|
||
<details> | ||
<summary>Example of generated file</summary> | ||
|
||
```kotlin | ||
package com.github.trueddd.ui.res.icons.map | ||
|
||
import androidx.compose.material.icons.Icons | ||
import androidx.compose.material.icons.materialPath | ||
import androidx.compose.ui.graphics.vector.ImageVector | ||
import androidx.compose.ui.unit.dp | ||
|
||
@Suppress("UnusedReceiverParameter") | ||
val Icons.Rounded.Map: ImageVector | ||
get() { | ||
if (savedMap != null) { | ||
return savedMap!! | ||
} | ||
savedMap = ImageVector.Builder( | ||
name = "Map", | ||
defaultWidth = 24.dp, | ||
defaultHeight = 24.dp, | ||
viewportWidth = 24f, | ||
viewportHeight = 24f, | ||
) | ||
.materialPath { | ||
moveTo(14.65f, 4.98f) | ||
lineToRelative(-5.0f, -1.75f) | ||
curveToRelative(-0.42f, -0.15f, -0.88f, -0.15f, -1.3f, -0.01f) | ||
lineTo(4.36f, 4.56f) | ||
curveTo(3.55f, 4.84f, 3.0f, 5.6f, 3.0f, 6.46f) | ||
verticalLineToRelative(11.85f) | ||
curveToRelative(0.0f, 1.41f, 1.41f, 2.37f, 2.72f, 1.86f) | ||
lineToRelative(2.93f, -1.14f) | ||
curveToRelative(0.22f, -0.09f, 0.47f, -0.09f, 0.69f, -0.01f) | ||
lineToRelative(5.0f, 1.75f) | ||
curveToRelative(0.42f, 0.15f, 0.88f, 0.15f, 1.3f, 0.01f) | ||
lineToRelative(3.99f, -1.34f) | ||
curveToRelative(0.81f, -0.27f, 1.36f, -1.04f, 1.36f, -1.9f) | ||
verticalLineTo(5.69f) | ||
curveToRelative(0.0f, -1.41f, -1.41f, -2.37f, -2.72f, -1.86f) | ||
lineToRelative(-2.93f, 1.14f) | ||
curveToRelative(-0.22f, 0.08f, -0.46f, 0.09f, -0.69f, 0.01f) | ||
close() | ||
moveTo(15.0f, 18.89f) | ||
lineToRelative(-6.0f, -2.11f) | ||
verticalLineTo(5.11f) | ||
lineToRelative(6.0f, 2.11f) | ||
verticalLineToRelative(11.67f) | ||
close() | ||
} | ||
.build() | ||
return savedMap!! | ||
} | ||
|
||
private var savedMap: ImageVector? = null | ||
``` | ||
</details> | ||
|
||
There is a lot of Material icons that are not included into `androidx.compose.material.icons` package, | ||
and in order to use them you have to either import them as resource or parse path data in runtime. | ||
Both these approaches are valid, but I wanted new icons easy to modify and compile-time checked. | ||
|
||
### CLI tool usage | ||
Run downloaded or locally built jar file on the terminal: | ||
```shell | ||
java -jar vektr.jar <arguments> | ||
``` | ||
Here is the list of available arguments: | ||
|
||
| Name | Value | Required | Example | Description | | ||
|-----------------------|----------------|----------|--------------------------------|--------------------------------------------------------------------------------------------------------------------------------| | ||
| Input file | `in` | true | `--in=./search_24px.xml` | Defines the source XML file | | ||
| Output directory | `out` | true | `--out=./dir/` | Defines the directory where generated file will be placed | | ||
| Name of icon | `n`, `name` | false | `--name=Search` | The icon name, if not specified name of input file will be used | | ||
| Output file name | `f`, `file` | false | `--file=RoundedSearch` | Name of file where generated code will be saved. If not specified, name of icon will be used | | ||
| Package name | `p`, `package` | false | `--package=com.github.trueddd` | Package name for the generated file | | ||
| Icon style | `s`, `style` | false | `--style=Outlined` | Icon style that will be used as Icon receiver type. Must be one of these: `Outlined`, `Rounded`, `TwoTone`, `Sharp`, `Filled`. | | ||
| Auto mirror | `a` | false | `-a` (flag) | Generate this icon as auto mirrored | | ||
| Use path data builder | `pd` | false | `-pd` (flag) | Use path data builder instead of materialPath function (not recommended) | | ||
|
||
For example, sample code hidden in spoiler was generated with following command: | ||
```shell | ||
java -jar vektr.jar --name=Map --file=Rounded --package=com.github.trueddd.ui.res.icons.map --style=Rounded --in=./rounded_map_24px.xml --out=./ | ||
``` | ||
|
||
### Build locally | ||
Run following command on terminal to build jar. | ||
```shell | ||
gradlew build | ||
``` | ||
Optionally, you can create `local.properties` file in root folder and set path | ||
where you want jar to be placed after build: | ||
```properties | ||
target_path=<path> | ||
``` | ||
|
||
### To-do | ||
Here is planned tasks for this project, | ||
but do not hesitate to offer some changes or features via issues. | ||
- [ ] Move code composing into common module | ||
- [ ] Add web app for online file conversions | ||
- [ ] Reduce jar size | ||
- [ ] Use downloaded zip archives as conversion source (currently only XML files are supported) | ||
- [ ] Use Material icon URL as conversion source |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import java.util.* | ||
|
||
plugins { | ||
alias(libs.plugins.kotlinMultiplatform) | ||
alias(libs.plugins.shadowJar) | ||
alias(libs.plugins.jetbrainsCompose) | ||
application | ||
java | ||
} | ||
|
||
kotlin { | ||
jvm { | ||
withJava() | ||
} | ||
jvmToolchain(16) | ||
|
||
sourceSets { | ||
val jvmMain by getting | ||
|
||
commonMain.dependencies { | ||
implementation(compose.material) | ||
implementation(compose.ui) | ||
} | ||
jvmMain.dependencies { | ||
implementation(libs.poet) | ||
implementation(libs.jackson.kotlin) | ||
implementation(libs.jackson.xml) | ||
implementation(libs.clikt) | ||
} | ||
} | ||
} | ||
|
||
application { | ||
mainClass.set("com.github.trueddd.MainKt") | ||
} | ||
|
||
tasks.shadowJar { | ||
archiveFileName.set("vektr.jar") | ||
minimize { | ||
exclude("META-INF/*.DSA") | ||
exclude("META-INF/*.SF") | ||
exclude(dependency("com.fasterxml.woodstox:.*:.*")) | ||
exclude(dependency("org.jetbrains.kotlin:kotlin-reflect:.*")) | ||
} | ||
doLast { | ||
val targetPath = Properties().apply { | ||
load(project.rootProject.file("local.properties").inputStream()) | ||
}.getProperty("target_path") ?: return@doLast | ||
copy { | ||
from(archiveFile) | ||
into(targetPath) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.github.trueddd | ||
|
||
enum class IconStyle { | ||
Rounded, | ||
Outlined, | ||
Filled, | ||
TwoTone, | ||
Sharp, | ||
} |
176 changes: 176 additions & 0 deletions
176
app/src/commonMain/kotlin/com/github/trueddd/PathNodeMapper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
package com.github.trueddd | ||
|
||
import androidx.compose.ui.graphics.vector.PathBuilder | ||
import androidx.compose.ui.graphics.vector.PathNode | ||
import com.squareup.kotlinpoet.CodeBlock | ||
import com.squareup.kotlinpoet.MemberName.Companion.member | ||
import com.squareup.kotlinpoet.asClassName | ||
|
||
internal fun PathNode.asMethodCall(): CodeBlock.Builder { | ||
val className = PathBuilder::class.asClassName() | ||
val representation = when (this) { | ||
is PathNode.MoveTo -> CodeBlock.of( | ||
"%L(%Lf, %Lf)", | ||
className.member("moveTo").simpleName, x, y | ||
) | ||
is PathNode.LineTo -> CodeBlock.of( | ||
"%L(%Lf, %Lf)", | ||
className.member("lineTo").simpleName, x, y | ||
) | ||
is PathNode.HorizontalTo -> CodeBlock.of( | ||
"%L(%Lf)", | ||
className.member("horizontalLineTo").simpleName, x | ||
) | ||
is PathNode.VerticalTo -> CodeBlock.of( | ||
"%L(%Lf)", | ||
className.member("verticalLineTo").simpleName, y | ||
) | ||
is PathNode.QuadTo -> CodeBlock.of( | ||
"%L(%Lf, %Lf, %Lf, %Lf)", | ||
className.member("quadTo").simpleName, x1, y1, x2, y2 | ||
) | ||
is PathNode.CurveTo -> CodeBlock.of( | ||
"%L(%Lf, %Lf, %Lf, %Lf, %Lf, %Lf)", | ||
className.member("curveTo").simpleName, x1, y1, x2, y2, x3, y3 | ||
) | ||
is PathNode.ArcTo -> CodeBlock.of( | ||
"%L(%Lf, %Lf, %Lf, %L, %L, %Lf, %Lf)", | ||
className.member("arcTo").simpleName, horizontalEllipseRadius, verticalEllipseRadius, theta, | ||
isMoreThanHalf, isPositiveArc, arcStartX , arcStartY | ||
) | ||
is PathNode.Close -> CodeBlock.of( | ||
"%L()", | ||
className.member("close").simpleName | ||
) | ||
is PathNode.ReflectiveCurveTo -> CodeBlock.of( | ||
"%L(%Lf, %Lf, %Lf, %Lf)", | ||
className.member("reflectiveCurveTo").simpleName, x1, y1, x2, y2 | ||
) | ||
is PathNode.ReflectiveQuadTo -> CodeBlock.of( | ||
"%L(%Lf, %Lf)", | ||
className.member("reflectiveQuadTo").simpleName, x, y | ||
) | ||
is PathNode.RelativeArcTo -> CodeBlock.of( | ||
"%L(%Lf, %Lf, %Lf, %L, %L, %Lf, %Lf)", | ||
className.member("arcToRelative").simpleName, horizontalEllipseRadius, verticalEllipseRadius, theta, | ||
isMoreThanHalf, isPositiveArc, arcStartDx, arcStartDy | ||
) | ||
is PathNode.RelativeCurveTo -> CodeBlock.of( | ||
"%L(%Lf, %Lf, %Lf, %Lf, %Lf, %Lf)", | ||
className.member("curveToRelative").simpleName, dx1, dy1, dx2, dy2, dx3, dy3 | ||
) | ||
is PathNode.RelativeHorizontalTo -> CodeBlock.of( | ||
"%L(%Lf)", | ||
className.member("horizontalLineToRelative").simpleName, dx | ||
) | ||
is PathNode.RelativeLineTo -> CodeBlock.of( | ||
"%L(%Lf, %Lf)", | ||
className.member("lineToRelative").simpleName, dx, dy | ||
) | ||
is PathNode.RelativeMoveTo -> CodeBlock.of( | ||
"%L(%Lf, %Lf)", | ||
className.member("moveToRelative").simpleName, dx, dy | ||
) | ||
is PathNode.RelativeQuadTo -> CodeBlock.of( | ||
"%L(%Lf, %Lf, %Lf, %Lf)", | ||
className.member("quadToRelative").simpleName, dx1, dy1, dx2, dy2 | ||
) | ||
is PathNode.RelativeReflectiveCurveTo -> CodeBlock.of( | ||
"%L(%Lf, %Lf, %Lf, %Lf)", | ||
className.member("reflectiveCurveToRelative").simpleName, dx1, dy1, dx2, dy2 | ||
) | ||
is PathNode.RelativeReflectiveQuadTo -> CodeBlock.of( | ||
"%L(%Lf, %Lf)", | ||
className.member("reflectiveQuadToRelative").simpleName, dx, dy | ||
) | ||
is PathNode.RelativeVerticalTo -> CodeBlock.of( | ||
"%L(%Lf)", | ||
className.member("verticalLineToRelative").simpleName, dy | ||
) | ||
} | ||
return CodeBlock.builder().add(representation) | ||
} | ||
|
||
internal fun PathNode.asClassCall(): CodeBlock.Builder { | ||
val representation = when (this) { | ||
is PathNode.MoveTo -> CodeBlock.of( | ||
"%T(%Lf, %Lf)", | ||
PathNode.MoveTo::class, x, y | ||
) | ||
is PathNode.LineTo -> CodeBlock.of( | ||
"%T(%Lf, %Lf)", | ||
PathNode.LineTo::class, x, y | ||
) | ||
is PathNode.HorizontalTo -> CodeBlock.of( | ||
"%T(%Lf)", | ||
PathNode.HorizontalTo::class, x | ||
) | ||
is PathNode.VerticalTo -> CodeBlock.of( | ||
"%T(%Lf)", | ||
PathNode.VerticalTo::class, y | ||
) | ||
is PathNode.QuadTo -> CodeBlock.of( | ||
"%T(%Lf, %Lf, %Lf, %Lf)", | ||
PathNode.QuadTo::class, x1, y1, x2, y2 | ||
) | ||
is PathNode.CurveTo -> CodeBlock.of( | ||
"%T(%Lf, %Lf, %Lf, %Lf, %Lf, %Lf)", | ||
PathNode.CurveTo::class, x1, y1, x2, y2, x3, y3 | ||
) | ||
is PathNode.ArcTo -> CodeBlock.of( | ||
"%T(%Lf, %Lf, %Lf, %L, %L, %Lf, %Lf)", | ||
PathNode.ArcTo::class, horizontalEllipseRadius, verticalEllipseRadius, theta, | ||
isMoreThanHalf, isPositiveArc, arcStartX , arcStartY | ||
) | ||
is PathNode.Close -> CodeBlock.of( | ||
"%T", | ||
PathNode.Close::class | ||
) | ||
is PathNode.ReflectiveCurveTo -> CodeBlock.of( | ||
"%T(%Lf, %Lf, %Lf, %Lf)", | ||
PathNode.ReflectiveCurveTo::class, x1, y1, x2, y2 | ||
) | ||
is PathNode.ReflectiveQuadTo -> CodeBlock.of( | ||
"%T(%Lf, %Lf)", | ||
PathNode.ReflectiveQuadTo::class, x, y | ||
) | ||
is PathNode.RelativeArcTo -> CodeBlock.of( | ||
"%T(%Lf, %Lf, %Lf, %L, %L, %Lf, %Lf)", | ||
PathNode.RelativeArcTo::class, horizontalEllipseRadius, verticalEllipseRadius, theta, | ||
isMoreThanHalf, isPositiveArc, arcStartDx, arcStartDy | ||
) | ||
is PathNode.RelativeCurveTo -> CodeBlock.of( | ||
"%T(%Lf, %Lf, %Lf, %Lf, %Lf, %Lf)", | ||
PathNode.RelativeCurveTo::class, dx1, dy1, dx2, dy2, dx3, dy3 | ||
) | ||
is PathNode.RelativeHorizontalTo -> CodeBlock.of( | ||
"%T(%Lf)", | ||
PathNode.RelativeHorizontalTo::class, dx | ||
) | ||
is PathNode.RelativeLineTo -> CodeBlock.of( | ||
"%T(%Lf, %Lf)", | ||
PathNode.RelativeLineTo::class, dx, dy | ||
) | ||
is PathNode.RelativeMoveTo -> CodeBlock.of( | ||
"%T(%Lf, %Lf)", | ||
PathNode.RelativeMoveTo::class, dx, dy | ||
) | ||
is PathNode.RelativeQuadTo -> CodeBlock.of( | ||
"%T(%Lf, %Lf, %Lf, %Lf)", | ||
PathNode.RelativeQuadTo::class, dx1, dy1, dx2, dy2 | ||
) | ||
is PathNode.RelativeReflectiveCurveTo -> CodeBlock.of( | ||
"%T(%Lf, %Lf, %Lf, %Lf)", | ||
PathNode.RelativeReflectiveCurveTo::class, dx1, dy1, dx2, dy2 | ||
) | ||
is PathNode.RelativeReflectiveQuadTo -> CodeBlock.of( | ||
"%T(%Lf, %Lf)", | ||
PathNode.RelativeReflectiveQuadTo::class, dx, dy | ||
) | ||
is PathNode.RelativeVerticalTo -> CodeBlock.of( | ||
"%T(%Lf)", | ||
PathNode.RelativeVerticalTo::class, dy | ||
) | ||
} | ||
return CodeBlock.builder().add(representation) | ||
} |
Oops, something went wrong.