├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── ic_launcher-playstore.png
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── values
│ │ │ │ ├── ic_launcher_background.xml
│ │ │ │ ├── themes.xml
│ │ │ │ └── strings.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ └── data_extraction_rules.xml
│ │ │ └── drawable
│ │ │ │ ├── ic_content_copy_24dp.xml
│ │ │ │ ├── ic_launcher_monochrome.xml
│ │ │ │ ├── ic_launcher_foreground.xml
│ │ │ │ └── ic_launcher_background.xml
│ │ ├── kotlin
│ │ │ └── ink
│ │ │ │ └── duo3
│ │ │ │ └── fontconverter
│ │ │ │ ├── utils
│ │ │ │ ├── GetAppVerion.kt
│ │ │ │ └── StyleText.kt
│ │ │ │ ├── ui
│ │ │ │ ├── theme
│ │ │ │ │ ├── Type.kt
│ │ │ │ │ ├── Color.kt
│ │ │ │ │ └── Theme.kt
│ │ │ │ └── components
│ │ │ │ │ ├── SomethingMystical.kt
│ │ │ │ │ ├── AppBanner.kt
│ │ │ │ │ ├── InputCard.kt
│ │ │ │ │ └── StyleItem.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ └── FontConverterApp.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── kotlin
│ │ │ └── ink
│ │ │ └── duo3
│ │ │ └── fontconverter
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── kotlin
│ │ └── ink
│ │ └── duo3
│ │ └── fontconverter
│ │ └── ExampleInstrumentedTest.kt
├── release
│ └── output-metadata.json
├── proguard-rules.pro
└── build.gradle.kts
├── assets
└── banner.png
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── settings.gradle.kts
├── .gitignore
├── README.md
├── gradle.properties
├── gradlew.bat
├── gradlew
└── LICENSE
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/assets/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/123Duo3/FontConverter/HEAD/assets/banner.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/123Duo3/FontConverter/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/123Duo3/FontConverter/HEAD/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/123Duo3/FontConverter/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/123Duo3/FontConverter/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/123Duo3/FontConverter/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/123Duo3/FontConverter/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/123Duo3/FontConverter/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/123Duo3/FontConverter/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/123Duo3/FontConverter/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/123Duo3/FontConverter/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/123Duo3/FontConverter/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/123Duo3/FontConverter/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri May 12 09:39:54 CST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-milestone-1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 |
16 | rootProject.name = "FontConverter"
17 | include(":app")
18 |
--------------------------------------------------------------------------------
/app/src/test/kotlin/ink/duo3/fontconverter/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package ink.duo3.fontconverter
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/release/output-metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "artifactType": {
4 | "type": "APK",
5 | "kind": "Directory"
6 | },
7 | "applicationId": "ink.duo3.fontconverter",
8 | "variantName": "release",
9 | "elements": [
10 | {
11 | "type": "SINGLE",
12 | "filters": [],
13 | "attributes": [],
14 | "versionCode": 3,
15 | "versionName": "1.1.1",
16 | "outputFile": "app-release.apk"
17 | }
18 | ],
19 | "elementType": "File"
20 | }
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Gradle files
2 | .gradle/
3 | build/
4 |
5 | # Local configuration file (sdk path, etc)
6 | local.properties
7 |
8 | # Log/OS Files
9 | *.log
10 |
11 | # Android Studio generated files and folders
12 | captures/
13 | .externalNativeBuild/
14 | .cxx/
15 | *.apk
16 | output.json
17 |
18 | # IntelliJ
19 | *.iml
20 | .idea/
21 | misc.xml
22 | deploymentTargetDropDown.xml
23 | render.experimental.xml
24 |
25 | # Keystore files
26 | *.jks
27 | *.keystore
28 |
29 | # Google Services (e.g. APIs or Firebase)
30 | google-services.json
31 |
32 | # Android Profiling
33 | *.hprof
34 | app/.DS_Store
35 | .DS_Store
36 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_content_copy_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_monochrome.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/androidTest/kotlin/ink/duo3/fontconverter/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package ink.duo3.fontconverter
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("ink.duo3.fontconverter", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | # FontConverter
3 |
4 | [](LICENSE)
5 | [](https://github.com/123Duo3/FontConverter/releases)
6 |
7 | Convert fonts by using Mathematical Alphanumeric Symbols and other Unicode blocks, enables multiple font styles on platforms without rich text.
8 |
9 | # Supported Text Styles
10 | - Serif
11 | - [x] Bold
12 | - [x] Italic
13 | - [x] Bold italic
14 | - Sans Serif
15 | - [x] Normal
16 | - [x] Bold
17 | - [x] Italic
18 | - [x] Bold italic
19 | - Script
20 | - [x] Normal
21 | - [x] Bold
22 | - Fraktur
23 | - [x] Normal
24 | - [x] Bold
25 | - Mono-space
26 | - [x] Normal
27 | - Double-struck
28 | - [x] Bold
29 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
16 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/ink/duo3/fontconverter/utils/GetAppVerion.kt:
--------------------------------------------------------------------------------
1 | package ink.duo3.fontconverter.utils
2 |
3 | import android.content.Context
4 | import android.content.pm.PackageManager
5 | import android.os.Build
6 | import androidx.core.content.pm.PackageInfoCompat
7 |
8 | data class AppVersion(
9 | val versionName: String,
10 | val versionNumber: Long,
11 | )
12 |
13 | fun getAppVersion(
14 | context: Context,
15 | ): AppVersion? {
16 | return try {
17 | val packageManager = context.packageManager
18 | val packageName = context.packageName
19 | val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
20 | packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
21 | } else {
22 | packageManager.getPackageInfo(packageName, 0)
23 | }
24 | AppVersion(
25 | versionName = packageInfo.versionName,
26 | versionNumber = PackageInfoCompat.getLongVersionCode(packageInfo),
27 | )
28 | } catch (e: Exception) {
29 | null
30 | }
31 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
15 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/ink/duo3/fontconverter/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package ink.duo3.fontconverter.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | bodyLarge = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp,
15 | lineHeight = 24.sp,
16 | letterSpacing = 0.5.sp
17 | )
18 | /* Other default text styles to override
19 | titleLarge = TextStyle(
20 | fontFamily = FontFamily.Default,
21 | fontWeight = FontWeight.Normal,
22 | fontSize = 22.sp,
23 | lineHeight = 28.sp,
24 | letterSpacing = 0.sp
25 | ),
26 | labelSmall = TextStyle(
27 | fontFamily = FontFamily.Default,
28 | fontWeight = FontWeight.Medium,
29 | fontSize = 11.sp,
30 | lineHeight = 16.sp,
31 | letterSpacing = 0.5.sp
32 | )
33 | */
34 | )
--------------------------------------------------------------------------------
/app/src/main/kotlin/ink/duo3/fontconverter/ui/components/SomethingMystical.kt:
--------------------------------------------------------------------------------
1 | package ink.duo3.fontconverter.ui.components
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.text.intl.Locale
5 | import androidx.compose.ui.text.toLowerCase
6 |
7 | @Composable
8 | fun SomethingMystical(text: String) {
9 | val vowels = listOf('a', 'e', 'i', 'o', 'u')
10 | val locale = Locale.current
11 | val words = text.toLowerCase(locale).split(" ")
12 | var result = ""
13 | words.forEachIndexed { wordIndex, word ->
14 | word.forEachIndexed { letterIndex, c ->
15 | if (word.first() in vowels) {
16 | if (letterIndex % 2 == 0) {
17 | result += c.uppercaseChar()
18 | } else {
19 | result += c
20 | }
21 | } else {
22 | if (letterIndex % 2 == 1) {
23 | result += c.uppercaseChar()
24 | } else {
25 | result += c
26 | }
27 | }
28 | }
29 | if (wordIndex != words.lastIndex) {
30 | result += ' '
31 | }
32 | }
33 |
34 | StyledItemDisplay(tag = "Bruh", text = result)
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/ink/duo3/fontconverter/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package ink.duo3.fontconverter
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.layout.BoxWithConstraints
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.CompositionLocalProvider
9 | import androidx.compose.runtime.LaunchedEffect
10 | import androidx.compose.runtime.compositionLocalOf
11 | import androidx.compose.runtime.getValue
12 | import androidx.compose.runtime.mutableStateOf
13 | import androidx.compose.runtime.remember
14 | import androidx.compose.runtime.setValue
15 | import androidx.compose.ui.platform.LocalDensity
16 | import androidx.compose.ui.unit.dp
17 | import androidx.core.view.WindowCompat
18 | import ink.duo3.fontconverter.ui.theme.AppTheme
19 |
20 | class MainActivity : ComponentActivity() {
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | WindowCompat.setDecorFitsSystemWindows(window, false)
24 | setContent {
25 | AppTheme {
26 | // A surface container using the 'background' color from the theme
27 | FontConverterApp()
28 | }
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | FontConvtr
3 | FontConverter
4 | Original
5 | Serif Bold
6 | Serif Italic
7 | Serif Bold Italic
8 | Version %1$s
9 | Unknown
10 | View source code on GitHub.
11 | Type something here…
12 | … and styled text goes here
13 | Copied
14 | Sans
15 | Sans Bold
16 | Sans Italic
17 | Sans Bold Italic
18 | Script
19 | Script Bold
20 | Fraktur
21 | Fraktur Bold
22 | Monospace
23 | Doublestruck
24 | Bruh
25 | Bruh
26 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/ink/duo3/fontconverter/ui/components/AppBanner.kt:
--------------------------------------------------------------------------------
1 | package ink.duo3.fontconverter.ui.components
2 |
3 | import android.graphics.drawable.Drawable
4 | import androidx.compose.foundation.BorderStroke
5 | import androidx.compose.foundation.Image
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.Spacer
8 | import androidx.compose.foundation.layout.size
9 | import androidx.compose.foundation.layout.width
10 | import androidx.compose.foundation.shape.CircleShape
11 | import androidx.compose.material3.MaterialTheme
12 | import androidx.compose.material3.Surface
13 | import androidx.compose.material3.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.draw.scale
18 | import androidx.compose.ui.layout.ContentScale
19 | import androidx.compose.ui.res.painterResource
20 | import androidx.compose.ui.res.stringResource
21 | import androidx.compose.ui.tooling.preview.Preview
22 | import androidx.compose.ui.unit.Dp
23 | import androidx.compose.ui.unit.dp
24 | import ink.duo3.fontconverter.R
25 | import ink.duo3.fontconverter.ui.theme.seed
26 |
27 | @Composable
28 | fun AppBanner() {
29 | Row(
30 | verticalAlignment = Alignment.CenterVertically
31 | ) {
32 | AppIcon()
33 | Spacer(modifier = Modifier.width(16.dp))
34 | Text(
35 | text = stringResource(id = R.string.app_name_full),
36 | style = MaterialTheme.typography.headlineSmall
37 | )
38 | }
39 | }
40 |
41 | @Composable
42 | fun AppIcon(size: Dp = 32.dp) {
43 | Surface(
44 | color = seed,
45 | shape = CircleShape,
46 | border = BorderStroke(
47 | 0.1.dp,
48 | MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.1f)
49 | ),
50 | modifier = Modifier.size(size)
51 | ) {
52 | Image(
53 | painter = painterResource(id = R.drawable.ic_launcher_foreground),
54 | contentDescription = "icon",
55 | contentScale = ContentScale.FillBounds,
56 | modifier = Modifier.scale(1.4f)
57 | )
58 | }
59 | }
60 |
61 | @Preview
62 | @Composable
63 | fun AppBannerPreview() {
64 | AppBanner()
65 | }
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.application")
3 | id("org.jetbrains.kotlin.android")
4 | }
5 |
6 | android {
7 | namespace = "ink.duo3.fontconverter"
8 | compileSdk = 33
9 |
10 | defaultConfig {
11 | applicationId = "ink.duo3.fontconverter"
12 | minSdk = 24
13 | targetSdk = 33
14 | versionCode = 3
15 | versionName = "1.1.1"
16 |
17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18 | vectorDrawables {
19 | useSupportLibrary = true
20 | }
21 | }
22 |
23 | buildTypes {
24 | release {
25 | isMinifyEnabled = true
26 | proguardFiles(
27 | getDefaultProguardFile("proguard-android-optimize.txt"),
28 | "proguard-rules.pro"
29 | )
30 | }
31 | }
32 | compileOptions {
33 | sourceCompatibility = JavaVersion.VERSION_1_8
34 | targetCompatibility = JavaVersion.VERSION_1_8
35 | }
36 | kotlinOptions {
37 | jvmTarget = "1.8"
38 | }
39 | buildFeatures {
40 | compose = true
41 | }
42 | composeOptions {
43 | kotlinCompilerExtensionVersion = "1.4.3"
44 | }
45 | packaging {
46 | resources {
47 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
48 | }
49 | }
50 | }
51 |
52 | dependencies {
53 | val ui = "1.4.3"
54 |
55 | implementation("androidx.window:window:1.0.0")
56 | implementation("com.google.accompanist:accompanist-systemuicontroller:0.30.1")
57 | implementation("com.google.accompanist:accompanist-adaptive:0.30.1")
58 | implementation("androidx.core:core-ktx:1.10.1")
59 | implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
60 | implementation("androidx.activity:activity-compose:1.7.1")
61 | implementation(platform("androidx.compose:compose-bom:2023.05.00"))
62 | implementation("androidx.compose.ui:ui:$ui")
63 | implementation("androidx.compose.ui:ui-graphics:$ui")
64 | implementation("androidx.compose.ui:ui-tooling-preview:$ui")
65 | implementation("androidx.compose.material3:material3:1.2.0-alpha01")
66 | testImplementation("junit:junit:4.13.2")
67 | androidTestImplementation("androidx.test.ext:junit:1.1.5")
68 | androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
69 | androidTestImplementation(platform("androidx.compose:compose-bom:2023.05.00"))
70 | androidTestImplementation("androidx.compose.ui:ui-test-junit4:$ui")
71 | debugImplementation("androidx.compose.ui:ui-tooling:$ui")
72 | debugImplementation("androidx.compose.ui:ui-test-manifest:$ui")
73 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/ink/duo3/fontconverter/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package ink.duo3.fontconverter.ui.theme
2 | import androidx.compose.ui.graphics.Color
3 |
4 | val md_theme_light_primary = Color(0xFF4355B9)
5 | val md_theme_light_onPrimary = Color(0xFFFFFFFF)
6 | val md_theme_light_primaryContainer = Color(0xFFDEE0FF)
7 | val md_theme_light_onPrimaryContainer = Color(0xFF00105C)
8 | val md_theme_light_secondary = Color(0xFF5B5D72)
9 | val md_theme_light_onSecondary = Color(0xFFFFFFFF)
10 | val md_theme_light_secondaryContainer = Color(0xFFE0E1F9)
11 | val md_theme_light_onSecondaryContainer = Color(0xFF181A2C)
12 | val md_theme_light_tertiary = Color(0xFF77536D)
13 | val md_theme_light_onTertiary = Color(0xFFFFFFFF)
14 | val md_theme_light_tertiaryContainer = Color(0xFFFFD7F1)
15 | val md_theme_light_onTertiaryContainer = Color(0xFF2D1228)
16 | val md_theme_light_error = Color(0xFFBA1A1A)
17 | val md_theme_light_errorContainer = Color(0xFFFFDAD6)
18 | val md_theme_light_onError = Color(0xFFFFFFFF)
19 | val md_theme_light_onErrorContainer = Color(0xFF410002)
20 | val md_theme_light_background = Color(0xFFFEFBFF)
21 | val md_theme_light_onBackground = Color(0xFF1B1B1F)
22 | val md_theme_light_outline = Color(0xFF767680)
23 | val md_theme_light_inverseOnSurface = Color(0xFFF3F0F4)
24 | val md_theme_light_inverseSurface = Color(0xFF303034)
25 | val md_theme_light_inversePrimary = Color(0xFFBAC3FF)
26 | val md_theme_light_surfaceTint = Color(0xFF4355B9)
27 | val md_theme_light_outlineVariant = Color(0xFFC7C5D0)
28 | val md_theme_light_scrim = Color(0xFF000000)
29 | val md_theme_light_surface = Color(0xFFFBF8FD)
30 | val md_theme_light_onSurface = Color(0xFF1B1B1F)
31 | val md_theme_light_surfaceVariant = Color(0xFFE3E1EC)
32 | val md_theme_light_onSurfaceVariant = Color(0xFF46464F)
33 |
34 | val md_theme_dark_primary = Color(0xFFBAC3FF)
35 | val md_theme_dark_onPrimary = Color(0xFF08218A)
36 | val md_theme_dark_primaryContainer = Color(0xFF293CA0)
37 | val md_theme_dark_onPrimaryContainer = Color(0xFFDEE0FF)
38 | val md_theme_dark_secondary = Color(0xFFC3C5DD)
39 | val md_theme_dark_onSecondary = Color(0xFF2D2F42)
40 | val md_theme_dark_secondaryContainer = Color(0xFF434659)
41 | val md_theme_dark_onSecondaryContainer = Color(0xFFE0E1F9)
42 | val md_theme_dark_tertiary = Color(0xFFE6BAD7)
43 | val md_theme_dark_onTertiary = Color(0xFF44263D)
44 | val md_theme_dark_tertiaryContainer = Color(0xFF5D3C55)
45 | val md_theme_dark_onTertiaryContainer = Color(0xFFFFD7F1)
46 | val md_theme_dark_error = Color(0xFFFFB4AB)
47 | val md_theme_dark_errorContainer = Color(0xFF93000A)
48 | val md_theme_dark_onError = Color(0xFF690005)
49 | val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
50 | val md_theme_dark_background = Color(0xFF1B1B1F)
51 | val md_theme_dark_onBackground = Color(0xFFE4E1E6)
52 | val md_theme_dark_outline = Color(0xFF90909A)
53 | val md_theme_dark_inverseOnSurface = Color(0xFF1B1B1F)
54 | val md_theme_dark_inverseSurface = Color(0xFFE4E1E6)
55 | val md_theme_dark_inversePrimary = Color(0xFF4355B9)
56 | val md_theme_dark_surfaceTint = Color(0xFFBAC3FF)
57 | val md_theme_dark_outlineVariant = Color(0xFF46464F)
58 | val md_theme_dark_scrim = Color(0xFF000000)
59 | val md_theme_dark_surface = Color(0xFF131316)
60 | val md_theme_dark_onSurface = Color(0xFFC8C5CA)
61 | val md_theme_dark_surfaceVariant = Color(0xFF46464F)
62 | val md_theme_dark_onSurfaceVariant = Color(0xFFC7C5D0)
63 |
64 |
65 | val seed = Color(0xFF3F51B5)
66 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/ink/duo3/fontconverter/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package ink.duo3.fontconverter.ui.theme
2 |
3 | import android.os.Build
4 | import androidx.compose.foundation.isSystemInDarkTheme
5 | import androidx.compose.material3.MaterialTheme
6 | import androidx.compose.material3.lightColorScheme
7 | import androidx.compose.material3.darkColorScheme
8 | import androidx.compose.material3.dynamicDarkColorScheme
9 | import androidx.compose.material3.dynamicLightColorScheme
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.platform.LocalContext
13 | import com.google.accompanist.systemuicontroller.rememberSystemUiController
14 |
15 |
16 | private val LightColorScheme = lightColorScheme(
17 | primary = md_theme_light_primary,
18 | onPrimary = md_theme_light_onPrimary,
19 | primaryContainer = md_theme_light_primaryContainer,
20 | onPrimaryContainer = md_theme_light_onPrimaryContainer,
21 | secondary = md_theme_light_secondary,
22 | onSecondary = md_theme_light_onSecondary,
23 | secondaryContainer = md_theme_light_secondaryContainer,
24 | onSecondaryContainer = md_theme_light_onSecondaryContainer,
25 | tertiary = md_theme_light_tertiary,
26 | onTertiary = md_theme_light_onTertiary,
27 | tertiaryContainer = md_theme_light_tertiaryContainer,
28 | onTertiaryContainer = md_theme_light_onTertiaryContainer,
29 | error = md_theme_light_error,
30 | errorContainer = md_theme_light_errorContainer,
31 | onError = md_theme_light_onError,
32 | onErrorContainer = md_theme_light_onErrorContainer,
33 | background = md_theme_light_background,
34 | onBackground = md_theme_light_onBackground,
35 | outline = md_theme_light_outline,
36 | inverseOnSurface = md_theme_light_inverseOnSurface,
37 | inverseSurface = md_theme_light_inverseSurface,
38 | inversePrimary = md_theme_light_inversePrimary,
39 | surfaceTint = md_theme_light_surfaceTint,
40 | outlineVariant = md_theme_light_outlineVariant,
41 | scrim = md_theme_light_scrim,
42 | surface = md_theme_light_surface,
43 | onSurface = md_theme_light_onSurface,
44 | surfaceVariant = md_theme_light_surfaceVariant,
45 | onSurfaceVariant = md_theme_light_onSurfaceVariant,
46 | )
47 |
48 |
49 | private val DarkColorScheme = darkColorScheme(
50 | primary = md_theme_dark_primary,
51 | onPrimary = md_theme_dark_onPrimary,
52 | primaryContainer = md_theme_dark_primaryContainer,
53 | onPrimaryContainer = md_theme_dark_onPrimaryContainer,
54 | secondary = md_theme_dark_secondary,
55 | onSecondary = md_theme_dark_onSecondary,
56 | secondaryContainer = md_theme_dark_secondaryContainer,
57 | onSecondaryContainer = md_theme_dark_onSecondaryContainer,
58 | tertiary = md_theme_dark_tertiary,
59 | onTertiary = md_theme_dark_onTertiary,
60 | tertiaryContainer = md_theme_dark_tertiaryContainer,
61 | onTertiaryContainer = md_theme_dark_onTertiaryContainer,
62 | error = md_theme_dark_error,
63 | errorContainer = md_theme_dark_errorContainer,
64 | onError = md_theme_dark_onError,
65 | onErrorContainer = md_theme_dark_onErrorContainer,
66 | background = md_theme_dark_background,
67 | onBackground = md_theme_dark_onBackground,
68 | outline = md_theme_dark_outline,
69 | inverseOnSurface = md_theme_dark_inverseOnSurface,
70 | inverseSurface = md_theme_dark_inverseSurface,
71 | inversePrimary = md_theme_dark_inversePrimary,
72 | surfaceTint = md_theme_dark_surfaceTint,
73 | outlineVariant = md_theme_dark_outlineVariant,
74 | scrim = md_theme_dark_scrim,
75 | surface = md_theme_dark_surface,
76 | onSurface = md_theme_dark_onSurface,
77 | surfaceVariant = md_theme_dark_surfaceVariant,
78 | onSurfaceVariant = md_theme_dark_onSurfaceVariant,
79 | )
80 |
81 | @Composable
82 | fun AppTheme(
83 | darkTheme: Boolean = isSystemInDarkTheme(),
84 | // Dynamic color is available on Android 12+
85 | dynamicColor: Boolean = true,
86 | content: @Composable() () -> Unit
87 | ) {
88 | val colorScheme = when {
89 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
90 | val context = LocalContext.current
91 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
92 | }
93 |
94 | darkTheme -> DarkColorScheme
95 | else -> LightColorScheme
96 | }
97 |
98 | val systemUiController = rememberSystemUiController()
99 | if (darkTheme) {
100 | systemUiController.setSystemBarsColor(
101 | color = Color.Transparent
102 | )
103 | } else {
104 | systemUiController.setSystemBarsColor(
105 | color = Color.Transparent
106 | )
107 | systemUiController.setStatusBarColor(Color.Transparent, true)
108 | }
109 |
110 | MaterialTheme(
111 | colorScheme = colorScheme,
112 | //typography = Typography,
113 | content = content
114 | )
115 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/ink/duo3/fontconverter/ui/components/InputCard.kt:
--------------------------------------------------------------------------------
1 | package ink.duo3.fontconverter.ui.components
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.Row
7 | import androidx.compose.foundation.layout.Spacer
8 | import androidx.compose.foundation.layout.defaultMinSize
9 | import androidx.compose.foundation.layout.fillMaxWidth
10 | import androidx.compose.foundation.layout.height
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.shape.RoundedCornerShape
13 | import androidx.compose.foundation.shape.CircleShape
14 | import androidx.compose.foundation.text.BasicTextField
15 | import androidx.compose.foundation.text.selection.LocalTextSelectionColors
16 | import androidx.compose.foundation.text.selection.TextSelectionColors
17 | import androidx.compose.material.icons.Icons
18 | import androidx.compose.material.icons.rounded.Clear
19 | import androidx.compose.material3.MaterialTheme
20 | import androidx.compose.material3.Surface
21 | import androidx.compose.material3.Text
22 | import androidx.compose.runtime.Composable
23 | import androidx.compose.ui.Modifier
24 | import androidx.compose.ui.res.stringResource
25 | import androidx.compose.ui.tooling.preview.Preview
26 | import androidx.compose.ui.unit.dp
27 | import androidx.compose.material3.Icon
28 | import androidx.compose.runtime.CompositionLocalProvider
29 | import androidx.compose.ui.Alignment
30 | import androidx.compose.ui.graphics.Color
31 | import androidx.compose.ui.graphics.SolidColor
32 | import androidx.compose.ui.unit.Dp
33 | import ink.duo3.fontconverter.R
34 |
35 | @Composable
36 | fun InputCard (
37 | value: String,
38 | onValueChange: (newValue: String) -> Unit,
39 | onClick: () -> Unit,
40 | modifier: Modifier = Modifier
41 | ) {
42 | Surface(
43 | modifier = modifier,
44 | shape = RoundedCornerShape(16.dp),
45 | color = MaterialTheme.colorScheme.primary
46 | ) {
47 | Column() {
48 | Row(
49 | Modifier
50 | .padding(16.dp, 4.dp, 4.dp, 0.dp)
51 | .height(40.dp),
52 | verticalAlignment = Alignment.CenterVertically
53 | ) {
54 | Text(
55 | text = stringResource(id = R.string.font_original).uppercase(),
56 | style = MaterialTheme.typography.labelMedium,
57 | color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.75f),
58 | modifier = Modifier.padding(top = 0.dp)
59 | )
60 | Spacer(Modifier.weight(1f))
61 | if (value.isNotEmpty()) {
62 | Surface(
63 | modifier = Modifier,
64 | shape = CircleShape,
65 | color = Color.Transparent
66 | ) {
67 | Icon(
68 | modifier = Modifier
69 | .clickable(onClick = onClick, onClickLabel = "Clear")
70 | .padding(8.dp),
71 | imageVector = Icons.Rounded.Clear,
72 | contentDescription = "Clear",
73 | tint = MaterialTheme.colorScheme.onPrimary
74 | )
75 | }
76 | }
77 | }
78 |
79 | val customTextSelectionColors = TextSelectionColors(
80 | handleColor = MaterialTheme.colorScheme.primaryContainer,
81 | backgroundColor = MaterialTheme.colorScheme.onPrimary.copy(0.2f)
82 | )
83 |
84 | CompositionLocalProvider(
85 | LocalTextSelectionColors provides customTextSelectionColors
86 | ) {
87 | BasicTextField(
88 | modifier = Modifier
89 | .padding(16.dp, 0.dp, 16.dp, 16.dp)
90 | .defaultMinSize(minHeight = 128.dp)
91 | .fillMaxWidth(),
92 | value = value,
93 | onValueChange = onValueChange,
94 | singleLine = false,
95 | textStyle = MaterialTheme.typography.headlineMedium
96 | .copy(color = MaterialTheme.colorScheme.onPrimary),
97 | cursorBrush = SolidColor(MaterialTheme.colorScheme.onPrimary),
98 | decorationBox = {
99 | Box(
100 | modifier = Modifier
101 | .weight(1F),
102 | contentAlignment = Alignment.TopStart
103 | ) {
104 | it()
105 | if (value.isEmpty()) Text(
106 | stringResource(id = R.string.input_hint),
107 | style = MaterialTheme.typography.headlineMedium,
108 | color = MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.6f),
109 | )
110 | }
111 | }
112 | )
113 | }
114 | }
115 | }
116 | }
117 |
118 | @Preview
119 | @Composable
120 | fun InputCardPreview() {
121 | var input = ""
122 | InputCard(input, { input = it }, {})
123 | }
--------------------------------------------------------------------------------
/app/src/main/kotlin/ink/duo3/fontconverter/ui/components/StyleItem.kt:
--------------------------------------------------------------------------------
1 | package ink.duo3.fontconverter.ui.components
2 |
3 | import android.widget.Toast
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.Row
8 | import androidx.compose.foundation.layout.Spacer
9 | import androidx.compose.foundation.layout.defaultMinSize
10 | import androidx.compose.foundation.layout.fillMaxWidth
11 | import androidx.compose.foundation.layout.height
12 | import androidx.compose.foundation.layout.padding
13 | import androidx.compose.foundation.selection.selectable
14 | import androidx.compose.foundation.shape.CircleShape
15 | import androidx.compose.foundation.text.BasicTextField
16 | import androidx.compose.foundation.text.selection.SelectionContainer
17 | import androidx.compose.material.icons.Icons
18 | import androidx.compose.material.icons.rounded.Clear
19 | import androidx.compose.material3.Icon
20 | import androidx.compose.material3.MaterialTheme
21 | import androidx.compose.material3.Surface
22 | import androidx.compose.material3.Text
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.ui.Alignment
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.graphics.Color
27 | import androidx.compose.ui.graphics.SolidColor
28 | import androidx.compose.ui.platform.ClipboardManager
29 | import androidx.compose.ui.platform.LocalClipboardManager
30 | import androidx.compose.ui.platform.LocalContext
31 | import androidx.compose.ui.res.painterResource
32 | import androidx.compose.ui.res.stringResource
33 | import androidx.compose.ui.text.AnnotatedString
34 | import androidx.compose.ui.tooling.preview.Preview
35 | import androidx.compose.ui.unit.dp
36 | import ink.duo3.fontconverter.R
37 | import ink.duo3.fontconverter.utils.TextStyle
38 | import ink.duo3.fontconverter.utils.toName
39 | import ink.duo3.fontconverter.utils.toStyled
40 |
41 | @Composable
42 | fun StyleItem(text: String, style: TextStyle) {
43 | val styledText = text.toStyled(style)
44 | val styleName = style.toName()
45 | StyledItemDisplay(tag = styleName, text = styledText)
46 | }
47 |
48 | @Composable
49 | fun StyledItemDisplay (tag: String, text: String) {
50 | val context = LocalContext.current
51 | val clipboardManager: ClipboardManager = LocalClipboardManager.current
52 | val copiedToast = stringResource(id = R.string.toast_copied)
53 | Column(Modifier.padding(16.dp, 0.dp)) {
54 | Row(
55 | Modifier
56 | .padding(16.dp, 4.dp, 4.dp, 0.dp)
57 | .height(40.dp),
58 | verticalAlignment = Alignment.CenterVertically
59 | ) {
60 | Text(
61 | text = tag.uppercase(),
62 | style = MaterialTheme.typography.labelMedium,
63 | color = MaterialTheme.colorScheme.onSurfaceVariant,
64 | modifier = Modifier.padding(top = 0.dp)
65 | )
66 | Spacer(Modifier.weight(1f))
67 |
68 | Surface(
69 | modifier = Modifier,
70 | shape = CircleShape,
71 | color = Color.Transparent
72 | ) {
73 | Icon(
74 | modifier = if (text.isNotEmpty()) {
75 | Modifier
76 | .clickable(
77 | onClick = {
78 | clipboardManager
79 | .setText(AnnotatedString(text))
80 | .also {
81 | Toast
82 | .makeText(
83 | context,
84 | copiedToast,
85 | Toast.LENGTH_SHORT
86 | )
87 | .show()
88 | }
89 | },
90 | onClickLabel = "Copy"
91 | )
92 | .padding(8.dp)
93 | } else {
94 | Modifier
95 | .padding(8.dp)
96 | },
97 | painter = painterResource(id = R.drawable.ic_content_copy_24dp),
98 | contentDescription = "Copy",
99 | tint = if (text.isNotEmpty()) {
100 | MaterialTheme.colorScheme.onBackground
101 | } else {
102 | MaterialTheme.colorScheme.onBackground.copy(alpha = 0.4f)
103 | }
104 | )
105 | }
106 |
107 | }
108 | // if (styledText.isNotEmpty()) {
109 | SelectionContainer {
110 | Text(
111 | modifier = Modifier
112 | .padding(16.dp, 0.dp, 16.dp, 16.dp)
113 | .fillMaxWidth(),
114 | text = text,
115 | style = MaterialTheme.typography.headlineMedium
116 | )
117 | }
118 | // } else {
119 | // Text(
120 | // modifier = Modifier
121 | // .padding(16.dp, 0.dp, 16.dp, 16.dp)
122 | // .fillMaxWidth(),
123 | // text = stringResource(id = R.string.styled_hint).toStyled(style),
124 | // style = MaterialTheme.typography.headlineMedium,
125 | // color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
126 | // )
127 | // }
128 | }
129 | }
130 |
131 |
132 | @Preview
133 | @Composable
134 | fun StyleItemPreview() {
135 | StyleItem(text = "Hello World 123", style = TextStyle.ITALIC)
136 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/ink/duo3/fontconverter/utils/StyleText.kt:
--------------------------------------------------------------------------------
1 | package ink.duo3.fontconverter.utils
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.res.stringResource
5 | import ink.duo3.fontconverter.R
6 |
7 | fun String.toStyled(style: TextStyle): String {
8 | val sb = StringBuilder()
9 | for (c in this) {
10 | sb.append(
11 | when (style) {
12 | TextStyle.BOLD -> c.toBold()
13 | TextStyle.ITALIC -> c.toItalic()
14 | TextStyle.BOLD_ITALIC -> c.toBoldItalic()
15 | TextStyle.SANS_NORMAL -> c.toSansNormal()
16 | TextStyle.SANS_BOLD -> c.toSansBold()
17 | TextStyle.SANS_ITALIC -> c.toSansItalic()
18 | TextStyle.SANS_BOLD_ITALIC -> c.toSansBoldItalic()
19 | TextStyle.SCRIPT_NORMAL -> c.toScriptNormal()
20 | TextStyle.SCRIPT_BOLD -> c.toScriptBold()
21 | TextStyle.FRAKTUR_NORMAL -> c.toFrakturNormal()
22 | TextStyle.FRAKTUR_BOLD -> c.toFrakturBold()
23 | TextStyle.MONOSPACE_NORMAL -> c.toMonospaceNormal()
24 | TextStyle.DOUBLESTRUCK_BOLD -> c.toDoublestruckBold()
25 | }
26 | )
27 | }
28 | return sb.toString()
29 | }
30 |
31 | enum class TextStyle {
32 | BOLD,
33 | ITALIC,
34 | BOLD_ITALIC,
35 | SANS_NORMAL,
36 | SANS_BOLD,
37 | SANS_ITALIC,
38 | SANS_BOLD_ITALIC,
39 | SCRIPT_NORMAL,
40 | SCRIPT_BOLD,
41 | FRAKTUR_NORMAL,
42 | FRAKTUR_BOLD,
43 | MONOSPACE_NORMAL,
44 | DOUBLESTRUCK_BOLD
45 | }
46 |
47 | @Composable
48 | fun TextStyle.toName(): String {
49 | return when (this) {
50 | TextStyle.BOLD -> stringResource(id = R.string.font_bold)
51 | TextStyle.ITALIC -> stringResource(id = R.string.font_italic)
52 | TextStyle.BOLD_ITALIC -> stringResource(id = R.string.font_bold_italic)
53 | TextStyle.SANS_NORMAL -> stringResource(id = R.string.font_sans_normal)
54 | TextStyle.SANS_BOLD -> stringResource(id = R.string.font_sans_bold)
55 | TextStyle.SANS_ITALIC -> stringResource(id = R.string.font_sans_italic)
56 | TextStyle.SANS_BOLD_ITALIC -> stringResource(id = R.string.font_sans_bold_italic)
57 | TextStyle.SCRIPT_NORMAL -> stringResource(id = R.string.font_script_normal)
58 | TextStyle.SCRIPT_BOLD -> stringResource(id = R.string.font_script_bold)
59 | TextStyle.FRAKTUR_NORMAL -> stringResource(id = R.string.font_fraktur_normal)
60 | TextStyle.FRAKTUR_BOLD -> stringResource(id = R.string.font_fraktur_bold)
61 | TextStyle.MONOSPACE_NORMAL -> stringResource(id = R.string.font_monospace_normal)
62 | TextStyle.DOUBLESTRUCK_BOLD -> stringResource(id = R.string.font_doublestruck_bold)
63 | }
64 | }
65 |
66 | fun Char.toBold(): String {
67 | return when (this) {
68 | in 'A'..'Z' -> "\uD835${(this.code - 'A'.code + 0xDC00).toChar()}"
69 | in 'a'..'z' -> "\uD835${(this.code - 'a'.code + 0xDC1A).toChar()}"
70 | in '0'..'9' -> "\uD835${(this.code - '0'.code + 0xDFCE).toChar()}"
71 | // Capitals of Greek Alphabet
72 | 'ϴ' -> "\uD835\uDEB9"
73 | in 'Α'..'Ω' -> "\uD835${(this.code - 'Α'.code + 0xDEA8).toChar()}"
74 | '∇' -> "\uD835\uDEC1"
75 | // Lowercase of Greek Alphabet
76 | in 'α'..'ω' -> "\uD835${(this.code - 'α'.code + 0xDEC2).toChar()}"
77 | '∂' -> "\uD835\uDEDB"
78 | 'ϵ' -> "\uD835\uDEDC"
79 | 'ϑ' -> "\uD835\uDEDD"
80 | 'ϰ' -> "\uD835\uDEDE"
81 | 'ϕ' -> "\uD835\uDEDF"
82 | 'ϱ' -> "\uD835\uDEE0"
83 | 'ϖ' -> "\uD835\uDEE1"
84 |
85 | 'Ϝ' -> "\uD835\uDFCA"
86 | 'ϝ' -> "\uD835\uDFCB"
87 |
88 | else -> this.toString()
89 | }
90 | }
91 |
92 | fun Char.toItalic(): String {
93 | return when (this) {
94 | 'h' -> "\u210E"
95 | in 'A'..'Z' -> "\uD835${(this.code - 'A'.code + 0xDC34).toChar()}"
96 | in 'a'..'z' -> "\uD835${(this.code - 'a'.code + 0xDC4E).toChar()}"
97 | // Capitals of Greek Alphabet
98 | 'ϴ' -> "\uD835\uDEF3"
99 | in 'Α'..'Ω' -> "\uD835${(this.code - 'Α'.code + 0xDEE2).toChar()}"
100 | '∇' -> "\uD835\uDEFB"
101 | // Lowercase of Greek Alphabet
102 | in 'α'..'ω' -> "\uD835${(this.code - 'α'.code + 0xDEFC).toChar()}"
103 | '∂' -> "\uD835\uDF15"
104 | 'ϵ' -> "\uD835\uDF16"
105 | 'ϑ' -> "\uD835\uDF17"
106 | 'ϰ' -> "\uD835\uDF18"
107 | 'ϕ' -> "\uD835\uDF19"
108 | 'ϱ' -> "\uD835\uDF1A"
109 | 'ϖ' -> "\uD835\uDF1B"
110 | else -> this.toString()
111 | }
112 | }
113 |
114 | fun Char.toBoldItalic(): String {
115 | return when (this) {
116 | in 'A'..'Z' -> "\uD835${(this.code - 'A'.code + 0xDC68).toChar()}"
117 | in 'a'..'z' -> "\uD835${(this.code - 'a'.code + 0xDC82).toChar()}"
118 | // Capitals of Greek Alphabet
119 | 'ϴ' -> "\uD835\uDF2D"
120 | in 'Α'..'Ω' -> "\uD835${(this.code - 'Α'.code + 0xDF1C).toChar()}"
121 | '∇' -> "\uD835\uDF35"
122 | // Lowercase of Greek Alphabet
123 | in 'α'..'ω' -> "\uD835${(this.code - 'α'.code + 0xDF36).toChar()}"
124 | '∂' -> "\uD835\uDF4F"
125 | 'ϵ' -> "\uD835\uDF50"
126 | 'ϑ' -> "\uD835\uDF51"
127 | 'ϰ' -> "\uD835\uDF52"
128 | 'ϕ' -> "\uD835\uDF53"
129 | 'ϱ' -> "\uD835\uDF54"
130 | 'ϖ' -> "\uD835\uDF55"
131 | else -> this.toString()
132 | }
133 | }
134 |
135 | fun Char.toSansNormal(): String {
136 | return when (this) {
137 | in 'A'..'Z' -> "\uD835${(this.code - 'A'.code + 0xDDA0).toChar()}"
138 | in 'a'..'z' -> "\uD835${(this.code - 'a'.code + 0xDDBA).toChar()}"
139 | in '0'..'9' -> "\uD835${(this.code - '0'.code + 0xDFE2).toChar()}"
140 | else -> this.toString()
141 | }
142 | }
143 | fun Char.toSansBold(): String {
144 | return when (this) {
145 | in 'A'..'Z' -> "\uD835${(this.code - 'A'.code + 0xDDD4).toChar()}"
146 | in 'a'..'z' -> "\uD835${(this.code - 'a'.code + 0xDDEE).toChar()}"
147 | in '0'..'9' -> "\uD835${(this.code - '0'.code + 0xDFEC).toChar()}"
148 | // Capitals of Greek Alphabet
149 | 'ϴ' -> "\uD835\uDF67"
150 | in 'Α'..'Ω' -> "\uD835${(this.code - 'Α'.code + 0xDF56).toChar()}"
151 | '∇' -> "\uD835\uDF6F"
152 | // Lowercase of Greek Alphabet
153 | in 'α'..'ω' -> "\uD835${(this.code - 'α'.code + 0xDF70).toChar()}"
154 | '∂' -> "\uD835\uDF89"
155 | 'ϵ' -> "\uD835\uDF8A"
156 | 'ϑ' -> "\uD835\uDF8B"
157 | 'ϰ' -> "\uD835\uDF8C"
158 | 'ϕ' -> "\uD835\uDF8D"
159 | 'ϱ' -> "\uD835\uDF8E"
160 | 'ϖ' -> "\uD835\uDF8F"
161 |
162 | else -> this.toString()
163 | }
164 | }
165 | fun Char.toSansItalic(): String {
166 | return when (this) {
167 | in 'A'..'Z' -> "\uD835${(this.code - 'A'.code + 0xDE08).toChar()}"
168 | in 'a'..'z' -> "\uD835${(this.code - 'a'.code + 0xDE22).toChar()}"
169 | else -> this.toString()
170 | }
171 | }
172 | fun Char.toSansBoldItalic(): String {
173 | return when (this) {
174 | in 'A'..'Z' -> "\uD835${(this.code - 'A'.code + 0xDE3C).toChar()}"
175 | in 'a'..'z' -> "\uD835${(this.code - 'a'.code + 0xDE56).toChar()}"
176 | // Capitals of Greek Alphabet
177 | 'ϴ' -> "\uD835\uDFA1"
178 | in 'Α'..'Ω' -> "\uD835${(this.code - 'Α'.code + 0xDF90).toChar()}"
179 | '∇' -> "\uD835\uDFA9"
180 | // Lowercase of Greek Alphabet
181 | in 'α'..'ω' -> "\uD835${(this.code - 'α'.code + 0xDFAA).toChar()}"
182 | '∂' -> "\uD835\uDFC3"
183 | 'ϵ' -> "\uD835\uDFC4"
184 | 'ϑ' -> "\uD835\uDFC5"
185 | 'ϰ' -> "\uD835\uDFC6"
186 | 'ϕ' -> "\uD835\uDFC7"
187 | 'ϱ' -> "\uD835\uDFC8"
188 | 'ϖ' -> "\uD835\uDFC9"
189 |
190 | else -> this.toString()
191 | }
192 | }
193 |
194 | fun Char.toScriptNormal(): String {
195 | return when (this) {
196 | 'B' -> "\u212C"
197 | 'E' -> "\u2130"
198 | 'F' -> "\u2131"
199 | 'H' -> "\u210B"
200 | 'I' -> "\u2110"
201 | 'L' -> "\u2112"
202 | 'M' -> "\u2133"
203 | 'R' -> "\u211B"
204 | in 'A'..'Z' -> "\uD835${(this.code - 'A'.code + 0xDC9C).toChar()}"
205 | 'e' -> "\u212F"
206 | 'g' -> "\u210A"
207 | 'o' -> "\u2134"
208 | in 'a'..'z' -> "\uD835${(this.code - 'a'.code + 0xDCB6).toChar()}"
209 | else -> this.toString()
210 | }
211 | }
212 |
213 | fun Char.toScriptBold(): String {
214 | return when (this) {
215 | in 'A'..'Z' -> "\uD835${(this.code - 'A'.code + 0xDCD0).toChar()}"
216 | in 'a'..'z' -> "\uD835${(this.code - 'a'.code + 0xDCEA).toChar()}"
217 | else -> this.toString()
218 | }
219 | }
220 |
221 | fun Char.toFrakturNormal(): String {
222 | return when (this) {
223 | 'C' -> "\u212D"
224 | 'H' -> "\u210C"
225 | 'I' -> "\u2111"
226 | 'R' -> "\u211C"
227 | 'Z' -> "\u2128"
228 | in 'A'..'Z' -> "\uD835${(this.code - 'A'.code + 0xDD04).toChar()}"
229 | in 'a'..'z' -> "\uD835${(this.code - 'a'.code + 0xDD1E).toChar()}"
230 | else -> this.toString()
231 | }
232 | }
233 |
234 | fun Char.toFrakturBold(): String {
235 | return when (this) {
236 | in 'A'..'Z' -> "\uD835${(this.code - 'A'.code + 0xDD6C).toChar()}"
237 | in 'a'..'z' -> "\uD835${(this.code - 'a'.code + 0xDD86).toChar()}"
238 | else -> this.toString()
239 | }
240 | }
241 |
242 | fun Char.toMonospaceNormal(): String {
243 | return when (this) {
244 | in 'A'..'Z' -> "\uD835${(this.code - 'A'.code + 0xDE70).toChar()}"
245 | in 'a'..'z' -> "\uD835${(this.code - 'a'.code + 0xDE8A).toChar()}"
246 | in '0'..'9' -> "\uD835${(this.code - '0'.code + 0xDFF6).toChar()}"
247 | else -> this.toString()
248 | }
249 | }
250 |
251 | fun Char.toDoublestruckBold(): String {
252 | return when (this) {
253 | 'C' -> "\u2102"
254 | 'H' -> "\u210D"
255 | 'N' -> "\u2115"
256 | 'P' -> "\u2119"
257 | 'Q' -> "\u211A"
258 | 'R' -> "\u211D"
259 | 'Z' -> "\u2124"
260 | in 'A'..'Z' -> "\uD835${(this.code - 'A'.code + 0xDD38).toChar()}"
261 | in 'a'..'z' -> "\uD835${(this.code - 'a'.code + 0xDD52).toChar()}"
262 | in '0'..'9' -> "\uD835${(this.code - '0'.code + 0xDFD8).toChar()}"
263 | else -> this.toString()
264 | }
265 | }
266 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/app/src/main/kotlin/ink/duo3/fontconverter/FontConverterApp.kt:
--------------------------------------------------------------------------------
1 | package ink.duo3.fontconverter
2 |
3 | import android.app.Activity
4 | import android.content.ClipData
5 | import android.content.Context
6 | import android.content.pm.PackageInfo
7 | import android.content.pm.PackageManager
8 | import android.os.Build
9 | import android.util.Log
10 | import android.widget.Toast
11 | import androidx.compose.foundation.ScrollState
12 | import androidx.compose.foundation.clickable
13 | import androidx.compose.foundation.gestures.Orientation
14 | import androidx.compose.foundation.gestures.scrollable
15 | import androidx.compose.foundation.layout.Box
16 | import androidx.compose.foundation.layout.Column
17 | import androidx.compose.foundation.layout.Row
18 | import androidx.compose.foundation.layout.Spacer
19 | import androidx.compose.foundation.layout.WindowInsets
20 | import androidx.compose.foundation.layout.asPaddingValues
21 | import androidx.compose.foundation.layout.calculateEndPadding
22 | import androidx.compose.foundation.layout.calculateStartPadding
23 | import androidx.compose.foundation.layout.defaultMinSize
24 | import androidx.compose.foundation.layout.fillMaxHeight
25 | import androidx.compose.foundation.layout.fillMaxSize
26 | import androidx.compose.foundation.layout.fillMaxWidth
27 | import androidx.compose.foundation.layout.height
28 | import androidx.compose.foundation.layout.heightIn
29 | import androidx.compose.foundation.layout.padding
30 | import androidx.compose.foundation.layout.size
31 | import androidx.compose.foundation.layout.systemBars
32 | import androidx.compose.foundation.layout.width
33 | import androidx.compose.foundation.layout.wrapContentHeight
34 | import androidx.compose.foundation.layout.wrapContentSize
35 | import androidx.compose.foundation.layout.wrapContentWidth
36 | import androidx.compose.foundation.rememberScrollState
37 | import androidx.compose.foundation.shape.CircleShape
38 | import androidx.compose.foundation.shape.RoundedCornerShape
39 | import androidx.compose.foundation.text.ClickableText
40 | import androidx.compose.foundation.verticalScroll
41 | import androidx.compose.material.icons.Icons
42 | import androidx.compose.material.icons.outlined.Info
43 | import androidx.compose.material.icons.rounded.Info
44 | import androidx.compose.material3.AlertDialog
45 | import androidx.compose.material3.AlertDialogDefaults
46 | import androidx.compose.material3.Divider
47 | import androidx.compose.material3.ExperimentalMaterial3Api
48 | import androidx.compose.material3.Icon
49 | import androidx.compose.material3.MaterialTheme
50 | import androidx.compose.material3.Surface
51 | import androidx.compose.material3.Text
52 | import androidx.compose.material3.TextButton
53 | import androidx.compose.runtime.Composable
54 | import androidx.compose.runtime.MutableState
55 | import androidx.compose.runtime.getValue
56 | import androidx.compose.runtime.mutableStateOf
57 | import androidx.compose.runtime.remember
58 | import androidx.compose.runtime.setValue
59 | import androidx.compose.ui.Alignment
60 | import androidx.compose.ui.Modifier
61 | import androidx.compose.ui.graphics.Color
62 | import androidx.compose.ui.platform.LocalConfiguration
63 | import androidx.compose.ui.platform.LocalContext
64 | import androidx.compose.ui.platform.LocalLayoutDirection
65 | import androidx.compose.ui.res.stringResource
66 | import androidx.compose.ui.text.SpanStyle
67 | import androidx.compose.ui.text.buildAnnotatedString
68 | import androidx.compose.ui.text.font.FontWeight
69 | import androidx.compose.ui.text.withStyle
70 | import androidx.compose.ui.unit.dp
71 | import androidx.core.content.pm.PackageInfoCompat
72 | import ink.duo3.fontconverter.ui.components.AppBanner
73 | import ink.duo3.fontconverter.ui.components.AppIcon
74 | import ink.duo3.fontconverter.ui.components.InputCard
75 | import ink.duo3.fontconverter.ui.components.SomethingMystical
76 | import ink.duo3.fontconverter.ui.components.StyleItem
77 | import ink.duo3.fontconverter.utils.TextStyle
78 | import ink.duo3.fontconverter.utils.getAppVersion
79 |
80 | @Composable
81 | fun FontConverterApp () {
82 | val context = LocalContext.current
83 | val easterEggToast = stringResource(id = R.string.toast_easter_egg)
84 | var clickCount = remember { mutableStateOf(0) }
85 | val openDialog = remember { mutableStateOf(false) }
86 | val onClick = {
87 | if (clickCount.value < 7) {
88 | clickCount.value += 1
89 | }
90 | }
91 | val showEasterEgg = (clickCount.value >= 7)
92 | if (showEasterEgg) {
93 | Toast.makeText(
94 | context,
95 | easterEggToast,
96 | Toast.LENGTH_SHORT
97 | ).show()
98 | }
99 |
100 | Surface(
101 | modifier = Modifier.fillMaxSize(),
102 | color = MaterialTheme.colorScheme.background
103 | ) {
104 | AboutDialog(openDialog = openDialog, onClick = onClick, showEasterEgg)
105 | if (LocalConfiguration.current.screenWidthDp.dp < 600.dp) {
106 | PortraitLayout(openDialog = openDialog, showEasterEgg)
107 | } else {
108 | LandscapeLayout(openDialog = openDialog, showEasterEgg)
109 | }
110 | }
111 | }
112 |
113 | @Composable
114 | fun PortraitLayout(openDialog: MutableState, showEasterEgg: Boolean) {
115 | var input by remember { mutableStateOf("") }
116 | val scrollState = rememberScrollState()
117 | val bottomPadding = if(
118 | WindowInsets.systemBars.asPaddingValues().calculateBottomPadding() == 0.dp
119 | ) {
120 | 16.dp
121 | } else {
122 | WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
123 | }
124 | Column(
125 | Modifier
126 | .fillMaxSize()
127 | .padding(
128 | start = WindowInsets.systemBars
129 | .asPaddingValues()
130 | .calculateStartPadding(LocalLayoutDirection.current),
131 | top = WindowInsets.systemBars
132 | .asPaddingValues()
133 | .calculateTopPadding(),
134 | bottom = 0.dp,
135 | end = WindowInsets.systemBars
136 | .asPaddingValues()
137 | .calculateEndPadding(LocalLayoutDirection.current)
138 | )
139 | ) {
140 | AppHeader(openDialog = openDialog)
141 | Column(
142 | Modifier
143 | .padding(16.dp, 16.dp, 16.dp, 0.dp)
144 | .heightIn(max = LocalConfiguration.current.screenHeightDp.dp / 2)
145 | ) {
146 | InputCard(value = input, onValueChange = {input = it}, onClick = { input = ""})
147 | }
148 | Column(Modifier.verticalScroll(scrollState)) {
149 | Spacer(modifier = Modifier.height(8.dp))
150 |
151 | ResultDisplay(input = input, showEasterEgg)
152 |
153 | Spacer(
154 | modifier = Modifier.height(bottomPadding)
155 | )
156 | }
157 | }
158 | }
159 |
160 | @Composable
161 | fun LandscapeLayout(openDialog: MutableState, showEasterEgg: Boolean) {
162 | var input by remember { mutableStateOf("") }
163 | val scrollState = rememberScrollState()
164 | val bottomPadding = if(
165 | WindowInsets.systemBars.asPaddingValues().calculateBottomPadding() == 0.dp
166 | ) {
167 | 16.dp
168 | } else {
169 | WindowInsets.systemBars.asPaddingValues().calculateBottomPadding()
170 | }
171 | val inputCardModifier = if (LocalConfiguration.current.screenHeightDp.dp < 480.dp) {
172 | Modifier
173 | .fillMaxHeight()
174 | .padding(bottom = bottomPadding)
175 | } else if (LocalConfiguration.current.screenHeightDp.dp < 900.dp) {
176 | Modifier.defaultMinSize(minHeight = LocalConfiguration.current.screenHeightDp.dp / 2)
177 | } else {
178 | Modifier.defaultMinSize(minHeight = LocalConfiguration.current.screenHeightDp.dp / 3)
179 | }
180 |
181 | Row(
182 | Modifier
183 | .fillMaxSize()
184 | .padding(
185 | start = WindowInsets.systemBars
186 | .asPaddingValues()
187 | .calculateStartPadding(LocalLayoutDirection.current),
188 | top = WindowInsets.systemBars
189 | .asPaddingValues()
190 | .calculateTopPadding(),
191 | bottom = 0.dp,
192 | end = WindowInsets.systemBars
193 | .asPaddingValues()
194 | .calculateEndPadding(LocalLayoutDirection.current)
195 | )
196 | ) {
197 | Column(Modifier.weight(0.5f)) {
198 | AppHeader(openDialog = openDialog)
199 | Column(
200 | Modifier
201 | .padding(16.dp, 16.dp, 16.dp, 0.dp)
202 | ) {
203 | InputCard(
204 | value = input,
205 | onValueChange = {input = it},
206 | onClick = { input = ""},
207 | modifier = inputCardModifier
208 | )
209 | }
210 | }
211 | Spacer(modifier = Modifier.width(8.dp))
212 | Column(
213 | Modifier
214 | .weight(0.5f)
215 | .verticalScroll(scrollState)) {
216 | ResultDisplay(input = input, showEasterEgg)
217 | Spacer(modifier = Modifier.height(bottomPadding))
218 | }
219 | }
220 | }
221 |
222 | @Composable
223 | fun AppHeader(openDialog: MutableState) {
224 | Row(
225 | modifier = Modifier.padding(16.dp, 0.dp),
226 | verticalAlignment = Alignment.CenterVertically
227 | ) {
228 | AppBanner()
229 | Spacer(modifier = Modifier.weight(1f))
230 | Surface(
231 | modifier = Modifier,
232 | shape = CircleShape,
233 | color = Color.Transparent
234 | ){
235 | Icon(
236 | modifier = Modifier
237 | .clickable(onClickLabel = "About") { openDialog.value = true }
238 | .padding(16.dp),
239 | imageVector = Icons.Outlined.Info,
240 | contentDescription = "About",
241 | tint = MaterialTheme.colorScheme.onBackground
242 | )
243 | }
244 | }
245 | }
246 |
247 | @Composable
248 | fun ResultDisplay(input: String, showEasterEgg: Boolean) {
249 | Column {
250 | TextStyle.values().forEachIndexed { index, style ->
251 | StyleItem(text = input, style = style)
252 | if (index != TextStyle.values().lastIndex) {
253 | Divider()
254 | }
255 | }
256 | if (showEasterEgg) {
257 | Divider()
258 | SomethingMystical(text = input)
259 | }
260 | }
261 | }
262 |
263 | @OptIn(ExperimentalMaterial3Api::class)
264 | @Composable
265 | fun AboutDialog (openDialog: MutableState, onClick: () -> Unit, showEasterEgg: Boolean) {
266 | val context = LocalContext.current
267 | val versionName = getAppVersion(context)?.versionName
268 | if (openDialog.value) {
269 | AlertDialog(
270 | onDismissRequest = {
271 | // Dismiss the dialog when the user clicks outside the dialog or on the back
272 | // button. If you want to disable that functionality, simply use an empty
273 | // onDismissRequest.
274 | openDialog.value = false
275 | }
276 | ) {
277 | Surface(
278 | modifier = Modifier
279 | .wrapContentWidth()
280 | .wrapContentHeight(),
281 | shape = MaterialTheme.shapes.large,
282 | tonalElevation = AlertDialogDefaults.TonalElevation
283 | ) {
284 | Column(modifier = Modifier.padding(16.dp)) {
285 | Row(verticalAlignment = Alignment.CenterVertically) {
286 | AppIcon(48.dp)
287 | Spacer(modifier = Modifier.width(12.dp))
288 | Column {
289 | Text(
290 | modifier = Modifier.padding(4.dp, 0.dp),
291 | text = stringResource(id = R.string.app_name_full),
292 | style = MaterialTheme.typography.titleLarge
293 | )
294 | Surface(
295 | Modifier.wrapContentSize(),
296 | shape = RoundedCornerShape(2.dp)
297 | ) {
298 | Text(
299 | text = stringResource(id = R.string.version)
300 | .format(
301 | (versionName ?: stringResource(id = R.string.unknown))
302 | ),
303 | modifier = Modifier
304 | .clickable(onClick = onClick, enabled = !showEasterEgg)
305 | .padding(4.dp, 0.dp)
306 | )
307 | }
308 | }
309 | Spacer(modifier = Modifier.width(16.dp))
310 | }
311 | }
312 | }
313 | }
314 | }
315 | }
316 |
--------------------------------------------------------------------------------