diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a45d4c125..e35e9f8a5 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -272,6 +272,8 @@ dependencies {
implementation(libs.arthenica.smartexceptions)
// seeker seek bar
implementation(libs.seeker)
+ // true type parser
+ implementation(libs.truetypeparser)
}
androidComponents {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt
index 056205bad..49c30debc 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt
@@ -13,6 +13,7 @@ import android.media.AudioManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
+import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.os.ParcelFileDescriptor
@@ -573,6 +574,21 @@ class PlayerActivity : BaseActivity() {
MPVLib.setPropertyDouble("sub-delay", subtitlesDelay().get() / 1000.0)
}
+ MPVLib.setPropertyString(
+ "sub-fonts-dir",
+ File(
+ Environment.getExternalStorageDirectory().absolutePath + File.separator +
+ getString(R.string.app_name),
+ "fonts",
+ ).path,
+ )
+
+ if (playerPreferences.subtitleFont().get().trim() != "") {
+ MPVLib.setPropertyString("sub-font", playerPreferences.subtitleFont().get())
+ } else {
+ MPVLib.setPropertyString("sub-font", "Sans Serif")
+ }
+
MPVLib.setPropertyString("sub-bold", if (boldSubtitles().get()) "yes" else "no")
MPVLib.setPropertyString("sub-italic", if (italicSubtitles().get()) "yes" else "no")
MPVLib.setPropertyInt("sub-font-size", subtitleFontSize().get())
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/PlayerPreferences.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/PlayerPreferences.kt
index ee9b9961c..f9eb2e1fa 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/PlayerPreferences.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/PlayerPreferences.kt
@@ -76,6 +76,8 @@ class PlayerPreferences(
fun overrideSubsASS() = preferenceStore.getBoolean("pref_override_subtitles_ass", false)
+ fun subtitleFont() = preferenceStore.getString("pref_subtitle_font", "Sans Serif")
+
fun subtitleFontSize() = preferenceStore.getInt("pref_subtitles_font_size", 55)
fun boldSubtitles() = preferenceStore.getBoolean("pref_bold_subtitles", false)
fun italicSubtitles() = preferenceStore.getBoolean("pref_italic_subtitles", false)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/subtitle/SubtitleColorPage.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/subtitle/SubtitleColorPage.kt
index 386780913..90c5679dd 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/subtitle/SubtitleColorPage.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/subtitle/SubtitleColorPage.kt
@@ -66,6 +66,8 @@ private fun SubtitleColors(
val borderColorPref = screenModel.preferences.borderColorSubtitles()
val backgroundColorPref = screenModel.preferences.backgroundColorSubtitles()
+ val font by screenModel.preferences.subtitleFont().collectAsState()
+
Row(horizontalArrangement = Arrangement.SpaceEvenly, modifier = Modifier.fillMaxWidth()) {
SubtitleColorSelector(
label = R.string.player_subtitle_text_color,
@@ -89,6 +91,7 @@ private fun SubtitleColors(
Column(horizontalAlignment = Alignment.CenterHorizontally) {
SubtitlePreview(
+ font = font,
isBold = screenModel.preferences.boldSubtitles().collectAsState().value,
isItalic = screenModel.preferences.italicSubtitles().collectAsState().value,
textColor = Color(textColorPref.collectAsState().value),
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/subtitle/SubtitleFontPage.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/subtitle/SubtitleFontPage.kt
index 5fadf519b..a2e75965f 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/subtitle/SubtitleFontPage.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/subtitle/SubtitleFontPage.kt
@@ -1,26 +1,37 @@
package eu.kanade.tachiyomi.ui.player.settings.sheets.subtitle
+import android.os.Environment
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
-import androidx.compose.material.SnackbarDefaults.backgroundColor
import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.outlined.FormatBold
import androidx.compose.material.icons.outlined.FormatItalic
import androidx.compose.material.icons.outlined.FormatSize
+import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import com.yubyf.truetypeparser.TTFFile
+import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.components.OutlinedNumericChooser
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
@@ -29,6 +40,7 @@ import eu.kanade.tachiyomi.ui.player.settings.PlayerSettingsScreenModel
import `is`.xyz.mpv.MPVLib
import tachiyomi.presentation.core.components.material.ReadItemAlpha
import tachiyomi.presentation.core.components.material.padding
+import java.io.File
@Composable
fun SubtitleFontPage(screenModel: PlayerSettingsScreenModel) {
@@ -41,6 +53,7 @@ fun SubtitleFontPage(screenModel: PlayerSettingsScreenModel) {
private fun SubtitleFont(
screenModel: PlayerSettingsScreenModel,
) {
+ val font by screenModel.preferences.subtitleFont().collectAsState()
val boldSubtitles by screenModel.preferences.boldSubtitles().collectAsState()
val italicSubtitles by screenModel.preferences.italicSubtitles().collectAsState()
val subtitleFontSize by screenModel.preferences.subtitleFontSize().collectAsState()
@@ -65,6 +78,30 @@ private fun SubtitleFont(
screenModel.preferences.subtitleFontSize().set(it)
}
+ val updateFont: (String) -> Unit = {
+ MPVLib.setPropertyString("sub-font", it)
+ screenModel.preferences.subtitleFont().set(it)
+ }
+
+ val context = LocalContext.current
+ val fontList by remember {
+ derivedStateOf {
+ val customFonts = File(
+ Environment.getExternalStorageDirectory().absolutePath +
+ File.separator + context.getString(R.string.app_name) +
+ File.separator,
+ "fonts",
+ ).listFiles { file ->
+ file.extension.equals("ttf", true) ||
+ file.extension.equals("otf", true)
+ }?.map {
+ TTFFile.open(it).families.values.toTypedArray()[0] to it.absolutePath
+ } ?: emptyList()
+ listOf("Sans Serif" to "") + customFonts
+ }
+ }
+ var selectingFont by remember { mutableStateOf(false) }
+
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.tiny),
@@ -74,11 +111,13 @@ private fun SubtitleFont(
horizontalArrangement = Arrangement.SpaceEvenly,
modifier = Modifier.fillMaxWidth(),
) {
- Icon(
- imageVector = Icons.Outlined.FormatSize,
- contentDescription = null,
- modifier = Modifier.size(32.dp),
- )
+ IconButton(onClick = { selectingFont = true }) {
+ Icon(
+ imageVector = Icons.Outlined.FormatSize,
+ contentDescription = null,
+ modifier = Modifier.size(32.dp),
+ )
+ }
OutlinedNumericChooser(
label = stringResource(id = R.string.player_font_size_text_field),
@@ -111,7 +150,26 @@ private fun SubtitleFont(
)
}
+ DropdownMenu(expanded = selectingFont, onDismissRequest = { selectingFont = false }) {
+ fontList.map {
+ val fontName = it.first
+ DropdownMenuItem(
+ text = { Text(fontName) },
+ onClick = { updateFont(fontName) },
+ trailingIcon = {
+ if (font == fontName) {
+ Icon(
+ imageVector = Icons.Default.Check,
+ contentDescription = null,
+ )
+ }
+ },
+ )
+ }
+ }
+
SubtitlePreview(
+ font = font,
isBold = boldSubtitles,
isItalic = italicSubtitles,
textColor = Color(textColor),
diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/subtitle/SubtitleSettingsSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/subtitle/SubtitleSettingsSheet.kt
index c2f6cb4f8..1424d8adb 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/subtitle/SubtitleSettingsSheet.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/subtitle/SubtitleSettingsSheet.kt
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.player.settings.sheets.subtitle
import android.graphics.Rect
import android.graphics.Typeface
import android.os.Build
+import android.os.Environment
import androidx.annotation.RequiresApi
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
@@ -25,6 +26,7 @@ import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
@@ -32,11 +34,13 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
+import com.yubyf.truetypeparser.TTFFile
import eu.kanade.presentation.components.TabbedDialog
import eu.kanade.presentation.components.TabbedDialogPaddings
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.player.settings.PlayerSettingsScreenModel
import tachiyomi.presentation.core.components.material.padding
+import java.io.File
@Composable
fun SubtitleSettingsSheet(
@@ -70,41 +74,38 @@ fun SubtitleSettingsSheet(
@Composable
fun OutLineText(
text: String,
+ font: Typeface,
outlineColor: Color = Color.Black,
textColor: Color = Color.White,
- backgroundColor: Color = Color.Black,
isBold: Boolean = false,
isItalic: Boolean = false,
+ backgroundColor: Color = Color.Black,
) {
val textPaintStroke = Paint().asFrameworkPaint().apply {
- typeface = Typeface.create(
- Typeface.SANS_SERIF,
- if (isBold) FontWeight.Bold.weight else FontWeight.Normal.weight,
- isItalic,
- )
+ typeface = font
isAntiAlias = true
style = android.graphics.Paint.Style.STROKE
- textSize = 16f
+ textSize = 48f
color = outlineColor.toArgb()
- strokeWidth = 2f
- strokeMiter = 2f
+ strokeWidth = 12f
+ strokeMiter = 8f
strokeJoin = android.graphics.Paint.Join.ROUND
// change the text alignment from left to center (basically shift the anchor point of the text)
// keep in mind that this only affects horizontal alignment
// https://developer.android.com/reference/android/graphics/Paint.Align
textAlign = android.graphics.Paint.Align.CENTER
+ isFakeBoldText = isBold
+ textSkewX = if (isItalic) -0.25f else 0f
}
val textPaint = Paint().asFrameworkPaint().apply {
- typeface = Typeface.create(
- Typeface.SANS_SERIF,
- if (isBold) FontWeight.Bold.weight else FontWeight.Normal.weight,
- isItalic,
- )
+ typeface = font
isAntiAlias = true
style = android.graphics.Paint.Style.FILL
- textSize = 16f
+ textSize = 48f
color = textColor.toArgb()
textAlign = android.graphics.Paint.Align.CENTER
+ isFakeBoldText = isBold
+ textSkewX = if (isItalic) -0.25f else 0f
}
Canvas(modifier = Modifier.fillMaxSize()) {
drawIntoCanvas {
@@ -141,15 +142,31 @@ fun OutLineText(
}
}
-@RequiresApi(Build.VERSION_CODES.P)
@Composable
fun SubtitlePreview(
+ font: String,
isBold: Boolean,
isItalic: Boolean,
textColor: Color,
borderColor: Color,
backgroundColor: Color,
) {
+ val fontMap = File(
+ Environment.getExternalStorageDirectory().absolutePath +
+ File.separator + LocalContext.current.getString(R.string.app_name) +
+ File.separator,
+ "fonts",
+ ).listFiles { file ->
+ file.extension.equals("ttf", true) ||
+ file.extension.equals("otf", true)
+ }?.associateBy(
+ { TTFFile.open(it).families.values.toTypedArray()[0] },
+ { it.absolutePath },
+ ) ?: emptyMap()
+
+ val fontFile = fontMap.keys.firstOrNull { it.contains(font, true) }
+ ?.let { Typeface.createFromFile(fontMap[it]?.let(::File)) } ?: Typeface.SANS_SERIF
+
Box(
modifier = Modifier
.padding(vertical = MaterialTheme.padding.medium)
@@ -159,6 +176,7 @@ fun SubtitlePreview(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
OutLineText(
text = stringResource(R.string.player_subtitle_settings_example),
+ font = fontFile,
outlineColor = borderColor,
textColor = textColor,
isBold = isBold,
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index b658e51a6..85d20422c 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -95,6 +95,8 @@ arthenica-smartexceptions = "com.arthenica:smart-exception-java:0.1.1"
seeker = "io.github.2307vivek:seeker:1.1.1"
+truetypeparser = "io.github.yubyf:truetypeparser-light:2.1.4"
+
[bundles]
reactivex = ["rxandroid", "rxjava"]
okhttp = ["okhttp-core", "okhttp-logging", "okhttp-dnsoverhttps"]
diff --git a/i18n/src/main/res/values/strings-aniyomi.xml b/i18n/src/main/res/values/strings-aniyomi.xml
index ccce7c9f0..bfafaae85 100644
--- a/i18n/src/main/res/values/strings-aniyomi.xml
+++ b/i18n/src/main/res/values/strings-aniyomi.xml
@@ -346,6 +346,7 @@
Text
Border
Background
+ Font Name
Exclude from data saver