Skip to content

Commit

Permalink
🍱 Add additional fonts (#620)
Browse files Browse the repository at this point in the history
* add new fonts!

* ui for new settings

* add additional fonts

* fix lint
  • Loading branch information
aaronleopold authored Mar 1, 2025
1 parent c6966d4 commit ce3fd2e
Show file tree
Hide file tree
Showing 108 changed files with 926 additions and 132 deletions.
23 changes: 23 additions & 0 deletions apps/expo/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,29 @@
"configureAndroidBackup": true,
"faceIDPermission": "Allow $(PRODUCT_NAME) to access your Face ID biometric data."
}
],
[
"expo-font",
{
"fonts": [
"assets/fonts/Atkinson-Hyperlegible-Bold.ttf",
"assets/fonts/Atkinson-Hyperlegible-BoldItalic.ttf",
"assets/fonts/Atkinson-Hyperlegible-Italic.ttf",
"assets/fonts/Atkinson-Hyperlegible-Regular.ttf",
"assets/fonts/Bitter-Italic-VariableFont_wght.ttf",
"assets/fonts/Bitter-VariableFont_wght.ttf",
"assets/fonts/CharisSIL-Bold.ttf",
"assets/fonts/CharisSIL-BoldItalic.ttf",
"assets/fonts/CharisSIL-Italic.ttf",
"assets/fonts/CharisSIL-Regular.ttf",
"assets/fonts/Literata-Italic[opsz,wght].ttf",
"assets/fonts/Literata[opsz,wght].ttf",
"assets/fonts/OpenDyslexic-Bold-Italic.otf",
"assets/fonts/OpenDyslexic-Bold.otf",
"assets/fonts/OpenDyslexic-Italic.otf",
"assets/fonts/OpenDyslexic-Regular.otf"
]
}
]
],
"owner": "stumpapp",
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added apps/expo/assets/fonts/CharisSIL-Bold.ttf
Binary file not shown.
Binary file added apps/expo/assets/fonts/CharisSIL-BoldItalic.ttf
Binary file not shown.
Binary file added apps/expo/assets/fonts/CharisSIL-Italic.ttf
Binary file not shown.
Binary file added apps/expo/assets/fonts/CharisSIL-Regular.ttf
Binary file not shown.
Binary file not shown.
Binary file added apps/expo/assets/fonts/Literata[opsz,wght].ttf
Binary file not shown.
Binary file not shown.
Binary file added apps/expo/assets/fonts/OpenDyslexic-Bold.otf
Binary file not shown.
Binary file added apps/expo/assets/fonts/OpenDyslexic-Italic.otf
Binary file not shown.
Binary file added apps/expo/assets/fonts/OpenDyslexic-Regular.otf
Binary file not shown.
1 change: 1 addition & 0 deletions apps/expo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"dayjs": "^1.11.13",
"expo": "^52.0.25",
"expo-application": "~6.0.2",
"expo-font": "~11.10.3",
"expo-image": "~2.0.4",
"expo-keep-awake": "~14.0.3",
"expo-linking": "~7.0.5",
Expand Down
4 changes: 2 additions & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"tailwindcss": "^3.4.3",
"typescript": "^5.4.5",
"vite": "^4.5.3",
"vite-plugin-tsconfig-paths": "^1.4.1",
"vite-plugin-pwa": "^0.21.1"
"vite-plugin-pwa": "^0.21.1",
"vite-plugin-tsconfig-paths": "^1.4.1"
}
}
2 changes: 1 addition & 1 deletion apps/web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default defineConfig({
VitePWA({
registerType: 'autoUpdate',
devOptions: {
enabled: true,
enabled: false,
},
workbox: {
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, // 5MB
Expand Down
18 changes: 18 additions & 0 deletions core/src/db/entity/user/preferences.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,14 @@ impl<I> Arrangement<I> {
#[derive(Default, Debug, Clone, Serialize, Deserialize, Type, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum SupportedFont {
AtkinsonHyperlegible,
Bitter,
Charis,
#[default]
Inter,
LibreBaskerville,
Literata,
Nunito,
OpenDyslexic,
// TODO(383): Support custom fonts
// Custom(String),
Expand All @@ -138,7 +144,13 @@ pub enum SupportedFont {
impl Display for SupportedFont {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SupportedFont::AtkinsonHyperlegible => write!(f, "atkinsonhyperlegible"),
SupportedFont::Bitter => write!(f, "bitter"),
SupportedFont::Charis => write!(f, "charis"),
SupportedFont::Inter => write!(f, "inter"),
SupportedFont::LibreBaskerville => write!(f, "librebaskerville"),
SupportedFont::Literata => write!(f, "literata"),
SupportedFont::Nunito => write!(f, "nunito"),
SupportedFont::OpenDyslexic => write!(f, "opendyslexic"),
}
}
Expand All @@ -147,6 +159,12 @@ impl Display for SupportedFont {
impl From<String> for SupportedFont {
fn from(value: String) -> Self {
match value.to_lowercase().as_str() {
"atkinsonhyperlegible" => SupportedFont::AtkinsonHyperlegible,
"bitter" => SupportedFont::Bitter,
"charis" => SupportedFont::Charis,
"librebaskerville" => SupportedFont::LibreBaskerville,
"literata" => SupportedFont::Literata,
"nunito" => SupportedFont::Nunito,
"opendyslexic" => SupportedFont::OpenDyslexic,
// Note: for now we just always default to Inter. This will be acceptable
// until we have custom font support.
Expand Down
1 change: 1 addition & 0 deletions docs/pages/guides/configuration/theming.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ Stump has a limited number of fonts built-in:

- [Inter](https://rsms.me/inter/)
- [OpenDyslexic](https://opendyslexic.org/)
- [Atkinson Hyperlegible](https://brailleinstitute.org/freefont)

If you would like a new font, it will have to be added to the project manually. This can be done by appropriately defining the font in CSS, such as:

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"dev:expo": "concurrently -n server,expo -c green.bold,blue.bold \"cargo watch --ignore core/prisma -x run -p stump_server\" \"yarn expo dev\"",
"prisma-studio": "npx prisma studio --schema core/prisma/schema.prisma",
"format": "lerna run format --stream --parallel",
"nuke": "lerna run nuke"
"nuke": "concurrently -n root,monorepo -c green.bold,blue.bold \"yarn exec rimraf node_modules\" \"lerna run nuke\""
},
"devDependencies": {
"@babel/core": "^7.25.8",
Expand Down
42 changes: 20 additions & 22 deletions packages/browser/src/components/readers/epub/EpubJsReader.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
import {
type EpubReaderPreferences,
queryClient,
useEpubLazy,
useEpubReader,
useQuery,
useSDK,
} from '@stump/client'
import { Bookmark, UpdateEpubProgress } from '@stump/sdk'
import { BookPreferences, queryClient, useEpubLazy, useQuery, useSDK } from '@stump/client'
import { Bookmark, Media, UpdateEpubProgress } from '@stump/sdk'
import { Book, Rendition } from 'epubjs'
import uniqby from 'lodash/uniqBy'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import toast from 'react-hot-toast'
import AutoSizer from 'react-virtualized-auto-sizer'

import { useTheme } from '@/hooks'
import { useBookPreferences } from '@/scenes/book/reader/useBookPreferences'

import EpubReaderContainer from './EpubReaderContainer'
import { stumpDark } from './themes'
import { applyTheme, stumpDark } from './themes'

// NOTE: http://epubjs.org/documentation/0.3/ for epubjs documentation overview

Expand Down Expand Up @@ -72,6 +66,8 @@ export default function EpubJsReader({ id, initialCfi }: EpubJsReaderProps) {
const { sdk } = useSDK()
const { theme } = useTheme()

const { epub, isLoading } = useEpubLazy(id)

const ref = useRef<HTMLDivElement>(null)

const [book, setBook] = useState<Book | null>(null)
Expand All @@ -80,9 +76,7 @@ export default function EpubJsReader({ id, initialCfi }: EpubJsReaderProps) {

const [currentLocation, setCurrentLocation] = useState<EpubLocationState>()

const { epubPreferences } = useEpubReader((state) => ({
epubPreferences: state.preferences,
}))
const { bookPreferences } = useBookPreferences({ book: epub?.media_entity || ({} as Media) })

const { data: bookmarks } = useQuery([sdk.epub.keys.getBookmarks, id], () =>
sdk.epub.getBookmarks(id),
Expand Down Expand Up @@ -124,8 +118,6 @@ export default function EpubJsReader({ id, initialCfi }: EpubJsReaderProps) {
return { chapter: position, chapterName: name, sectionIndex: sectionIndex }
}, [book, currentLocation])

const { epub, isLoading } = useEpubLazy(id)

/**
* A function for focusing the iframe in the epub reader. This will be used to ensure
* the iframe is focused whenever the reader is loaded and/or the location changes.
Expand Down Expand Up @@ -185,14 +177,18 @@ export default function EpubJsReader({ id, initialCfi }: EpubJsReaderProps) {
* @param rendition: The epubjs rendition instance
* @param preferences The epub reader preferences
*/
const applyEpubPreferences = (rendition: Rendition, preferences: EpubReaderPreferences) => {
const applyEpubPreferences = (rendition: Rendition, preferences: BookPreferences) => {
if (theme === 'dark') {
rendition.themes.register('stump-dark', applyTheme(stumpDark, preferences))
rendition.themes.select('stump-dark')
} else {
rendition.themes.register('stump-light', applyTheme({}, preferences))
rendition.themes.select('stump-light')
}
rendition.direction(preferences.readingDirection)
rendition.themes.fontSize(`${preferences.fontSize}px`)
if (preferences.fontSize) {
rendition.themes.fontSize(`${preferences.fontSize}px`)
}
}

/**
Expand All @@ -218,7 +214,9 @@ export default function EpubJsReader({ id, initialCfi }: EpubJsReaderProps) {

//? TODO: I guess here I would need to wait for and load in custom theme blobs...
//* Color manipulation reference: /~https://github.com/futurepress/epub.js/issues/1019
rendition_.themes.register('stump-dark', stumpDark)
rendition_.themes.register('stump-dark', applyTheme(stumpDark, bookPreferences))
rendition_.themes.register('stump-light', applyTheme({}, bookPreferences))

rendition_.on('relocated', handleLocationChange)

// This callback is used to change the page when a keydown event is received.
Expand All @@ -236,7 +234,7 @@ export default function EpubJsReader({ id, initialCfi }: EpubJsReaderProps) {
// When the epub page isn't in focus, the window fires them instead
window.addEventListener('keydown', keydown_callback)

applyEpubPreferences(rendition_, epubPreferences)
applyEpubPreferences(rendition_, bookPreferences)
setRendition(rendition_)

const targetCfi = epub?.media_entity.active_reading_session?.epubcfi ?? initialCfi
Expand Down Expand Up @@ -297,9 +295,9 @@ export default function EpubJsReader({ id, initialCfi }: EpubJsReaderProps) {
*/
useEffect(() => {
if (rendition) {
applyEpubPreferences(rendition, epubPreferences)
applyEpubPreferences(rendition, bookPreferences)
}
}, [rendition, epubPreferences, theme])
}, [rendition, bookPreferences, theme])

/**
* Invalidate the book query when a reader is unmounted so that the book overview
Expand Down Expand Up @@ -603,7 +601,7 @@ export default function EpubJsReader({ id, initialCfi }: EpubJsReaderProps) {
// 'epubcfi(/6/12!/4[3Q280-a9efbf2f573d4345819e3829f80e5dbc]/2[prologue]/4[prologue-text]/8/1:56)',
// ).then((res) => console.log('cfiWithinAnother', res))

if (isLoading || !epub) {
if (isLoading || !epub?.media_entity) {
return null
}

Expand Down
15 changes: 11 additions & 4 deletions packages/browser/src/components/readers/epub/EpubReaderHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Spacer, Text } from '@stump/components'
import { cn, Spacer, Text } from '@stump/components'
import { ArrowLeft } from 'lucide-react'
import { Link } from 'react-router-dom'

import { useBookPreferences } from '@/scenes/book/reader/useBookPreferences'
import { formatBookName } from '@/utils/format'

import paths from '../../../paths'
Expand All @@ -16,12 +17,13 @@ import {
} from './controls'
import { LocationManager } from './locations'

// TODO(UX): gather feedback on the header design. I am worried the actionable items are too small on
// mobile devices.
export default function EpubReaderHeader() {
const {
readerMeta: { bookEntity },
} = useEpubReaderContext()
const {
bookPreferences: { fontFamily },
} = useBookPreferences({ book: bookEntity })

const bookName = formatBookName(bookEntity)

Expand All @@ -39,7 +41,12 @@ export default function EpubReaderHeader() {

<Spacer />

<Text size="sm" className="line-clamp-1">
<Text
size="sm"
className={cn('line-clamp-1', {
[`font-${fontFamily}`]: !!fontFamily,
})}
>
{bookName}
</Text>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
import { useEpubReader } from '@stump/client'
import { cx } from '@stump/components'
import { ChevronLeft, ChevronRight } from 'lucide-react'
import { useCallback } from 'react'
import { useSwipeable } from 'react-swipeable'

import { useEpubReaderControls } from '../context'
import { useBookPreferences } from '@/scenes/book/reader/useBookPreferences'

import { useEpubReaderContext, useEpubReaderControls } from '../context'
import ControlButton from './ControlButton'

type Props = {
children: React.ReactNode
}

export default function EpubNavigationControls({ children }: Props) {
const {
readerMeta: { bookEntity: book },
} = useEpubReaderContext()
const { visible, onPaginateBackward, onPaginateForward, setVisible } = useEpubReaderControls()

const { readingDirection } = useEpubReader((state) => ({
readingDirection: state.preferences.readingDirection,
}))
const {
bookPreferences: { readingDirection },
} = useBookPreferences({ book })

const invertControls = readingDirection === 'rtl'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Label, NativeSelect } from '@stump/components'
import { useLocaleContext } from '@stump/i18n'
import { isSupportedFont } from '@stump/sdk'
import { useCallback } from 'react'

import { useBookPreferences } from '@/scenes/book/reader/useBookPreferences'
import { SUPPORTED_FONT_OPTIONS } from '@/scenes/settings/app/appearance/FontSelect'

import { useEpubReaderContext } from '../context'

export default function FontFamily() {
const { t } = useLocaleContext()
const {
readerMeta: { bookEntity },
} = useEpubReaderContext()
const {
bookPreferences: { fontFamily },
setBookPreferences,
} = useBookPreferences({ book: bookEntity })

const changeFont = useCallback(
(font: string) => {
if (!font) {
setBookPreferences({ fontFamily: undefined })
} else if (isSupportedFont(font)) {
// Note: useApplyTheme will apply the font to the body element after the preferences are updated
setBookPreferences({ fontFamily: font })
}
},
[setBookPreferences],
)

return (
<div className="py-1.5">
<Label htmlFor="font-family">{t(getKey('fontFamily.label'))}</Label>
<NativeSelect
id="font-family"
size="sm"
options={[{ value: '', label: 'Default' }].concat(SUPPORTED_FONT_OPTIONS)}
value={fontFamily ?? ''}
onChange={(e) => changeFont(e.target.value)}
className="mt-1.5"
/>
</div>
)
}

const LOCAL_BASE = 'settingsScene.app/reader.sections.textBasedBooks.sections'
const getKey = (key: string) => `${LOCAL_BASE}.${key}`
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { useEpubReader } from '@stump/client'
import { cx, Label, Text, TEXT_VARIANTS } from '@stump/components'
import { Minus, Plus } from 'lucide-react'
import { useCallback, useEffect, useRef } from 'react'

import { usePressAndHold } from '@/hooks/usePressAndHold'
import { useBookPreferences } from '@/scenes/book/reader/useBookPreferences'

import { useEpubReaderContext } from '../context'

export default function FontSizeControl() {
const { fontSize, setFontSize } = useEpubReader((state) => ({
fontSize: state.preferences.fontSize,
setFontSize: state.setFontSize,
}))
const {
readerMeta: { bookEntity },
} = useEpubReaderContext()
const {
bookPreferences: { fontSize = 13 },
setBookPreferences,
} = useBookPreferences({ book: bookEntity })
const fontSizeRef = useRef(fontSize)
useEffect(() => {
fontSizeRef.current = fontSize
Expand All @@ -20,10 +25,10 @@ export default function FontSizeControl() {
if (newSize < 1) {
return
} else {
setFontSize(newSize)
setBookPreferences({ fontSize: newSize })
}
},
[setFontSize],
[setBookPreferences],
)

const { bindButton } = usePressAndHold()
Expand Down
Loading

0 comments on commit ce3fd2e

Please sign in to comment.