Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co
- `Other` - for technical stuff.

## [Unreleased]
### Added
- Added system font fallback ([@Secozzi](https://github.com/Secozzi)) ([#156](https://github.com/quickdesh/Animiru/pull/156))

## [v0.19.7.3] - 2026-04-17
### Improved
Expand Down
40 changes: 39 additions & 1 deletion app/src/main/java/animiru/feature/mpvfiles/MpvConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class MpvConfig(
copyUserFiles(mpvDir)
copyFontsDirectory(mpvDir)
copyAssets(mpvDir)
writeFontsConf(context, mpvDir)
}
}

Expand Down Expand Up @@ -116,7 +117,7 @@ class MpvConfig(

private fun copyAssets(mpvDir: UniFile) {
val assetManager = context.assets
val files = arrayOf("subfont.ttf", "cacert.pem")
val files = arrayOf("cacert.pem")
for (filename in files) {
var ins: InputStream? = null
var out: OutputStream? = null
Expand All @@ -141,6 +142,43 @@ class MpvConfig(
}
}

private fun writeFontsConf(context: Context, mpvDir: UniFile) {
val parts = mutableListOf(
"<fontconfig>",
// Android system fonts reside here
"<dir>/system/fonts/</dir>",
"<dir>/product/fonts/</dir>",
// User provided fonts
"<dir>${mpvDir.createDirectory(MPV_FONTS_DIR)!!.filePath!!}</dir>",
// Point fontconfig to the right cache path so that caching works
"<cachedir>${context.cacheDir.path}</cachedir>",
// Conveniently there is *no* Java API to query the system default fonts, but we can
// manually specify the font families we know Android uses and provides by default.
// (compare to 60-latin.conf shipped with fontconfig)
"<alias><family>serif</family>",
"<prefer><family>Noto Serif</family></prefer>",
"</alias>",
"<alias><family>Sans Serif</family>",
"<prefer>",
"<family>Roboto</family>",
"<family>Noto Sans</family>", // other languages
"</prefer>",
"</alias>",
"<alias><family>monospace</family>",
"<prefer><family>Droid Sans Mono</family></prefer>",
"</alias>",
"</fontconfig>",
)
try {
val file = mpvDir.createFile("fonts.conf")
file?.openOutputStream()?.bufferedWriter()?.use {
it.write(parts.joinToString("\n"))
}
} catch (e: IOException) {
logcat(LogPriority.ERROR, e) { "Failed to write fonts.conf" }
}
}

private fun deleteAndGet(parent: UniFile, name: String): UniFile {
parent.createDirectory(name)?.delete()
return parent.createDirectory(name)!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ object PlayerSettingsSubtitleScreen : SearchableSettings {
val whitelist = subtitlePreferences.subtitleWhitelist
val blacklist = subtitlePreferences.subtitleBlacklist
val blackBars = subtitlePreferences.subtitleBlackBars
val systemFonts = subtitlePreferences.subtitleSystemFonts

return listOf(
Preference.PreferenceItem.EditTextInfoPreference(
Expand Down Expand Up @@ -86,6 +87,10 @@ object PlayerSettingsSubtitleScreen : SearchableSettings {
title = stringResource(AMMR.strings.player_pref_subtitle_black_bars),
subtitle = stringResource(AMMR.strings.player_pref_subtitle_black_bars_summary),
),
Preference.PreferenceItem.SwitchPreference(
preference = systemFonts,
title = stringResource(AMMR.strings.player_pref_subtitle_system_fonts),
),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
package eu.kanade.presentation.player.components

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
Expand All @@ -31,6 +35,9 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalWindowInfo
import androidx.compose.ui.unit.dp
import kotlinx.collections.immutable.ImmutableList

@Composable
Expand Down Expand Up @@ -64,15 +71,32 @@ fun ExposedTextDropDownMenu(
.fillMaxWidth(),
)

val sizeOfOneItem by remember {
mutableStateOf(50.dp)
}
val screenHeight = with(LocalDensity.current) {
LocalWindowInfo.current.containerSize.height.toDp()
}
val height by remember(options.size) {
val itemsSize = sizeOfOneItem * options.size
mutableStateOf(minOf(itemsSize, screenHeight * 3 / 4))
}

ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
options.forEach { option: String ->
DropdownMenuItem(
text = { Text(text = option) },
onClick = {
expanded = false
onValueChangedEvent(option)
},
)
LazyColumn(
modifier = Modifier
.width(500.dp)
.height(height),
) {
items(options) { option ->
DropdownMenuItem(
text = { Text(text = option) },
onClick = {
expanded = false
onValueChangedEvent(option)
},
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,6 @@ class PlayerActivity : BaseActivity() {

private fun setupPlayerMPV() {
val mpvDir = UniFile.fromFile(applicationContext.filesDir)!!.createDirectory(MPV_DIR)!!
val fontsDirectory = mpvDir.createDirectory(MPV_FONTS_DIR)!!

val mpvConfFile = mpvDir.createFile("mpv.conf")!!
advancedPlayerPreferences.mpvConf.get().let { mpvConfFile.writeText(it) }
Expand All @@ -459,8 +458,6 @@ class PlayerActivity : BaseActivity() {

player.init(mpv)

mpv.setPropertyString("sub-fonts-dir", fontsDirectory.filePath!!)
mpv.setPropertyString("osd-fonts-dir", fontsDirectory.filePath!!)
val showBlackBars = if (subtitlePreferences.subtitleBlackBars.get()) "yes" else "no"
mpv.setOptionString("sub-ass-force-margins", showBlackBars)
mpv.setOptionString("sub-use-margins", showBlackBars)
Expand Down
61 changes: 61 additions & 0 deletions app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import android.net.Uri
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.yubyf.truetypeparser.TTFFile
import dev.icerock.moko.resources.StringResource
import eu.kanade.domain.anime.interactor.SetAnimeViewerFlags
import eu.kanade.domain.base.BasePreferences
Expand Down Expand Up @@ -67,6 +68,7 @@ import eu.kanade.tachiyomi.ui.player.loader.HosterLoader
import eu.kanade.tachiyomi.ui.player.settings.AudioPreferences
import eu.kanade.tachiyomi.ui.player.settings.GesturePreferences
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
import eu.kanade.tachiyomi.ui.player.settings.SubtitlePreferences
import eu.kanade.tachiyomi.ui.player.utils.AniSkipApi
import eu.kanade.tachiyomi.ui.player.utils.ChapterUtils
import eu.kanade.tachiyomi.ui.player.utils.ChapterUtils.Companion.getStringRes
Expand All @@ -93,6 +95,7 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
Expand Down Expand Up @@ -124,6 +127,7 @@ import tachiyomi.domain.history.interactor.UpsertHistory
import tachiyomi.domain.history.model.HistoryUpdate
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.storage.service.StorageManager
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.i18n.aniyomi.AYMR
import tachiyomi.source.local.isLocal
Expand All @@ -133,6 +137,7 @@ import java.io.File
import java.io.InputStream
import java.util.Date
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.collections.first
import kotlin.coroutines.cancellation.CancellationException

class PlayerViewModel @JvmOverloads constructor(
Expand All @@ -141,6 +146,7 @@ class PlayerViewModel @JvmOverloads constructor(
private val json: Json = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(),
private val storageManager: StorageManager = Injekt.get(),
private val imageSaver: ImageSaver = Injekt.get(),
private val downloadPreferences: DownloadPreferences = Injekt.get(),
private val trackPreferences: TrackPreferences = Injekt.get(),
Expand All @@ -155,6 +161,7 @@ class PlayerViewModel @JvmOverloads constructor(
private val setAnimeViewerFlags: SetAnimeViewerFlags = Injekt.get(),
private val playerPreferences: PlayerPreferences = Injekt.get(),
private val audioPreferences: AudioPreferences = Injekt.get(),
private val subtitlePreferences: SubtitlePreferences = Injekt.get(),
private val gesturePreferences: GesturePreferences = Injekt.get(),
private val basePreferences: BasePreferences = Injekt.get(),
private val getCustomButtons: GetCustomButtons = Injekt.get(),
Expand Down Expand Up @@ -311,7 +318,16 @@ class PlayerViewModel @JvmOverloads constructor(
private val _remainingTime = MutableStateFlow(0)
val remainingTime = _remainingTime.asStateFlow()

private val _fontList = MutableStateFlow<ImmutableList<String>>(persistentListOf())
val fontList = _fontList.asStateFlow()

init {
viewModelScope.launchIO {
subtitlePreferences.subtitleSystemFonts.changes().collectLatest {
_fontList.update { _ -> fetchFonts(it).toPersistentList() }
}
}

mpv.propFlow<Long>("time-pos")
.filterNotNull()
.onEach(::onSecondReached)
Expand Down Expand Up @@ -357,6 +373,49 @@ class PlayerViewModel @JvmOverloads constructor(
}
}

fun fetchFonts(includeSystemFonts: Boolean): List<String> {
val fontFiles = mutableListOf<String>()

storageManager.getFontsDirectory()?.listFiles()?.filter { file ->
file.name?.lowercase()?.matches(FONT_EXTENSION_REGEX) == true
}?.mapNotNull {
try {
TTFFile.open(it.openInputStream()).families.values.first()
} catch (_: Exception) {
null
}
}?.let {
fontFiles.addAll(it)
}

if (!includeSystemFonts) {
return fontFiles.distinct()
}

val fontDirectories = listOf(
"/system/fonts/",
"/product/fonts/",
)

for (directory in fontDirectories) {
val dir = File(directory)
if (dir.exists() && dir.isDirectory) {
val files = dir.listFiles()
files?.filter { file ->
file.isFile && file.name.lowercase().matches(FONT_EXTENSION_REGEX)
}?.forEach { file ->
try {
fontFiles.add(
TTFFile.open(file.inputStream()).families.values.first(),
)
} catch (_: Exception) { }
}
}
}

return fontFiles.distinct()
}

private fun setCustomButtons(buttons: List<CustomButton>) {
_customButtons.update { _ -> buttons.toPersistentList() }
buttons.firstOrNull { it.isFavorite }?.let {
Expand Down Expand Up @@ -2155,6 +2214,8 @@ class PlayerViewModel @JvmOverloads constructor(
}
}

private val FONT_EXTENSION_REGEX = Regex($$""".*\.[ot]tf$""")

fun CustomButton.execute(mpv: MPV) {
mpv.command("script-message", "call_button_$id")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,7 @@ fun PlayerControls(
val isItalic by viewModel.mpv.propFlow<Boolean>("sub-italic").collectAsState()
val subJustify by viewModel.mpv.propFlow<String>("sub-justify").collectAsState()
val subFont by viewModel.mpv.propFlow<String>("sub-font").collectAsState()
val subFontList by viewModel.fontList.collectAsState()
val subFontSize by viewModel.mpv.propFlow<Int>("sub-font-size").collectAsState()
val subBorderStyle by viewModel.mpv.propFlow<String>("sub-border-style").collectAsState()
val subBorderSize by viewModel.mpv.propFlow<Int>("sub-outline-size").collectAsState()
Expand Down Expand Up @@ -741,6 +742,7 @@ fun PlayerControls(
subJustify?.let { SubtitleJustification.byValue(it) }
?: subtitlePreferences.subtitleJustification.get(),
subFont = subFont ?: subtitlePreferences.subtitleFont.get(),
subFontList = subFontList,
subFontSize = subFontSize ?: subtitlePreferences.subtitleFontSize.get(),
subBorderStyle = subBorderStyle?.let { SubtitlesBorderStyle.byValue(it) }
?: subtitlePreferences.borderStyleSubtitles.get(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import eu.kanade.tachiyomi.ui.player.controls.components.panels.VideoSettingsPan
import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences
import eu.kanade.tachiyomi.ui.player.settings.SubtitleAssOverride
import eu.kanade.tachiyomi.ui.player.settings.SubtitleJustification
import kotlinx.collections.immutable.ImmutableList
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get

Expand All @@ -58,6 +59,7 @@ fun PlayerPanels(
isItalic: Boolean,
subJustify: SubtitleJustification,
subFont: String,
subFontList: ImmutableList<String>,
subFontSize: Int,
subBorderStyle: SubtitlesBorderStyle,
subBorderSize: Int,
Expand Down Expand Up @@ -129,6 +131,7 @@ fun PlayerPanels(
isItalic = isItalic,
justify = subJustify,
font = subFont,
fontList = subFontList,
fontSize = subFontSize,
borderStyle = subBorderStyle,
borderSize = subBorderSize,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.compose.ui.Modifier
import eu.kanade.tachiyomi.ui.player.controls.components.panels.components.MultiCardPanel
import eu.kanade.tachiyomi.ui.player.settings.SubtitleAssOverride
import eu.kanade.tachiyomi.ui.player.settings.SubtitleJustification
import kotlinx.collections.immutable.ImmutableList
import tachiyomi.i18n.aniyomi.AYMR
import tachiyomi.presentation.core.i18n.stringResource

Expand All @@ -33,6 +34,7 @@ fun SubtitleSettingsPanel(
isItalic: Boolean,
justify: SubtitleJustification,
font: String,
fontList: ImmutableList<String>,
fontSize: Int,
borderStyle: SubtitlesBorderStyle,
borderSize: Int,
Expand Down Expand Up @@ -74,6 +76,7 @@ fun SubtitleSettingsPanel(
isItalic = isItalic,
justify = justify,
font = font,
fontList = fontList,
fontSize = fontSize,
borderStyle = borderStyle,
borderSize = borderSize,
Expand Down
Loading
Loading