mirror of
https://github.com/InvoiceShelf/InvoiceShelf.git
synced 2026-04-15 09:14:08 +00:00
Closes the audit gaps from the original font system commit. The bundled NotoSans only covered Latin/Greek/Cyrillic but the descriptions claimed Arabic, Thai and Hindi too — that was false. DejaVu Sans, the prior dompdf default, did cover Hebrew, Arabic, Armenian and Georgian, so swapping it for NotoSans had silently regressed those scripts. The Thai conditional include was also dropped from every PDF template in that commit, leaving th locales rendering boxes despite THSarabunNew still sitting in resources/static/fonts/. Adds four on-demand Font Packages — Noto Sans Hebrew, Noto Naskh Arabic (covering Arabic, Persian, Urdu, Sorani Kurdish), Noto Sans Devanagari (Hindi, Marathi, Sanskrit, Nepali) and Sarabun (Thai) — sourced from openmaptiles/fonts and google/fonts as static TTF. Static is mandatory because dompdf's PHP-Font-Lib does not parse variable fonts. Sarabun replaces THSarabunNew as the Thai face: same designer, OFL-licensed, maintained on a stable upstream URL, and surfaces through the same install flow as every other non-Latin script. The bundled THSarabunNew TTF files and the dead app/pdf/locale/th.blade.php legacy partial are removed as part of the migration. Unifies the bundled Noto Sans into FONT_PACKAGES as a noto-sans entry with bundled => true and files served from resources/static/fonts/ instead of storage/fonts/. FontService::isInstalled, downloadPackage, getInstalledFontFaces and getPackageStatuses honor the flag through a new packageDir() helper. The hardcoded @font-face block in the PDF partial is gone — fonts.blade.php collapses to a single getInstalledFontFaces() call so the package array is the only source of truth for every face, bundled or on-demand. Admin → Font Packages now lists Noto Sans at the top with a primary-colored Bundled pill (new settings.fonts.bundled string) alongside the existing Installed badge / Install button states. Also fixes the misleading settings.fonts.description and settings.fonts.bundled_info copy to actually describe what ships out of the box vs. what's optional, and rebuilds the en locale chunk.
127 lines
3.4 KiB
Vue
127 lines
3.4 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, ref } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { useNotificationStore } from '@v2/stores/notification.store'
|
|
import { client } from '@v2/api/client'
|
|
import { API } from '@v2/api/endpoints'
|
|
|
|
interface FontPackage {
|
|
key: string
|
|
name: string
|
|
family: string
|
|
locales: string[]
|
|
size: string
|
|
installed: boolean
|
|
bundled?: boolean
|
|
}
|
|
|
|
const { t } = useI18n()
|
|
const notificationStore = useNotificationStore()
|
|
|
|
const packages = ref<FontPackage[]>([])
|
|
const isLoading = ref(false)
|
|
const installing = ref<Set<string>>(new Set())
|
|
|
|
onMounted(async () => {
|
|
await loadStatus()
|
|
})
|
|
|
|
async function loadStatus(): Promise<void> {
|
|
isLoading.value = true
|
|
try {
|
|
const { data } = await client.get(API.FONTS_STATUS)
|
|
packages.value = data.packages
|
|
} catch {
|
|
// Silently fail
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
async function installFont(pkg: FontPackage): Promise<void> {
|
|
installing.value.add(pkg.key)
|
|
|
|
try {
|
|
await client.post(`${API.FONTS_INSTALL}/${pkg.key}/install`)
|
|
|
|
pkg.installed = true
|
|
|
|
notificationStore.showNotification({
|
|
type: 'success',
|
|
message: t('settings.fonts.download_complete', { name: pkg.name }),
|
|
})
|
|
} catch {
|
|
notificationStore.showNotification({
|
|
type: 'error',
|
|
message: t('settings.fonts.download_failed', { name: pkg.name }),
|
|
})
|
|
} finally {
|
|
installing.value.delete(pkg.key)
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<BaseSettingCard
|
|
:title="$t('settings.fonts.title')"
|
|
:description="$t('settings.fonts.description')"
|
|
>
|
|
<p class="text-sm text-muted mb-6">
|
|
{{ $t('settings.fonts.bundled_info') }}
|
|
</p>
|
|
|
|
<div class="space-y-3">
|
|
<div
|
|
v-for="pkg in packages"
|
|
:key="pkg.key"
|
|
class="flex items-center justify-between p-4 border border-line-light rounded-lg"
|
|
>
|
|
<div>
|
|
<div class="font-medium text-heading">{{ pkg.name }}</div>
|
|
<div class="text-xs text-subtle mt-1">
|
|
{{ pkg.locales.join(', ') }} — {{ pkg.size }}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-3">
|
|
<span
|
|
v-if="pkg.bundled"
|
|
class="inline-flex items-center rounded-full bg-primary-50 px-2.5 py-1 text-xs font-medium text-primary-600"
|
|
>
|
|
{{ $t('settings.fonts.bundled') }}
|
|
</span>
|
|
|
|
<span
|
|
v-else-if="pkg.installed"
|
|
class="inline-flex items-center rounded-full bg-success px-2.5 py-1 text-xs font-medium text-status-green"
|
|
>
|
|
{{ $t('settings.fonts.installed') }}
|
|
</span>
|
|
|
|
<BaseButton
|
|
v-else
|
|
size="sm"
|
|
variant="primary-outline"
|
|
:loading="installing.has(pkg.key)"
|
|
:disabled="installing.has(pkg.key)"
|
|
@click="installFont(pkg)"
|
|
>
|
|
<template #left="slotProps">
|
|
<BaseIcon
|
|
v-if="!installing.has(pkg.key)"
|
|
name="CloudArrowDownIcon"
|
|
:class="slotProps.class"
|
|
/>
|
|
</template>
|
|
{{ installing.has(pkg.key) ? $t('settings.fonts.downloading') : $t('settings.fonts.install') }}
|
|
</BaseButton>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="!packages.length && !isLoading" class="text-center py-8 text-muted">
|
|
{{ $t('settings.fonts.no_packages') }}
|
|
</div>
|
|
</BaseSettingCard>
|
|
</template>
|