book-reader/app/src/main/java/com/aryan/reader/paginatedreader/FontLoader.kt
Aryan d7a9cae9e1
v1.0.47 (#279)
* Add performance and stylus debugging logs

* Refactor and decouple UI models from `MainViewModel`

* Refactor library state management and projection logic

* Implement desktop shell using Compose Multiplatform

* Implement desktop shell using Compose Multiplatform

* Implement desktop shell using Compose Multiplatform

* Introduce ReaderEngine and enhance EPUB reader features in windows app

* Move core paginated reader logic to a Kotlin Multiplatform `shared` module and introduce experimental desktop support.

* Implement PDF rendering and text extraction for desktop using Pdfium

* Add `NonReaderScreens.kt` and UI dependencies

* Refactor and centralize library state management and models to improve cross-platform consistency

* Implement JSON persistence for desktop library and enhance library management features including shelf CRUD, tagging, and metadata editing

* Implement PDF annotation system and enhanced zoom controls for the desktop viewer

* Implement WebView-based EPUB rendering for desktop using CEF and embedded resources

* Optimize UI state projection, navigation state handling, and main screen pager performance

* Implement Bring Your Own Key (BYOK) support for AI features in OSS version

* Support Gemini-based Cloud TTS with BYOK support for OSS builds

* Refactor table cell image sizing in `PaginatedReader` and improve `MobiParser` native library loading and error handling.

* crash fixes

* Enhance navigation stability with lifecycle-aware safety checks and update `navigation-compose` to 2.9.6

* Implement dynamic bottom padding for the page info bar to account for device rounded corners

* Implement bidirectional jump history navigation and replace the jump-back pill with a dedicated `PdfJumpHistoryBar`

* Optimize PDF tiling performance and refine pan-and-fling gesture handling

* Implement customizable toolbars with drag-and-drop reordering and placement for PDF and EPUB readers

* Updated UI for customize toolbar

* Refine drag-and-drop reordering and section assignment for PDF and EPUB reader controls

* restructure PDF viewer UI component hierarchy to fix verifier crash

* Implement separate text dimming factors for light and dark themes

* Synchronize Pdfium access and improve resource lifecycle safety across Kotlin and native layers

* Enhance image alignment in paginated and EPUB readers through anchor detection and style-based positioning

* Centralize file type resolution logic and implement HTML sanitization during import

* Introduce vertical margin customization and configurable progress bar positioning

* texture support in epub reader

* Enhance TTS session management, progress tracking, and diagnostic logging

* Optimize library state projection and folder synchronization performance by refactoring collection lookups and refining metadata extraction logic.

* Refine TTS page mapping for PDF and overhaul TTS control UI

* Implement natural session completion logic in `TtsPlaybackManager` for cloud tts

* Replace Snackbar with `CustomTopBanner` for notifications in `PdfViewerScreen`

* Refine TTS playback continuity across PDF pages and improve state management for session transitions

* Implement global texture transparency and enhance textured theme support across PDF and EPUB readers.

* Update reader themes and improve texture rendering in page animations, EPUB UI, and immersive mode

* Add Support Project screen

* Optimize library performance via projection caching, batch database updates, and scoped folder synchronization.

* Enhance folder synchronization with fallback query mechanisms and refactor annotation sidecar importing logic

* Bump version to 1.0.47 (51)
2026-05-04 21:55:38 +05:30

120 lines
5.2 KiB
Kotlin

/*
* Episteme Reader - A native Android document reader.
* Copyright (C) 2026 Episteme
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* mail: epistemereader@gmail.com
*/
package com.aryan.reader.paginatedreader
import timber.log.Timber
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import java.io.File
import java.security.MessageDigest
private fun getCacheKeyForFont(bookId: String, fontPath: String): String {
val identifier = "$bookId:$fontPath"
val digest = MessageDigest.getInstance("MD5").digest(identifier.toByteArray())
return digest.joinToString("") { "%02x".format(it) } + ".ttf"
}
/**
* Loads custom font faces defined in the EPUB's CSS into a map of [FontFamily] objects.
* It handles WOFF2 fonts by converting them to TTF and storing them in a global, persistent cache.
*/
fun loadFontFamilies(fontFaces: List<FontFaceInfo>, extractionPath: String): Map<String, FontFamily> {
if (fontFaces.isEmpty()) {
return emptyMap()
}
Timber.d("Loading ${fontFaces.size} font faces from extraction path: $extractionPath")
// 1. Define a stable, global font cache directory.
// This assumes the parent of the extraction path is a stable base directory for epubs.
val baseCacheDir = File(extractionPath).parentFile ?: return emptyMap()
val fontCacheDir = File(baseCacheDir, "font_cache")
if (!fontCacheDir.exists()) {
fontCacheDir.mkdirs()
}
// 2. Get a stable identifier for the book from the extraction path.
// e.g., "d0e205bf-65cc-4ab4-93cc-cd2d613a7bb3.epub" from a longer temp path.
val bookId = File(extractionPath).name.substringBeforeLast("_")
val fontsByFamily = fontFaces.groupBy {
it.fontFamily.trim().removeSurrounding("'").removeSurrounding("\"").lowercase()
}
Timber.d("Grouped font faces by normalized family: ${fontsByFamily.keys}")
return fontsByFamily.mapValues { (familyName, fontInfos) ->
val fontList = fontInfos.mapNotNull { fontInfo ->
try {
Timber.d("Attempting to load font '$familyName' from resolved src path: '${fontInfo.src}'")
var fontFile = File(extractionPath, fontInfo.src)
if (!fontFile.exists()) {
Timber.w("Font file not found at: ${fontFile.absolutePath}")
return@mapNotNull null
}
// Handle WOFF2 conversion and global caching
if (fontFile.extension.equals("woff2", ignoreCase = true)) {
// 3. Generate a unique, deterministic cache key for the font.
val cacheKey = getCacheKeyForFont(bookId, fontInfo.src)
val cachedTtfFile = File(fontCacheDir, cacheKey)
if (cachedTtfFile.exists()) {
// Use the globally cached TTF file if it exists
fontFile = cachedTtfFile
Timber.d("Using globally cached TTF for '${fontInfo.src}'")
} else {
// Convert and save the TTF to the global cache if it doesn't exist
Timber.d("Converting woff2 font: ${fontFile.name}")
val woff2Data = fontFile.readBytes()
val ttfData = Woff2Converter.convertWoff2ToTtf(woff2Data)
if (ttfData != null) {
cachedTtfFile.writeBytes(ttfData)
fontFile = cachedTtfFile // Use the newly created TTF file
Timber.d("Successfully converted and globally cached woff2 as '${cachedTtfFile.name}'")
} else {
Timber.e("Failed to convert woff2 font: ${fontFile.name}")
return@mapNotNull null
}
}
}
Font(
fontFile,
fontInfo.fontWeight ?: FontWeight.Normal,
fontInfo.fontStyle ?: FontStyle.Normal
)
} catch (e: Exception) {
Timber.e(e, "Error loading font: ${fontInfo.src}")
null
}
}
if (fontList.isNotEmpty()) {
Timber.d("Loaded family '$familyName' with ${fontList.size} font styles.")
FontFamily(fontList)
} else {
Timber.w("Could not load any font styles for family '$familyName'.")
null
}
}.filterValues { it != null }.mapValues { it.value!! }
}