diff --git a/web/src/reader/ebook/typography-engine.ts b/web/src/reader/ebook/typography-engine.ts index e43f0a3..10090d7 100644 --- a/web/src/reader/ebook/typography-engine.ts +++ b/web/src/reader/ebook/typography-engine.ts @@ -2,33 +2,72 @@ // Procedural style: Functions, not classes interface TypographyConfig { + readingFont: + | "literata" + | "crimson" + | "source-serif" + | "eb-garamond" + | "libertinus" + | "noto-serif" + | "charis-sil" + | "ibm-plex"; // Bundled libre fonts fontSize: number; lineHeight: number; - textAlign: "left" | "justify"; + marginTop: number; + marginBottom: number; + marginLeft: number; + marginRight: number; + textAlign: "left" | "right" | "center" | "justify"; + textIndent: number; hyphenate: boolean; ligatures: boolean; - fontSmoothing: "auto" | "grayscale"; + fontSmoothing: "auto" | "antialiased" | "subpixel-antialiased"; } -export function applyTypographyConfig( - element: HTMLElement, +function applyTypography( + container: HTMLElement, config: TypographyConfig, ): void { - // Enable/disable ligatures - setLigatures(element, config.ligatures); + const content = container.querySelector(".ebook-content"); + if (!content) return; + + const fontStack = getFontStack(config.readingFont); + + content.setAttribute( + "style", + ` + font-family: ${fontStack}; + font-size: ${config.fontSize}px; + line-height: ${config.lineHeight}; + text-align: ${config.textAlign}; + margin-top: ${config.marginTop}px; + margin-bottom: ${config.marginBottom}px; + margin-left: ${config.marginLeft}px; + margin-right: ${config.marginRight}px; + text-indent: ${config.textIndent}px; + -webkit-font-smoothing: ${config.fontSmoothing}; + -moz-osx-font-smoothing: ${config.fontSmoothing === "grayscale" ? "grayscale" : "auto"}; + `, + ); - // Enable/disable hyphenation if (config.hyphenate) { - enableHyphenation(element); + enableHyphenation(container, content as HTMLElement); } - // Apply justification settings + setLigatures(content as HTMLElement, config.ligatures); + if (config.textAlign === "justify") { - enableJustification(element); + enableJustification(content as HTMLElement); } +} - // Apply font smoothing - element.style.fontSmooth = config.fontSmoothing; +function enableHyphenation(container: HTMLElement, element: HTMLElement): void { + element.style.hyphens = "auto"; + element.style.hyphenateLimitChars = "6 3 3"; + + const lang = + container.closest("[data-language]")?.getAttribute("data-language") || "en"; + element.setAttribute("lang", lang); } function setLigatures(element: HTMLElement, enabled: boolean): void { @@ -41,16 +80,6 @@ function setLigatures(element: HTMLElement, enabled: boolean): void { } } -function enableHyphenation(element: HTMLElement): void { - element.style.hyphens = "auto"; - element.style.hyphenateLimitChars = "6 3 3"; - - // Add language attribute from EPUB metadata - const lang = - element.closest("[data-language]")?.getAttribute("data-language") || "en"; - element.setAttribute("lang", lang); -} - function enableJustification(element: HTMLElement): void { element.style.wordBreak = "normal"; element.style.overflowWrap = "break-word"; @@ -58,7 +87,14 @@ function enableJustification(element: HTMLElement): void { element.style.letterSpacing = "0.01em"; } -export function measureReadingTime( +function updateTypographyConfig( + currentConfig: TypographyConfig, + newConfig: Partial, +): TypographyConfig { + return { ...currentConfig, ...newConfig }; +} + +function measureReadingTime( container: HTMLElement, wordsPerMinute: number = 250, ): number { @@ -72,7 +108,7 @@ export function measureReadingTime( return Math.ceil(minutes); } -export function getWordCount(container: HTMLElement): number { +function getWordCount(container: HTMLElement): number { const content = container.querySelector(".ebook-content"); if (!content) return 0; diff --git a/web/static/fonts/crimson/CrimsonPro-Italic-VariableFont_wght.woff2 b/web/static/fonts/crimson/CrimsonPro-Italic-Variable.woff2 similarity index 100% rename from web/static/fonts/crimson/CrimsonPro-Italic-VariableFont_wght.woff2 rename to web/static/fonts/crimson/CrimsonPro-Italic-Variable.woff2 diff --git a/web/static/fonts/crimson/CrimsonPro-VariableFont_wght.woff2 b/web/static/fonts/crimson/CrimsonPro-Variable.woff2 similarity index 100% rename from web/static/fonts/crimson/CrimsonPro-VariableFont_wght.woff2 rename to web/static/fonts/crimson/CrimsonPro-Variable.woff2 diff --git a/web/static/fonts/eb-garamond/EBGaramond-Italic-VariableFont_wght.woff2 b/web/static/fonts/eb-garamond/EBGaramond-Italic-Variable.woff2 similarity index 100% rename from web/static/fonts/eb-garamond/EBGaramond-Italic-VariableFont_wght.woff2 rename to web/static/fonts/eb-garamond/EBGaramond-Italic-Variable.woff2 diff --git a/web/static/fonts/eb-garamond/EBGaramond-VariableFont_wght.woff2 b/web/static/fonts/eb-garamond/EBGaramond-Variable.woff2 similarity index 100% rename from web/static/fonts/eb-garamond/EBGaramond-VariableFont_wght.woff2 rename to web/static/fonts/eb-garamond/EBGaramond-Variable.woff2 diff --git a/web/static/fonts/literata/Literata-Italic-VariableFont_opsz,wght.woff2 b/web/static/fonts/literata/Literata-Italic-Variable.woff2 similarity index 100% rename from web/static/fonts/literata/Literata-Italic-VariableFont_opsz,wght.woff2 rename to web/static/fonts/literata/Literata-Italic-Variable.woff2 diff --git a/web/static/fonts/literata/Literata-VariableFont_opsz,wght.woff2 b/web/static/fonts/literata/Literata-Variable.woff2 similarity index 100% rename from web/static/fonts/literata/Literata-VariableFont_opsz,wght.woff2 rename to web/static/fonts/literata/Literata-Variable.woff2 diff --git a/web/static/fonts/noto-serif/NotoSerif-Italic-VariableFont_wdth,wght.woff2 b/web/static/fonts/noto-serif/NotoSerif-Italic-Variable.woff2 similarity index 100% rename from web/static/fonts/noto-serif/NotoSerif-Italic-VariableFont_wdth,wght.woff2 rename to web/static/fonts/noto-serif/NotoSerif-Italic-Variable.woff2 diff --git a/web/static/fonts/noto-serif/NotoSerif-VariableFont_wdth,wght.woff2 b/web/static/fonts/noto-serif/NotoSerif-Variable.woff2 similarity index 100% rename from web/static/fonts/noto-serif/NotoSerif-VariableFont_wdth,wght.woff2 rename to web/static/fonts/noto-serif/NotoSerif-Variable.woff2 diff --git a/web/static/fonts/source-serif/SourceSerif4-Italic-VariableFont_opsz,wght.woff2 b/web/static/fonts/source-serif/SourceSerif4-Italic-Variable.woff2 similarity index 100% rename from web/static/fonts/source-serif/SourceSerif4-Italic-VariableFont_opsz,wght.woff2 rename to web/static/fonts/source-serif/SourceSerif4-Italic-Variable.woff2 diff --git a/web/static/fonts/source-serif/SourceSerif4-VariableFont_opsz,wght.woff2 b/web/static/fonts/source-serif/SourceSerif4-Variable.woff2 similarity index 100% rename from web/static/fonts/source-serif/SourceSerif4-VariableFont_opsz,wght.woff2 rename to web/static/fonts/source-serif/SourceSerif4-Variable.woff2 diff --git a/web/static/reader-fonts.css b/web/static/reader-fonts.css new file mode 100644 index 0000000..257e7ea --- /dev/null +++ b/web/static/reader-fonts.css @@ -0,0 +1,229 @@ +/* Libre reading fonts for Bookhoard ebook reader */ + +/* Literata - Designed for Google Play Books */ +@font-face { + font-family: "Literata"; + src: url("/static/fonts/literata/Literata-Variable.woff2") format("woff2"); + font-weight: 400; + font-style: normal; +} +@font-face { + font-family: "Literata"; + src: url("/static/fonts/literata/Literata-Variable.woff2") format("woff2"); + font-weight: 700; + font-style: normal; +} +@font-face { + font-family: "Literata"; + src: url("/static/fonts/literata/Literata-Italic-Variable.woff2") + format("woff2"); + font-weight: 400; + font-style: italic; +} +@font-face { + font-family: "Literata"; + src: url("/static/fonts/literata/Literata-Italic-Variable.woff2") + format("woff2"); + font-weight: 700; + font-style: italic; +} + +/* Crimson Text - Optimized for screen reading */ +@font-face { + font-family: "Crimson Pro"; + src: url("/static/fonts/crimson/CrimsonPro-Variable.woff2") format("woff2"); + font-weight: 400; + font-style: normal; +} +@font-face { + font-family: "Crimson Pro"; + src: url("/static/fonts/crimson/CrimsonPro-Variable.woff2") format("woff2"); + font-weight: 700; + font-style: normal; +} +@font-face { + font-family: "Crimson Pro"; + src: url("/static/fonts/crimson/CrimsonPro-Italic-Variable.woff2") + format("woff2"); + font-weight: 400; + font-style: italic; +} +@font-face { + font-family: "Crimson Pro"; + src: url("/static/fonts/crimson/CrimsonPro-Italic-Variable.woff2") + format("woff2"); + font-weight: 700; + font-style: italic; +} + +/* Source Serif 4 - Adobe professional quality */ +@font-face { + font-family: "Source Serif 4"; + src: url("/static/fonts/source-serif/SourceSerif4-Variable.woff2") + format("woff2"); + font-weight: 400; + font-style: normal; +} +@font-face { + font-family: "Source Serif 4"; + src: url("/static/fonts/source-serif/SourceSerif4-Variable.woff2") + format("woff2"); + font-weight: 700; + font-style: normal; +} +@font-face { + font-family: "Source Serif 4"; + src: url("/static/fonts/source-serif/SourceSerif4-Italic-Variable.woff2") + format("woff2"); + font-weight: 400; + font-style: italic; +} +@font-face { + font-family: "Source Serif 4"; + src: url("/static/fonts/source-serif/SourceSerif4-Italic-Variable.woff2") + format("woff2"); + font-weight: 700; + font-style: italic; +} + +/* EB Garamond - Classic elegance */ +@font-face { + font-family: "EB Garamond"; + src: url("/static/fonts/eb-garamond/EBGaramond-Variable.woff2") + format("woff2"); + font-weight: 400; + font-style: normal; +} +@font-face { + font-family: "EB Garamond"; + src: url("/static/fonts/eb-garamond/EBGaramond-Variable.woff2") + format("woff2"); + font-weight: 700; + font-style: normal; +} +@font-face { + font-family: "EB Garamond"; + src: url("/static/fonts/eb-garamond/EBGaramond-Italic-Variable.woff2") + format("woff2"); + font-weight: 400; + font-style: italic; +} +@font-face { + font-family: "EB Garamond"; + src: url("/static/fonts/eb-garamond/EBGaramond-Italic-Variable.woff2") + format("woff2"); + font-weight: 700; + font-style: italic; +} + +/* Libertinus Serif - Academic/technical */ +@font-face { + font-family: "Libertinus Serif"; + src: url("/static/fonts/libertinus/LibertinusSerif-Regular.woff2") + format("woff2"); + font-weight: 400; + font-style: normal; +} +@font-face { + font-family: "Libertinus Serif"; + src: url("/static/fonts/libertinus/LibertinusSerif-Bold.woff2") + format("woff2"); + font-style: normal; + font-weight: 700; +} +@font-face { + font-family: "Libertinus Serif"; + src: url("/static/fonts/libertinus/LibertinusSerif-Italic.woff2") + format("woff2"); + font-weight: 400; + font-style: italic; +} +@font-face { + font-family: "Libertinus Serif"; + src: url("/static/fonts/libertinus/LibertinusSerif-BoldItalic.woff2") + format("woff2"); + font-weight: 700; + font-style: italic; +} + +/* Noto Serif - Maximum language support */ +@font-face { + font-family: "Noto Serif"; + src: url("/static/fonts/noto-serif/NotoSerif-Variable.woff2") format("woff2"); + font-weight: 400; + font-style: normal; +} +@font-face { + font-family: "Noto Serif"; + src: url("/static/fonts/noto-serif/NotoSerif-Variable.woff2") format("woff2"); + font-weight: 700; + font-style: normal; +} +@font-face { + font-family: "Noto Serif"; + src: url("/static/fonts/noto-serif/NotoSerif-Italic-Variable.woff2") + format("woff2"); + font-weight: 400; + font-style: italic; +} +@font-face { + font-family: "Noto Serif"; + src: url("/static/fonts/noto-serif/NotoSerif-Italic-Variable.woff2") + format("woff2"); + font-weight: 700; + font-style: italic; +} + +/* Charis SIL - Multilingual specialist */ +@font-face { + font-family: "Charis SIL"; + src: url("/static/fonts/charis-sil/CharisSIL-Regular.woff2") format("woff2"); + font-weight: 400; + font-style: normal; +} +@font-face { + font-family: "Charis SIL"; + src: url("/static/fonts/charis-sil/CharisSIL-Bold.woff2") format("woff2"); + font-weight: 700; + font-style: normal; +} +@font-face { + font-family: "Charis SIL"; + src: url("/static/fonts/charis-sil/CharisSIL-Italic.woff2") format("woff2"); + font-weight: 400; + font-style: italic; +} +@font-face { + font-family: "Charis SIL"; + src: url("/static/fonts/charis-sil/CharisSIL-BoldItalic.woff2") + format("woff2"); + font-weight: 700; + font-style: italic; +} + +/* IBM Plex Serif - Modern & versatile */ +@font-face { + font-family: "IBM Plex Serif"; + src: url("/static/fonts/ibm-plex/IBMPlexSerif-Regular.woff2") format("woff2"); + font-weight: 400; + font-style: normal; +} +@font-face { + font-family: "IBM Plex Serif"; + src: url("/static/fonts/ibm-plex/IBMPlexSerif-Bold.woff2") format("woff2"); + font-weight: 700; + font-style: normal; +} +@font-face { + font-family: "IBM Plex Serif"; + src: url("/static/fonts/ibm-plex/IBMPlexSerif-Italic.woff2") format("woff2"); + font-weight: 400; + font-style: italic; +} +@font-face { + font-family: "IBM Plex Serif"; + src: url("/static/fonts/ibm-plex/IBMPlexSerif-BoldItalic.woff2") + format("woff2"); + font-weight: 700; + font-style: italic; +}