UNPKG

@readium/navigator

Version:

Next generation SDK for publications in Web Apps

536 lines (489 loc) 20.9 kB
import { Layout, Metadata, ReadingProgression } from "@readium/shared"; import { IPreferencesEditor } from "../../preferences/PreferencesEditor"; import { EpubPreferences } from "./EpubPreferences"; import { EpubSettings } from "./EpubSettings"; import { BooleanPreference, EnumPreference, Preference, RangePreference } from "../../preferences/Preference"; import { TextAlignment, filterRangeConfig, fontSizeRangeConfig, fontWeightRangeConfig, fontWidthRangeConfig, letterSpacingRangeConfig, lineHeightRangeConfig, lineLengthRangeConfig, paragraphIndentRangeConfig, paragraphSpacingRangeConfig, wordSpacingRangeConfig } from "../../preferences/Types"; import defaultColors from "@readium/css/css/vars/colors.json"; // WIP: will change cos’ of all the missing pieces export class EpubPreferencesEditor implements IPreferencesEditor { preferences: EpubPreferences; private settings: EpubSettings; private metadata: Metadata | null; private layout: Layout; constructor(initialPreferences: EpubPreferences, settings: EpubSettings, metadata: Metadata) { this.preferences = initialPreferences; this.settings = settings; this.metadata = metadata; this.layout = this.metadata?.effectiveLayout || Layout.reflowable; } clear() { this.preferences = new EpubPreferences({ optimalLineLength: 65 }); } private updatePreference<K extends keyof EpubPreferences>(key: K, value: EpubPreferences[K]) { this.preferences[key] = value; } get backgroundColor(): Preference<string> { return new Preference<string>({ initialValue: this.preferences.backgroundColor, effectiveValue: this.settings.backgroundColor || defaultColors.RS__backgroundColor, isEffective: this.preferences.backgroundColor !== null, onChange: (newValue: string | null | undefined) => { this.updatePreference("backgroundColor", newValue || null); } }); } get blendFilter(): BooleanPreference { return new BooleanPreference({ initialValue: this.preferences.blendFilter, effectiveValue: this.settings.blendFilter || false, isEffective: this.preferences.blendFilter !== null, onChange: (newValue: boolean | null | undefined) => { this.updatePreference("blendFilter", newValue || null); } }); } get columnCount(): Preference<number> { return new Preference<number>({ initialValue: this.preferences.columnCount, effectiveValue: this.settings.columnCount || null, isEffective: this.layout !== Layout.fixed && !this.settings.scroll, onChange: (newValue: number | null | undefined) => { this.updatePreference("columnCount", newValue || null); } }); } get constraint(): Preference<number> { return new Preference<number>({ initialValue: this.preferences.constraint, effectiveValue: this.preferences.constraint || 0, isEffective: true, onChange: (newValue: number | null | undefined) => { this.updatePreference("constraint", newValue || null); } }) } get darkenFilter(): RangePreference<number> { return new RangePreference<number>({ initialValue: typeof this.preferences.darkenFilter === "boolean" ? 100 : this.preferences.darkenFilter, effectiveValue: typeof this.settings.darkenFilter === "boolean" ? 100 : this.settings.darkenFilter || 0, isEffective: this.settings.darkenFilter !== null, onChange: (newValue: number | boolean | null | undefined) => { this.updatePreference("darkenFilter", newValue || null); }, supportedRange: filterRangeConfig.range, step: filterRangeConfig.step }); } get deprecatedFontSize(): BooleanPreference { return new BooleanPreference({ initialValue: this.preferences.deprecatedFontSize, effectiveValue: CSS.supports("zoom", "1") ? this.settings.deprecatedFontSize || false : true, isEffective: this.layout !== Layout.fixed, onChange: (newValue: boolean | null | undefined) => { this.updatePreference("deprecatedFontSize", newValue || null); } }); } get fontFamily(): Preference<string> { return new Preference<string>({ initialValue: this.preferences.fontFamily, effectiveValue: this.settings.fontFamily || null, isEffective: this.layout !== Layout.fixed, onChange: (newValue: string | null | undefined) => { this.updatePreference("fontFamily", newValue || null); } }); } get fontSize(): RangePreference<number> { return new RangePreference<number>({ initialValue: this.preferences.fontSize, effectiveValue: this.settings.fontSize || 1, isEffective: this.layout !== Layout.fixed, onChange: (newValue: number | null | undefined) => { this.updatePreference("fontSize", newValue || null); }, supportedRange: fontSizeRangeConfig.range, step: fontSizeRangeConfig.step }); } get fontSizeNormalize(): BooleanPreference { return new BooleanPreference({ initialValue: this.preferences.fontSizeNormalize, effectiveValue: this.settings.fontSizeNormalize || false, isEffective: this.layout !== Layout.fixed && this.preferences.fontSizeNormalize !== null, onChange: (newValue: boolean | null | undefined) => { this.updatePreference("fontSizeNormalize", newValue || null); } }); } get fontOpticalSizing(): BooleanPreference { return new BooleanPreference({ initialValue: this.preferences.fontOpticalSizing, effectiveValue: this.settings.fontOpticalSizing || true, isEffective: this.layout !== Layout.fixed && this.preferences.fontOpticalSizing !== null, onChange: (newValue: boolean | null | undefined) => { this.updatePreference("fontOpticalSizing", newValue || null); } }); } get fontWeight(): RangePreference<number> { return new RangePreference<number>({ initialValue: this.preferences.fontWeight, effectiveValue: this.settings.fontWeight || 400, isEffective: this.layout !== Layout.fixed && this.preferences.fontWeight !== null, onChange: (newValue: number | null | undefined) => { this.updatePreference("fontWeight", newValue || null); }, supportedRange: fontWeightRangeConfig.range, step: fontWeightRangeConfig.step }); } get fontWidth(): RangePreference<number> { return new RangePreference<number>({ initialValue: this.preferences.fontWidth, effectiveValue: this.settings.fontWidth || 100, isEffective: this.layout !== Layout.fixed && this.preferences.fontWidth !== null, onChange: (newValue: number | null | undefined) => { this.updatePreference("fontWidth", newValue || null); }, supportedRange: fontWidthRangeConfig.range, step: fontWidthRangeConfig.step }); } get hyphens(): BooleanPreference { return new BooleanPreference({ initialValue: this.preferences.hyphens, effectiveValue: this.settings.hyphens || false, isEffective: this.layout !== Layout.fixed && this.metadata?.effectiveReadingProgression === ReadingProgression.ltr && this.preferences.hyphens !== null, onChange: (newValue: boolean | null | undefined) => { this.updatePreference("hyphens", newValue || null); } }); } get invertFilter(): RangePreference<number> { return new RangePreference<number>({ initialValue: typeof this.preferences.invertFilter === "boolean" ? 100 : this.preferences.invertFilter, effectiveValue: typeof this.settings.invertFilter === "boolean" ? 100 : this.settings.invertFilter || 0, isEffective: this.settings.invertFilter !== null, onChange: (newValue: number | boolean | null | undefined) => { this.updatePreference("invertFilter", newValue || null); }, supportedRange: filterRangeConfig.range, step: filterRangeConfig.step }); } get invertGaijiFilter(): RangePreference<number> { return new RangePreference<number>({ initialValue: typeof this.preferences.invertGaijiFilter === "boolean" ? 100 : this.preferences.invertGaijiFilter, effectiveValue: typeof this.settings.invertGaijiFilter === "boolean" ? 100 : this.settings.invertGaijiFilter || 0, isEffective: this.preferences.invertGaijiFilter !== null, onChange: (newValue: number | boolean | null | undefined) => { this.updatePreference("invertGaijiFilter", newValue || null); }, supportedRange: filterRangeConfig.range, step: filterRangeConfig.step }); } get iOSPatch(): BooleanPreference { return new BooleanPreference({ initialValue: this.preferences.iOSPatch, effectiveValue: this.settings.iOSPatch || false, isEffective: this.layout !== Layout.fixed, onChange: (newValue: boolean | null | undefined) => { this.updatePreference("iOSPatch", newValue || null); } }); } get iPadOSPatch(): BooleanPreference { return new BooleanPreference({ initialValue: this.preferences.iPadOSPatch, effectiveValue: this.settings.iPadOSPatch || false, isEffective: this.layout !== Layout.fixed, onChange: (newValue: boolean | null | undefined) => { this.updatePreference("iPadOSPatch", newValue || null); } }); } get letterSpacing(): RangePreference<number> { return new RangePreference<number>({ initialValue: this.preferences.letterSpacing, effectiveValue: this.settings.letterSpacing || 0, isEffective: this.layout !== Layout.fixed && this.preferences.letterSpacing !== null, onChange: (newValue: number | null | undefined) => { this.updatePreference("letterSpacing", newValue || null); }, supportedRange: letterSpacingRangeConfig.range, step: letterSpacingRangeConfig.step }); } get ligatures(): BooleanPreference { return new BooleanPreference({ initialValue: this.preferences.ligatures, effectiveValue: this.settings.ligatures || true, isEffective: (() => { // Always respect explicit null (disabled) preference if (this.preferences.ligatures === null) { return false; } // Disable for fixed layout if (this.layout === Layout.fixed) { return false; } // Check for languages/scripts that should disable ligatures // ReadiumCSS does not apply in CJK const primaryLang = this.metadata?.languages?.[0]?.toLowerCase(); if (primaryLang) { // Disable for Chinese, Japanese, Korean, and Traditional Mongolian (mn-Mong) if (["zh", "ja", "ko", "mn-mong"].some(lang => primaryLang.startsWith(lang))) { return false; } } // Enable by default return true; })(), onChange: (newValue: boolean | null | undefined) => { this.updatePreference("ligatures", newValue || null); } }); } get lineHeight(): RangePreference<number> { return new RangePreference<number>({ initialValue: this.preferences.lineHeight, effectiveValue: this.settings.lineHeight, isEffective: this.layout !== Layout.fixed && this.preferences.lineHeight !== null, onChange: (newValue: number | null | undefined) => { this.updatePreference("lineHeight", newValue || null); }, supportedRange: lineHeightRangeConfig.range, step: lineHeightRangeConfig.step }); } get linkColor(): Preference<string> { return new Preference<string>({ initialValue: this.preferences.linkColor, effectiveValue: this.settings.linkColor || defaultColors.RS__linkColor, isEffective: this.layout !== Layout.fixed && this.preferences.linkColor !== null, onChange: (newValue: string | null | undefined) => { this.updatePreference("linkColor", newValue || null); } }); } get maximalLineLength(): RangePreference<number> { return new RangePreference<number>({ initialValue: this.preferences.maximalLineLength, effectiveValue: this.settings.maximalLineLength, isEffective: this.layout !== Layout.fixed, onChange: (newValue: number | null | undefined) => { this.updatePreference("maximalLineLength", newValue); }, supportedRange: lineLengthRangeConfig.range, step: lineLengthRangeConfig.step }); } get minimalLineLength(): RangePreference<number> { return new RangePreference<number>({ initialValue: this.preferences.minimalLineLength, effectiveValue: this.settings.minimalLineLength, isEffective: this.layout !== Layout.fixed, onChange: (newValue: number | null | undefined) => { this.updatePreference("minimalLineLength", newValue); }, supportedRange: lineLengthRangeConfig.range, step: lineLengthRangeConfig.step }); } get noRuby(): BooleanPreference { return new BooleanPreference({ initialValue: this.preferences.noRuby, effectiveValue: this.settings.noRuby || false, isEffective: this.layout !== Layout.fixed && this.metadata?.languages?.includes("ja") || false, onChange: (newValue: boolean | null | undefined) => { this.updatePreference("noRuby", newValue || null); } }); } get optimalLineLength(): RangePreference<number> { return new RangePreference<number>({ initialValue: this.preferences.optimalLineLength, effectiveValue: this.settings.optimalLineLength, isEffective: this.layout !== Layout.fixed, onChange: (newValue: number | null | undefined) => { this.updatePreference("optimalLineLength", newValue as number); }, supportedRange: lineLengthRangeConfig.range, step: lineLengthRangeConfig.step }); } get pageGutter(): Preference<number> { return new Preference<number>({ initialValue: this.preferences.pageGutter, effectiveValue: this.settings.pageGutter, isEffective: this.layout !== Layout.fixed, onChange: (newValue: number | null | undefined) => { this.updatePreference("pageGutter", newValue || null); } }); } get paragraphIndent(): RangePreference<number> { return new RangePreference<number>({ initialValue: this.preferences.paragraphIndent, effectiveValue: this.settings.paragraphIndent || 0, isEffective: this.layout !== Layout.fixed && this.preferences.paragraphIndent !== null, onChange: (newValue: number | null | undefined) => { this.updatePreference("paragraphIndent", newValue || null); }, supportedRange: paragraphIndentRangeConfig.range, step: paragraphIndentRangeConfig.step }); } get paragraphSpacing(): RangePreference<number> { return new RangePreference<number>({ initialValue: this.preferences.paragraphSpacing, effectiveValue: this.settings.paragraphSpacing || 0, isEffective: this.layout !== Layout.fixed && this.preferences.paragraphSpacing !== null, onChange: (newValue: number | null | undefined) => { this.updatePreference("paragraphSpacing", newValue || null); }, supportedRange: paragraphSpacingRangeConfig.range, step: paragraphSpacingRangeConfig.step }); } get scroll(): BooleanPreference { return new BooleanPreference({ initialValue: this.preferences.scroll, effectiveValue: this.settings.scroll || false, isEffective: this.layout !== Layout.fixed, onChange: (newValue: boolean | null | undefined) => { this.updatePreference("scroll", newValue || null); } }); } get scrollPaddingTop(): Preference<number> { return new Preference<number>({ initialValue: this.preferences.scrollPaddingTop, effectiveValue: this.settings.scrollPaddingTop || 0, isEffective: this.layout !== Layout.fixed && !!this.settings.scroll && this.preferences.scrollPaddingTop !== null, onChange: (newValue: number | null | undefined) => { this.updatePreference("scrollPaddingTop", newValue || null); } }); } get scrollPaddingBottom(): Preference<number> { return new Preference<number>({ initialValue: this.preferences.scrollPaddingBottom, effectiveValue: this.settings.scrollPaddingBottom || 0, isEffective: this.layout !== Layout.fixed && !!this.settings.scroll && this.preferences.scrollPaddingBottom !== null, onChange: (newValue: number | null | undefined) => { this.updatePreference("scrollPaddingBottom", newValue || null); } }); } /* get scrollPaddingLeft(): Preference<number> { return new Preference<number>({ initialValue: this.preferences.scrollPaddingLeft, effectiveValue: this.settings.scrollPaddingLeft || 0, isEffective: this.layout !== Layout.fixed && !!this.settings.scroll && this.preferences.scrollPaddingLeft !== null, onChange: (newValue: number | null | undefined) => { this.updatePreference("scrollPaddingLeft", newValue || null); } }); } get scrollPaddingRight(): Preference<number> { return new Preference<number>({ initialValue: this.preferences.scrollPaddingRight, effectiveValue: this.settings.scrollPaddingRight || 0, isEffective: this.layout !== Layout.fixed && !!this.settings.scroll && this.preferences.scrollPaddingRight !== null, onChange: (newValue: number | null | undefined) => { this.updatePreference("scrollPaddingRight", newValue || null); } }); } */ get selectionBackgroundColor(): Preference<string> { return new Preference<string>({ initialValue: this.preferences.selectionBackgroundColor, effectiveValue: this.settings.selectionBackgroundColor || defaultColors.RS__selectionBackgroundColor, isEffective: this.layout !== Layout.fixed && this.preferences.selectionBackgroundColor !== null, onChange: (newValue: string | null | undefined) => { this.updatePreference("selectionBackgroundColor", newValue || null); } }); } get selectionTextColor(): Preference<string> { return new Preference<string>({ initialValue: this.preferences.selectionTextColor, effectiveValue: this.settings.selectionTextColor || defaultColors.RS__selectionTextColor, isEffective: this.layout !== Layout.fixed && this.preferences.selectionTextColor !== null, onChange: (newValue: string | null | undefined) => { this.updatePreference("selectionTextColor", newValue || null); } }); } get textAlign(): EnumPreference<TextAlignment> { return new EnumPreference<TextAlignment>({ initialValue: this.preferences.textAlign, effectiveValue: this.settings.textAlign || TextAlignment.start, isEffective: this.layout !== Layout.fixed && this.preferences.textAlign !== null, onChange: (newValue: TextAlignment | null | undefined) => { this.updatePreference("textAlign", newValue || null); }, supportedValues: Object.values(TextAlignment) }); } get textColor(): Preference<string> { return new Preference<string>({ initialValue: this.preferences.textColor, effectiveValue: this.settings.textColor || defaultColors.RS__textColor, isEffective: this.layout !== Layout.fixed && this.preferences.textColor !== null, onChange: (newValue: string | null | undefined) => { this.updatePreference("textColor", newValue || null); } }); } get textNormalization(): BooleanPreference { return new BooleanPreference({ initialValue: this.preferences.textNormalization, effectiveValue: this.settings.textNormalization || false, isEffective: this.layout !== Layout.fixed, onChange: (newValue: boolean | null | undefined) => { this.updatePreference("textNormalization", newValue || null); } }); } get visitedColor(): Preference<string> { return new Preference<string>({ initialValue: this.preferences.visitedColor, effectiveValue: this.settings.visitedColor || defaultColors.RS__visitedColor, isEffective: this.layout !== Layout.fixed && this.preferences.visitedColor !== null, onChange: (newValue: string | null | undefined) => { this.updatePreference("visitedColor", newValue || null); } }); } get wordSpacing(): RangePreference<number> { return new RangePreference<number>({ initialValue: this.preferences.wordSpacing, effectiveValue: this.settings.wordSpacing || 0, isEffective: this.layout !== Layout.fixed && this.preferences.wordSpacing !== null, onChange: (newValue: number | null | undefined) => { this.updatePreference("wordSpacing", newValue || null); }, supportedRange: wordSpacingRangeConfig.range, step: wordSpacingRangeConfig.step }); } }