@readium/navigator
Version:
Next generation SDK for publications in Web Apps
536 lines (489 loc) • 20.9 kB
text/typescript
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
});
}
}