mirror of
https://github.com/gotson/komga.git
synced 2025-12-06 16:42:24 +01:00
feat(webui): support additional fonts
added embedded font OpenDyslexic
additional fonts can be added in the configuration directory under ./fonts/{fontFamily}/
supported files are woff/woff2/ttf/otf
Closes: #1836
This commit is contained in:
parent
42047cdafb
commit
201c066fc4
17 changed files with 257 additions and 2 deletions
|
|
@ -756,8 +756,10 @@
|
||||||
"epubreader": {
|
"epubreader": {
|
||||||
"current_chapter": "Current chapter",
|
"current_chapter": "Current chapter",
|
||||||
"page_of": "Page {page} of {count}",
|
"page_of": "Page {page} of {count}",
|
||||||
|
"publisher_font": "Publisher",
|
||||||
"settings": {
|
"settings": {
|
||||||
"column_count": "Column count",
|
"column_count": "Column count",
|
||||||
|
"font_family": "Font",
|
||||||
"layout": "Layout",
|
"layout": "Layout",
|
||||||
"layout_paginated": "Paginated",
|
"layout_paginated": "Paginated",
|
||||||
"layout_scroll": "Scroll",
|
"layout_scroll": "Scroll",
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import komgaHistory from './plugins/komga-history.plugin'
|
||||||
import komgaAnnouncements from './plugins/komga-announcements.plugin'
|
import komgaAnnouncements from './plugins/komga-announcements.plugin'
|
||||||
import komgaReleases from './plugins/komga-releases.plugin'
|
import komgaReleases from './plugins/komga-releases.plugin'
|
||||||
import komgaSettings from './plugins/komga-settings.plugin'
|
import komgaSettings from './plugins/komga-settings.plugin'
|
||||||
|
import komgaFonts from './plugins/komga-fonts.plugin'
|
||||||
import vuetify from './plugins/vuetify'
|
import vuetify from './plugins/vuetify'
|
||||||
import logger from './plugins/logger.plugin'
|
import logger from './plugins/logger.plugin'
|
||||||
import './public-path'
|
import './public-path'
|
||||||
|
|
@ -80,6 +81,7 @@ Vue.use(komgaHistory, {http: Vue.prototype.$http})
|
||||||
Vue.use(komgaAnnouncements, {http: Vue.prototype.$http})
|
Vue.use(komgaAnnouncements, {http: Vue.prototype.$http})
|
||||||
Vue.use(komgaReleases, {http: Vue.prototype.$http})
|
Vue.use(komgaReleases, {http: Vue.prototype.$http})
|
||||||
Vue.use(komgaSettings, {http: Vue.prototype.$http})
|
Vue.use(komgaSettings, {http: Vue.prototype.$http})
|
||||||
|
Vue.use(komgaFonts, {http: Vue.prototype.$http})
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
|
|
|
||||||
17
komga-webui/src/plugins/komga-fonts.plugin.ts
Normal file
17
komga-webui/src/plugins/komga-fonts.plugin.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import {AxiosInstance} from 'axios'
|
||||||
|
import _Vue from 'vue'
|
||||||
|
import KomgaFontsService from '@/services/komga-fonts.service'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
install(
|
||||||
|
Vue: typeof _Vue,
|
||||||
|
{http}: { http: AxiosInstance }) {
|
||||||
|
Vue.prototype.$komgaFonts = new KomgaFontsService(http)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'vue/types/vue' {
|
||||||
|
interface Vue {
|
||||||
|
$komgaFonts: KomgaFontsService;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
komga-webui/src/services/komga-fonts.service.ts
Normal file
23
komga-webui/src/services/komga-fonts.service.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import {AxiosInstance} from 'axios'
|
||||||
|
|
||||||
|
const API_FONTS = '/api/v1/fonts'
|
||||||
|
|
||||||
|
export default class KomgaFontsService {
|
||||||
|
private http: AxiosInstance
|
||||||
|
|
||||||
|
constructor(http: AxiosInstance) {
|
||||||
|
this.http = http
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFamilies(): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
return (await this.http.get(`${API_FONTS}/families`)).data
|
||||||
|
} catch (e) {
|
||||||
|
let msg = 'An error occurred while trying to retrieve font families'
|
||||||
|
if (e.response.data.message) {
|
||||||
|
msg += `: ${e.response.data.message}`
|
||||||
|
}
|
||||||
|
throw new Error(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -213,6 +213,14 @@
|
||||||
|
|
||||||
<v-subheader class="font-weight-black text-h6">{{ $t('bookreader.settings.display') }}</v-subheader>
|
<v-subheader class="font-weight-black text-h6">{{ $t('bookreader.settings.display') }}</v-subheader>
|
||||||
|
|
||||||
|
<v-list-item v-if="fontFamilies.length > 1">
|
||||||
|
<settings-select
|
||||||
|
:items="fontFamilies"
|
||||||
|
v-model="fontFamily"
|
||||||
|
:label="$t('epubreader.settings.font_family')"
|
||||||
|
/>
|
||||||
|
</v-list-item>
|
||||||
|
|
||||||
<v-list-item>
|
<v-list-item>
|
||||||
<v-list-item-title>{{ $t('epubreader.settings.viewing_theme') }}</v-list-item-title>
|
<v-list-item-title>{{ $t('epubreader.settings.viewing_theme') }}</v-list-item-title>
|
||||||
<v-btn
|
<v-btn
|
||||||
|
|
@ -299,7 +307,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import D2Reader, {Locator} from '@d-i-t-a/reader'
|
import D2Reader, {Locator} from '@d-i-t-a/reader'
|
||||||
import {bookManifestUrl, bookPositionsUrl} from '@/functions/urls'
|
import urls, {bookManifestUrl, bookPositionsUrl} from '@/functions/urls'
|
||||||
import {BookDto} from '@/types/komga-books'
|
import {BookDto} from '@/types/komga-books'
|
||||||
import {getBookTitleCompact} from '@/functions/book-title'
|
import {getBookTitleCompact} from '@/functions/book-title'
|
||||||
import {SeriesDto} from '@/types/komga-series'
|
import {SeriesDto} from '@/types/komga-series'
|
||||||
|
|
@ -382,6 +390,12 @@ export default Vue.extend({
|
||||||
{text: this.$t('enums.epubreader.column_count.one').toString(), value: '1'},
|
{text: this.$t('enums.epubreader.column_count.one').toString(), value: '1'},
|
||||||
{text: this.$t('enums.epubreader.column_count.two').toString(), value: '2'},
|
{text: this.$t('enums.epubreader.column_count.two').toString(), value: '2'},
|
||||||
],
|
],
|
||||||
|
fontFamilyDefault: [{
|
||||||
|
text: this.$t('epubreader.publisher_font'),
|
||||||
|
value: 'Original',
|
||||||
|
}],
|
||||||
|
fontFamiliesAdditional: [] as string[],
|
||||||
|
fontFamilies: [] as any[],
|
||||||
settings: {
|
settings: {
|
||||||
// R2D2BC
|
// R2D2BC
|
||||||
appearance: 'readium-default-on',
|
appearance: 'readium-default-on',
|
||||||
|
|
@ -393,6 +407,7 @@ export default Vue.extend({
|
||||||
fixedLayoutMargin: 0,
|
fixedLayoutMargin: 0,
|
||||||
fixedLayoutShadow: false,
|
fixedLayoutShadow: false,
|
||||||
direction: 'auto',
|
direction: 'auto',
|
||||||
|
fontFamily: 'Original',
|
||||||
// Epub Reader
|
// Epub Reader
|
||||||
alwaysFullscreen: false,
|
alwaysFullscreen: false,
|
||||||
navigationClick: true,
|
navigationClick: true,
|
||||||
|
|
@ -439,10 +454,13 @@ export default Vue.extend({
|
||||||
screenfull.exit()
|
screenfull.exit()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
async mounted() {
|
||||||
Object.assign(this.settings, this.$store.state.persistedState.epubreader)
|
Object.assign(this.settings, this.$store.state.persistedState.epubreader)
|
||||||
this.settings.alwaysFullscreen = this.$store.state.persistedState.webreader.alwaysFullscreen
|
this.settings.alwaysFullscreen = this.$store.state.persistedState.webreader.alwaysFullscreen
|
||||||
|
|
||||||
|
this.fontFamiliesAdditional = await this.$komgaFonts.getFamilies()
|
||||||
|
this.fontFamilies = [...this.fontFamilyDefault, ...this.fontFamiliesAdditional]
|
||||||
|
|
||||||
this.setup(this.bookId)
|
this.setup(this.bookId)
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
|
@ -611,6 +629,16 @@ export default Vue.extend({
|
||||||
this.$store.commit('setEpubreaderSettings', this.settings)
|
this.$store.commit('setEpubreaderSettings', this.settings)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
fontFamily: {
|
||||||
|
get: function (): string {
|
||||||
|
return this.settings.fontFamily ?? 'Original'
|
||||||
|
},
|
||||||
|
set: function (value: string): void {
|
||||||
|
this.settings.fontFamily = value
|
||||||
|
this.d2Reader.applyUserSettings({fontFamily: value})
|
||||||
|
this.$store.commit('setEpubreaderSettings', this.settings)
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
previousBook() {
|
previousBook() {
|
||||||
|
|
@ -721,6 +749,12 @@ export default Vue.extend({
|
||||||
// parse query params to get incognito mode
|
// parse query params to get incognito mode
|
||||||
this.incognito = !!(this.$route.query.incognito && this.$route.query.incognito.toString().toLowerCase() === 'true')
|
this.incognito = !!(this.$route.query.incognito && this.$route.query.incognito.toString().toLowerCase() === 'true')
|
||||||
|
|
||||||
|
const fontFamiliesInjectables = this.fontFamiliesAdditional.map(x => ({
|
||||||
|
type: 'style',
|
||||||
|
url: new URL(`${urls.origin}api/v1/fonts/resource/${x}/css`, import.meta.url).toString(),
|
||||||
|
fontFamily: x,
|
||||||
|
}))
|
||||||
|
|
||||||
this.d2Reader = await D2Reader.load({
|
this.d2Reader = await D2Reader.load({
|
||||||
url: new URL(bookManifestUrl(this.bookId)),
|
url: new URL(bookManifestUrl(this.bookId)),
|
||||||
userSettings: this.settings,
|
userSettings: this.settings,
|
||||||
|
|
@ -747,6 +781,7 @@ export default Vue.extend({
|
||||||
{type: 'style', url: new URL('../styles/r2d2bc/popup.css.resource', import.meta.url).toString()},
|
{type: 'style', url: new URL('../styles/r2d2bc/popup.css.resource', import.meta.url).toString()},
|
||||||
{type: 'style', url: new URL('../styles/r2d2bc/popover.css.resource', import.meta.url).toString()},
|
{type: 'style', url: new URL('../styles/r2d2bc/popover.css.resource', import.meta.url).toString()},
|
||||||
{type: 'style', url: new URL('../styles/r2d2bc/style.css.resource', import.meta.url).toString()},
|
{type: 'style', url: new URL('../styles/r2d2bc/style.css.resource', import.meta.url).toString()},
|
||||||
|
...fontFamiliesInjectables,
|
||||||
],
|
],
|
||||||
requestConfig: {
|
requestConfig: {
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,8 @@ class KomgaProperties {
|
||||||
|
|
||||||
var kobo = Kobo()
|
var kobo = Kobo()
|
||||||
|
|
||||||
|
val fonts = Fonts()
|
||||||
|
|
||||||
class Cors {
|
class Cors {
|
||||||
var allowedOrigins: List<String> = emptyList()
|
var allowedOrigins: List<String> = emptyList()
|
||||||
}
|
}
|
||||||
|
|
@ -72,6 +74,11 @@ class KomgaProperties {
|
||||||
var pragmas: Map<String, String> = emptyMap()
|
var pragmas: Map<String, String> = emptyMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Fonts {
|
||||||
|
@get:NotBlank
|
||||||
|
var dataDirectory: String = ""
|
||||||
|
}
|
||||||
|
|
||||||
class Lucene {
|
class Lucene {
|
||||||
@get:NotBlank
|
@get:NotBlank
|
||||||
var dataDirectory: String = ""
|
var dataDirectory: String = ""
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,8 @@ class SecurityConfiguration(
|
||||||
"/api/v1/oauth2/providers",
|
"/api/v1/oauth2/providers",
|
||||||
// epub resources - fonts are always requested anonymously, so we check for authorization within the controller method directly
|
// epub resources - fonts are always requested anonymously, so we check for authorization within the controller method directly
|
||||||
"/api/v1/books/{bookId}/resource/**",
|
"/api/v1/books/{bookId}/resource/**",
|
||||||
|
// dynamic fonts
|
||||||
|
"/api/v1/fonts/resource/**",
|
||||||
// OPDS authentication document
|
// OPDS authentication document
|
||||||
"/opds/v2/auth",
|
"/opds/v2/auth",
|
||||||
// KOReader user creation
|
// KOReader user creation
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
package org.gotson.komga.interfaces.api.rest
|
||||||
|
|
||||||
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
|
import org.gotson.komga.infrastructure.configuration.KomgaProperties
|
||||||
|
import org.gotson.komga.language.contains
|
||||||
|
import org.springframework.core.io.ByteArrayResource
|
||||||
|
import org.springframework.core.io.FileSystemResource
|
||||||
|
import org.springframework.core.io.Resource
|
||||||
|
import org.springframework.core.io.support.PathMatchingResourcePatternResolver
|
||||||
|
import org.springframework.http.ContentDisposition
|
||||||
|
import org.springframework.http.HttpStatus
|
||||||
|
import org.springframework.http.MediaType
|
||||||
|
import org.springframework.http.ResponseEntity
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping
|
||||||
|
import org.springframework.web.bind.annotation.RestController
|
||||||
|
import org.springframework.web.server.ResponseStatusException
|
||||||
|
import kotlin.io.path.Path
|
||||||
|
import kotlin.io.path.extension
|
||||||
|
import kotlin.io.path.isDirectory
|
||||||
|
import kotlin.io.path.isReadable
|
||||||
|
import kotlin.io.path.isRegularFile
|
||||||
|
import kotlin.io.path.listDirectoryEntries
|
||||||
|
import kotlin.io.path.name
|
||||||
|
import kotlin.io.path.toPath
|
||||||
|
|
||||||
|
private val logger = KotlinLogging.logger {}
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(value = ["api/v1/fonts"], produces = [MediaType.APPLICATION_JSON_VALUE])
|
||||||
|
class FontsController(
|
||||||
|
komgaProperties: KomgaProperties,
|
||||||
|
) {
|
||||||
|
private val supportedExtensions = listOf("woff", "woff2", "ttf", "otf")
|
||||||
|
private final val fonts: Map<String, List<Resource>>
|
||||||
|
|
||||||
|
init {
|
||||||
|
val resolver = PathMatchingResourcePatternResolver()
|
||||||
|
val fontsEmbedded =
|
||||||
|
try {
|
||||||
|
resolver
|
||||||
|
.getResources("/embeddedFonts/**/*.*")
|
||||||
|
.filterNot { it.filename == null }
|
||||||
|
.filter { supportedExtensions.contains(it.uri.toPath().extension, true) }
|
||||||
|
.groupBy {
|
||||||
|
it.uri
|
||||||
|
.toPath()
|
||||||
|
.parent.name
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error(e) { "Could not load embedded fonts" }
|
||||||
|
emptyMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
val fontsDir = Path(komgaProperties.fonts.dataDirectory)
|
||||||
|
val fontsAdditional =
|
||||||
|
try {
|
||||||
|
if (fontsDir.isDirectory() && fontsDir.isReadable()) {
|
||||||
|
fontsDir
|
||||||
|
.listDirectoryEntries()
|
||||||
|
.filter { it.isDirectory() }
|
||||||
|
.associate { dir ->
|
||||||
|
dir.name to
|
||||||
|
dir
|
||||||
|
.listDirectoryEntries()
|
||||||
|
.filter { it.isRegularFile() }
|
||||||
|
.filter { it.isReadable() }
|
||||||
|
.filter { supportedExtensions.contains(it.extension, true) }
|
||||||
|
.map { FileSystemResource(it) }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptyMap()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error(e) { "Could not load additional fonts" }
|
||||||
|
emptyMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fonts = fontsEmbedded + fontsAdditional
|
||||||
|
|
||||||
|
logger.info { "Fonts embedded: $fontsEmbedded" }
|
||||||
|
logger.info { "Fonts discovered: $fontsAdditional" }
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("families")
|
||||||
|
fun listFonts(): Set<String> = fonts.keys
|
||||||
|
|
||||||
|
@GetMapping("resource/{fontFamily}/{fontFile}")
|
||||||
|
fun getFontFile(
|
||||||
|
@PathVariable fontFamily: String,
|
||||||
|
@PathVariable fontFile: String,
|
||||||
|
): ResponseEntity<Resource> {
|
||||||
|
fonts[fontFamily]?.let { resources ->
|
||||||
|
val resource = resources.firstOrNull { it.uri.toPath().name == fontFile } ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||||
|
return ResponseEntity
|
||||||
|
.ok()
|
||||||
|
.headers {
|
||||||
|
it.contentDisposition =
|
||||||
|
ContentDisposition
|
||||||
|
.attachment()
|
||||||
|
.filename(fontFile)
|
||||||
|
.build()
|
||||||
|
}.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
|
.body(resource)
|
||||||
|
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("resource/{fontFamily}/css", produces = ["text/css"])
|
||||||
|
fun getFontFamilyAsCss(
|
||||||
|
@PathVariable fontFamily: String,
|
||||||
|
): ResponseEntity<Resource> {
|
||||||
|
fonts[fontFamily]?.let { files ->
|
||||||
|
val groups = files.groupBy { getFontCharacteristics(it.uri.toPath().name) }
|
||||||
|
|
||||||
|
val css =
|
||||||
|
groups
|
||||||
|
.map { (styleWeight, resources) -> buildFontFaceBlock(fontFamily, styleWeight, resources) }
|
||||||
|
.joinToString(separator = "\n")
|
||||||
|
|
||||||
|
return ResponseEntity
|
||||||
|
.ok()
|
||||||
|
.headers {
|
||||||
|
it.contentDisposition =
|
||||||
|
ContentDisposition
|
||||||
|
.attachment()
|
||||||
|
.filename("$fontFamily.css")
|
||||||
|
.build()
|
||||||
|
}.body(ByteArrayResource(css.toByteArray()))
|
||||||
|
} ?: throw ResponseStatusException(HttpStatus.NOT_FOUND)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildFontFaceBlock(
|
||||||
|
fontFamily: String,
|
||||||
|
styleAndWeight: FontCharacteristics,
|
||||||
|
fonts: List<Resource>,
|
||||||
|
): String {
|
||||||
|
val srcBlock =
|
||||||
|
fonts.joinToString(separator = ",", postfix = ";") { resource ->
|
||||||
|
val path = resource.uri.toPath()
|
||||||
|
"""url('${path.name}') format('${path.extension}')"""
|
||||||
|
}
|
||||||
|
// language=CSS
|
||||||
|
return """
|
||||||
|
@font-face {
|
||||||
|
font-family: '$fontFamily';
|
||||||
|
src: $srcBlock
|
||||||
|
font-weight: ${styleAndWeight.weight};
|
||||||
|
font-style: ${styleAndWeight.style};
|
||||||
|
}
|
||||||
|
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFontCharacteristics(filename: String): FontCharacteristics {
|
||||||
|
val style = if (filename.contains("italic", true)) "italic" else "normal"
|
||||||
|
val weight = if (filename.contains("bold", true)) "bold" else "normal"
|
||||||
|
return FontCharacteristics(style, weight)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class FontCharacteristics(
|
||||||
|
val style: String,
|
||||||
|
val weight: String,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,8 @@ komga:
|
||||||
file: \${komga.config-dir}/database.sqlite
|
file: \${komga.config-dir}/database.sqlite
|
||||||
lucene:
|
lucene:
|
||||||
data-directory: \${komga.config-dir}/lucene
|
data-directory: \${komga.config-dir}/lucene
|
||||||
|
fonts:
|
||||||
|
data-directory: \${komga.config-dir}/fonts
|
||||||
config-dir: \${user.home}/.komga
|
config-dir: \${user.home}/.komga
|
||||||
tasks-db:
|
tasks-db:
|
||||||
file: \${komga.config-dir}/tasks.sqlite
|
file: \${komga.config-dir}/tasks.sqlite
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
BIN
komga/src/main/resources/embeddedFonts/OpenDyslexic/OpenDyslexic-Bold.woff
Executable file
BIN
komga/src/main/resources/embeddedFonts/OpenDyslexic/OpenDyslexic-Bold.woff
Executable file
Binary file not shown.
BIN
komga/src/main/resources/embeddedFonts/OpenDyslexic/OpenDyslexic-Bold.woff2
Executable file
BIN
komga/src/main/resources/embeddedFonts/OpenDyslexic/OpenDyslexic-Bold.woff2
Executable file
Binary file not shown.
BIN
komga/src/main/resources/embeddedFonts/OpenDyslexic/OpenDyslexic-Italic.woff
Executable file
BIN
komga/src/main/resources/embeddedFonts/OpenDyslexic/OpenDyslexic-Italic.woff
Executable file
Binary file not shown.
BIN
komga/src/main/resources/embeddedFonts/OpenDyslexic/OpenDyslexic-Italic.woff2
Executable file
BIN
komga/src/main/resources/embeddedFonts/OpenDyslexic/OpenDyslexic-Italic.woff2
Executable file
Binary file not shown.
BIN
komga/src/main/resources/embeddedFonts/OpenDyslexic/OpenDyslexic-Regular.woff
Executable file
BIN
komga/src/main/resources/embeddedFonts/OpenDyslexic/OpenDyslexic-Regular.woff
Executable file
Binary file not shown.
BIN
komga/src/main/resources/embeddedFonts/OpenDyslexic/OpenDyslexic-Regular.woff2
Executable file
BIN
komga/src/main/resources/embeddedFonts/OpenDyslexic/OpenDyslexic-Regular.woff2
Executable file
Binary file not shown.
Loading…
Reference in a new issue