From 1dbbee440a23f76108cd3a498f3b4926b5a8c6bf Mon Sep 17 00:00:00 2001 From: Jiminy Panoz Date: Thu, 6 Nov 2025 16:00:48 +0100 Subject: [PATCH 1/4] Update ReadiumCSS --- navigator/CHANGELOG.MD | 7 +++++++ navigator/package.json | 4 ++-- pnpm-lock.yaml | 10 +++++----- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/navigator/CHANGELOG.MD b/navigator/CHANGELOG.MD index 96b7a54f..607474ef 100644 --- a/navigator/CHANGELOG.MD +++ b/navigator/CHANGELOG.MD @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.2.4] - 2025-11-06 + +### Changed + +- Updated ReadiumCSS to `v2.0.0-beta.22` +- Switched experimental WebPublication Navigator Preferences to ReadiumCSS + ## [2.2.3] - 2025-11-01 ### Fixed diff --git a/navigator/package.json b/navigator/package.json index 0e7fad0f..89b739d6 100644 --- a/navigator/package.json +++ b/navigator/package.json @@ -1,6 +1,6 @@ { "name": "@readium/navigator", - "version": "2.2.3", + "version": "2.2.4", "type": "module", "description": "Next generation SDK for publications in Web Apps", "author": "readium", @@ -49,7 +49,7 @@ }, "devDependencies": { "@laynezh/vite-plugin-lib-assets": "^2.1.0", - "@readium/css": "2.0.0-beta.20", + "@readium/css": "2.0.0-beta.22", "@readium/navigator-html-injectables": "workspace:*", "@readium/shared": "workspace:*", "@types/path-browserify": "^1.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a95a5b11..46351d78 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,8 @@ importers: specifier: ^2.1.0 version: 2.1.0(vite@7.1.5(@types/node@24.3.0)(jiti@2.5.1)(less@4.4.1)(sass@1.91.0)(stylus@0.62.0)) '@readium/css': - specifier: 2.0.0-beta.20 - version: 2.0.0-beta.20 + specifier: 2.0.0-beta.22 + version: 2.0.0-beta.22 '@readium/navigator-html-injectables': specifier: workspace:* version: link:../navigator-html-injectables @@ -1264,8 +1264,8 @@ packages: '@readium/css@1.1.0': resolution: {integrity: sha512-vcUx/+UYlWXuG6ioZNVBFDlKCuyH+65x9dNJM9jLlA8yT5ReH0k2UR9DN8cwx5/BgJhoQLUsA9s2DPhGaMhX6A==} - '@readium/css@2.0.0-beta.20': - resolution: {integrity: sha512-69Ai/vZE/1V29lpqlcPHeaPIUlKDw9GrviJxD5tDbM6vPflJHOskPHSKFQ54tgxd75hgoFgBEVu7CCdfA2HRpA==} + '@readium/css@2.0.0-beta.22': + resolution: {integrity: sha512-mWQ5Rl7n74EofItTo+nKXHUjCMK57H+UYMtiuCX9PHED8wwaanmnQpNyWx9QL6a25iOMF4efg4+LKGrS8rIJjg==} '@rollup/rollup-android-arm-eabi@4.50.1': resolution: {integrity: sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag==} @@ -4086,7 +4086,7 @@ snapshots: '@readium/css@1.1.0': {} - '@readium/css@2.0.0-beta.20': {} + '@readium/css@2.0.0-beta.22': {} '@rollup/rollup-android-arm-eabi@4.50.1': optional: true From b81fa2c1c05291e2f1a2a6b03ff4d3cc85a6cd95 Mon Sep 17 00:00:00 2001 From: Jiminy Panoz Date: Thu, 6 Nov 2025 16:19:39 +0100 Subject: [PATCH 2/4] Update WebPub Navigator for ReadiumCSS --- .../epub/preferences/EpubPreferencesEditor.ts | 1 - navigator/src/webpub/WebPubBlobBuilder.ts | 25 ++- navigator/src/webpub/css/Properties.ts | 8 + navigator/src/webpub/css/WebPubCSS.ts | 2 + navigator/src/webpub/css/WebPubStylesheet.ts | 205 ------------------ navigator/src/webpub/css/index.ts | 3 +- .../src/webpub/preferences/WebPubDefaults.ts | 12 + .../webpub/preferences/WebPubPreferences.ts | 6 + .../preferences/WebPubPreferencesEditor.ts | 22 ++ .../src/webpub/preferences/WebPubSettings.ts | 17 ++ 10 files changed, 85 insertions(+), 216 deletions(-) delete mode 100644 navigator/src/webpub/css/WebPubStylesheet.ts diff --git a/navigator/src/epub/preferences/EpubPreferencesEditor.ts b/navigator/src/epub/preferences/EpubPreferencesEditor.ts index e9b8ce37..dbd26cc6 100644 --- a/navigator/src/epub/preferences/EpubPreferencesEditor.ts +++ b/navigator/src/epub/preferences/EpubPreferencesEditor.ts @@ -257,7 +257,6 @@ export class EpubPreferencesEditor implements IPreferencesEditor { initialValue: this.preferences.ligatures, effectiveValue: this.settings.ligatures || true, isEffective: this.layout !== Layout.fixed - && this.metadata?.languages?.some(lang => lang === "ar" || lang === "fa") && this.preferences.ligatures !== null || false, onChange: (newValue: boolean | null | undefined) => { this.updatePreference("ligatures", newValue || null); diff --git a/navigator/src/webpub/WebPubBlobBuilder.ts b/navigator/src/webpub/WebPubBlobBuilder.ts index b390d601..d4175c4b 100644 --- a/navigator/src/webpub/WebPubBlobBuilder.ts +++ b/navigator/src/webpub/WebPubBlobBuilder.ts @@ -1,9 +1,16 @@ import { Link, Publication } from "@readium/shared"; -import { webPubStylesheet } from "./css/WebPubStylesheet"; -// Utilities (matching FrameBlobBuilder pattern) +// Readium CSS imports +// The "?inline" query is to prevent some bundlers from injecting these into the page (e.g. vite) +// @ts-ignore +import readiumCSSWebPub from "@readium/css/css/dist/webPub/ReadiumCSS-webPub.css?inline"; + +// Utilities const blobify = (source: string, type: string) => URL.createObjectURL(new Blob([source], { type })); const stripJS = (source: string) => source.replace(/\/\/.*/g, "").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\n/g, "").replace(/\s+/g, " "); +const stripCSS = (source: string) => source.replace(/\/\*(?:(?!\*\/)[\s\S])*\*\/|[\r\n\t]+/g, '').replace(/ {2,}/g, ' ') + // Fully resolve absolute local URLs created by bundlers since it's going into a blob + .replace(/url\((?!(https?:)?\/\/)("?)\/([^\)]+)/g, `url($2${window.location.origin}/$3`); const scriptify = (doc: Document, source: string) => { const s = doc.createElement("script"); s.dataset.readium = "true"; @@ -11,16 +18,18 @@ const scriptify = (doc: Document, source: string) => { return s; } const styleify = (doc: Document, source: string) => { - const s = doc.createElement("style"); + const s = doc.createElement("link"); s.dataset.readium = "true"; - s.textContent = source; + s.rel = "stylesheet"; + s.type = "text/css"; + s.href = source.startsWith("blob:") ? source : blobify(source, "text/css"); return s; } type CacheFunction = () => string; const resourceBlobCache = new Map(); const cached = (key: string, cacher: CacheFunction) => { - if (resourceBlobCache.has(key)) return resourceBlobCache.get(key)!; + if(resourceBlobCache.has(key)) return resourceBlobCache.get(key)!; const value = cacher(); resourceBlobCache.set(key, value); return value; @@ -103,9 +112,9 @@ export class WebPubBlobBuilder { private finalizeDOM(doc: Document, base: string | undefined, mediaType: any, txt?: string, cssProperties?: { [key: string]: string }): string { if(!doc) return ""; - // Add WebPubCSS stylesheet at end of head (like EPUB ReadiumCSS-after) - const webPubStyle = styleify(doc, webPubStylesheet); - doc.head.appendChild(webPubStyle); + // ReadiumCSS WebPub + doc.head.appendChild(styleify(doc, cached("ReadiumCSS-webpub", () => blobify(stripCSS(readiumCSSWebPub), "text/css")))); + if (cssProperties) { this.setProperties(cssProperties, doc); } diff --git a/navigator/src/webpub/css/Properties.ts b/navigator/src/webpub/css/Properties.ts index b0728548..eb515331 100644 --- a/navigator/src/webpub/css/Properties.ts +++ b/navigator/src/webpub/css/Properties.ts @@ -6,6 +6,8 @@ export interface IWebUserProperties { bodyHyphens?: BodyHyphens | null; fontFamily?: string | null; fontWeight?: number | null; + iOSPatch?: boolean | null; + iPadOSPatch?: boolean | null; letterSpacing?: number | null; ligatures?: Ligatures | null; lineHeight?: number | null; @@ -22,6 +24,8 @@ export class WebUserProperties extends Properties { bodyHyphens: BodyHyphens | null; fontFamily: string | null; fontWeight: number | null; + iOSPatch: boolean | null; + iPadOSPatch: boolean | null; letterSpacing: number | null; ligatures: Ligatures | null; lineHeight: number | null; @@ -38,6 +42,8 @@ export class WebUserProperties extends Properties { this.bodyHyphens = props.bodyHyphens ?? null; this.fontFamily = props.fontFamily ?? null; this.fontWeight = props.fontWeight ?? null; + this.iOSPatch = props.iOSPatch ?? null; + this.iPadOSPatch = props.iPadOSPatch ?? null; this.letterSpacing = props.letterSpacing ?? null; this.ligatures = props.ligatures ?? null; this.lineHeight = props.lineHeight ?? null; @@ -56,6 +62,8 @@ export class WebUserProperties extends Properties { if (this.bodyHyphens) cssProperties["--USER__bodyHyphens"] = this.bodyHyphens; if (this.fontFamily) cssProperties["--USER__fontFamily"] = this.fontFamily; if (this.fontWeight != null) cssProperties["--USER__fontWeight"] = this.toUnitless(this.fontWeight); + if (this.iOSPatch) cssProperties["--USER__iOSPatch"] = this.toFlag("iOSPatch"); + if (this.iPadOSPatch) cssProperties["--USER__iPadOSPatch"] = this.toFlag("iPadOSPatch"); if (this.letterSpacing != null) cssProperties["--USER__letterSpacing"] = this.toRem(this.letterSpacing); if (this.ligatures) cssProperties["--USER__ligatures"] = this.ligatures; if (this.lineHeight != null) cssProperties["--USER__lineHeight"] = this.toUnitless(this.lineHeight); diff --git a/navigator/src/webpub/css/WebPubCSS.ts b/navigator/src/webpub/css/WebPubCSS.ts index d73d2e26..754caa13 100644 --- a/navigator/src/webpub/css/WebPubCSS.ts +++ b/navigator/src/webpub/css/WebPubCSS.ts @@ -22,6 +22,8 @@ export class WebPubCSS { : "none", fontFamily: settings.fontFamily, fontWeight: settings.fontWeight, + iOSPatch: settings.iOSPatch, + iPadOSPatch: settings.iPadOSPatch, letterSpacing: settings.letterSpacing, ligatures: typeof settings.ligatures !== "boolean" ? null diff --git a/navigator/src/webpub/css/WebPubStylesheet.ts b/navigator/src/webpub/css/WebPubStylesheet.ts deleted file mode 100644 index 45daab7f..00000000 --- a/navigator/src/webpub/css/WebPubStylesheet.ts +++ /dev/null @@ -1,205 +0,0 @@ -// WebPubCSS is equivalent to ReadiumCSS for WebPub -// Respects the stylesheet order from after - -export const webPubStylesheet = ` -/* TextAlign */ - -:root[style*="--USER__textAlign"] { - text-align: var(--USER__textAlign); -} - -:root[style*="--USER__textAlign"] body, -:root[style*="--USER__textAlign"] p:not([class*="title"]):not(blockquote p):not(figcaption p):not(header p):not(hgroup p):not(div:has(+ *) > h1 + p):not(div:has(+ *) > p:has(+ h1)), -:root[style*="--USER__textAlign"] li, -:root[style*="--USER__textAlign"] dd { - text-align: var(--USER__textAlign) !important; - -moz-text-align-last: auto !important; - -epub-text-align-last: auto !important; - text-align-last: auto !important; -} - -/* Hyphens */ - -:root[style*="--USER__bodyHyphens"] { - -webkit-hyphens: var(--USER__bodyHyphens) !important; - -moz-hyphens: var(--USER__bodyHyphens) !important; - -ms-hyphens: var(--USER__bodyHyphens) !important; - -epub-hyphens: var(--USER__bodyHyphens) !important; - hyphens: var(--USER__bodyHyphens) !important; -} - -:root[style*="--USER__bodyHyphens"] body, -:root[style*="--USER__bodyHyphens"] p, -:root[style*="--USER__bodyHyphens"] li, -:root[style*="--USER__bodyHyphens"] div, -:root[style*="--USER__bodyHyphens"] dd { - -webkit-hyphens: inherit; - -moz-hyphens: inherit; - -ms-hyphens: inherit; - -epub-hyphens: inherit; - hyphens: inherit; -} - -/* FontFamily */ - -:root[style*="--USER__fontFamily"] { - font-family: var(--USER__fontFamily) !important; -} - -:root[style*="--USER__fontFamily"] * { - font-family: revert !important; -} - -/* TextNormalize */ - -:root[style*="readium-a11y-on"] { - font-weight: normal !important; - font-style: normal !important; -} - -:root[style*="readium-a11y-on"] body *:not(code):not(var):not(kbd):not(samp) { - font-family: inherit !important; - font-weight: inherit !important; - font-style: inherit !important; -} - -:root[style*="readium-a11y-on"] body * { - text-decoration: none !important; - font-variant-caps: normal !important; - font-variant-position: normal !important; - font-variant-numeric: normal !important; -} - -:root[style*="readium-a11y-on"] sup, -:root[style*="readium-a11y-on"] sub { - font-size: 1rem !important; - vertical-align: baseline !important; -} - -/* Zoom */ - -:root { - --USER__zoom: 1; -} - -:root[style*="--USER__zoom"] body { - zoom: var(--USER__zoom) !important; -} - -@supports selector(figure:has(> img)) { - :root[style*="--USER__zoom"] figure:has(> img), - :root[style*="--USER__zoom"] figure:has(> video), - :root[style*="--USER__zoom"] figure:has(> svg), - :root[style*="--USER__zoom"] figure:has(> canvas), - :root[style*="--USER__zoom"] figure:has(> iframe), - :root[style*="--USER__zoom"] figure:has(> audio), - :root[style*="--USER__zoom"] div:has(> img), - :root[style*="--USER__zoom"] div:has(> video), - :root[style*="--USER__zoom"] div:has(> svg), - :root[style*="--USER__zoom"] div:has(> canvas), - :root[style*="--USER__zoom"] div:has(> iframe), - :root[style*="--USER__zoom"] div:has(> audio), - :root[style*="--USER__zoom"] table { - zoom: calc(100% / var(--USER__zoom)) !important; - } - - :root[style*="--USER__zoom"] figcaption, - :root[style*="--USER__zoom"] caption, - :root[style*="--USER__zoom"] td, - :root[style*="--USER__zoom"] th { - zoom: var(--USER__zoom) !important; - } -} - -/* LineHeight */ - -:root[style*="--USER__lineHeight"] { - line-height: var(--USER__lineHeight) !important; -} - -:root[style*="--USER__lineHeight"] body, -:root[style*="--USER__lineHeight"] p, -:root[style*="--USER__lineHeight"] li, -:root[style*="--USER__lineHeight"] div { - line-height: inherit; -} - -/* ParagraphSpacing */ - -:root[style*="--USER__paraSpacing"] p { - margin-block: var(--USER__paraSpacing) !important; -} - -/* ParagraphIndent */ - -:root[style*="--USER__paraIndent"] p { - text-indent: var(--USER__paraIndent) !important; -} - -:root[style*="--USER__paraIndent"] p *, -:root[style*="--USER__paraIndent"] p:first-letter { - text-indent: 0 !important; -} - -/* WordSpacing */ - -:root[style*="--USER__wordSpacing"] h1, -:root[style*="--USER__wordSpacing"] h2, -:root[style*="--USER__wordSpacing"] h3, -:root[style*="--USER__wordSpacing"] h4, -:root[style*="--USER__wordSpacing"] h5, -:root[style*="--USER__wordSpacing"] h6, -:root[style*="--USER__wordSpacing"] p, -:root[style*="--USER__wordSpacing"] li, -:root[style*="--USER__wordSpacing"] div, -:root[style*="--USER__wordSpacing"] dt, -:root[style*="--USER__wordSpacing"] dd { - word-spacing: var(--USER__wordSpacing); -} - -/* LetterSpacing */ - -:root[style*="--USER__letterSpacing"] h1, -:root[style*="--USER__letterSpacing"] h2, -:root[style*="--USER__letterSpacing"] h3, -:root[style*="--USER__letterSpacing"] h4, -:root[style*="--USER__letterSpacing"] h5, -:root[style*="--USER__letterSpacing"] h6, -:root[style*="--USER__letterSpacing"] p, -:root[style*="--USER__letterSpacing"] li, -:root[style*="--USER__letterSpacing"] div, -:root[style*="--USER__letterSpacing"] dt, -:root[style*="--USER__letterSpacing"] dd { - letter-spacing: var(--USER__letterSpacing); - font-variant: none; -} - -/* FontWeight */ - -:root[style*="--USER__fontWeight"] body { - font-weight: var(--USER__fontWeight) !important; -} - -/* Attempt to handle known bolds */ -:root[style*="--USER__fontWeight"] b, -:root[style*="--USER__fontWeight"] strong { - font-weight: bolder; -} - -/* Ruby */ - -:root[style*="readium-noRuby-on"] body rt, -:root[style*="readium-noRuby-on"] body rp { - display: none; -} - -/* Ligatures */ - -:root[style*="--USER__ligatures"] { - font-variant-ligatures: var(--USER__ligatures) !important; -} - -:root[style*="--USER__ligatures"] * { - font-variant-ligatures: inherit !important; -} -`; \ No newline at end of file diff --git a/navigator/src/webpub/css/index.ts b/navigator/src/webpub/css/index.ts index 64abcd62..282a400f 100644 --- a/navigator/src/webpub/css/index.ts +++ b/navigator/src/webpub/css/index.ts @@ -1,3 +1,2 @@ export * from "./Properties"; -export * from "./WebPubCSS"; -export * from "./WebPubStylesheet"; \ No newline at end of file +export * from "./WebPubCSS"; \ No newline at end of file diff --git a/navigator/src/webpub/preferences/WebPubDefaults.ts b/navigator/src/webpub/preferences/WebPubDefaults.ts index 02be5531..f299d441 100644 --- a/navigator/src/webpub/preferences/WebPubDefaults.ts +++ b/navigator/src/webpub/preferences/WebPubDefaults.ts @@ -12,10 +12,14 @@ import { ensureString } from "../../preferences/guards"; +import { sMLWithRequest } from "../../helpers"; + export interface IWebPubDefaults { fontFamily?: string | null, fontWeight?: number | null, hyphens?: boolean | null, + iOSPatch?: boolean | null, + iPadOSPatch?: boolean | null, letterSpacing?: number | null, ligatures?: boolean | null, lineHeight?: number | null, @@ -32,6 +36,8 @@ export class WebPubDefaults { fontFamily: string | null; fontWeight: number | null; hyphens: boolean | null; + iOSPatch: boolean | null; + iPadOSPatch: boolean | null; letterSpacing: number | null; ligatures: boolean | null; lineHeight: number | null; @@ -47,6 +53,12 @@ export class WebPubDefaults { this.fontFamily = ensureString(defaults.fontFamily) || null; this.fontWeight = ensureValueInRange(defaults.fontWeight, fontWeightRangeConfig.range) || null; this.hyphens = ensureBoolean(defaults.hyphens) ?? null; + this.iOSPatch = defaults.iOSPatch === false + ? false + : ((sMLWithRequest.OS.iOS || sMLWithRequest.OS.iPadOS) && sMLWithRequest.iOSRequest === "mobile"); + this.iPadOSPatch = defaults.iPadOSPatch === false + ? false + : (sMLWithRequest.OS.iPadOS && sMLWithRequest.iOSRequest === "desktop"); this.letterSpacing = ensureNonNegative(defaults.letterSpacing) || null; this.ligatures = ensureBoolean(defaults.ligatures) ?? null; this.lineHeight = ensureNonNegative(defaults.lineHeight) || null; diff --git a/navigator/src/webpub/preferences/WebPubPreferences.ts b/navigator/src/webpub/preferences/WebPubPreferences.ts index ace267bb..88727a56 100644 --- a/navigator/src/webpub/preferences/WebPubPreferences.ts +++ b/navigator/src/webpub/preferences/WebPubPreferences.ts @@ -18,6 +18,8 @@ export interface IWebPubPreferences { fontFamily?: string | null, fontWeight?: number | null, hyphens?: boolean | null, + iOSPatch?: boolean | null, + iPadOSPatch?: boolean | null, letterSpacing?: number | null, ligatures?: boolean | null, lineHeight?: number | null, @@ -34,6 +36,8 @@ export class WebPubPreferences implements ConfigurablePreferences { fontFamily?: string | null; fontWeight?: number | null; hyphens?: boolean | null; + iOSPatch?: boolean | null; + iPadOSPatch?: boolean | null; letterSpacing?: number | null; ligatures?: boolean | null; lineHeight?: number | null; @@ -49,6 +53,8 @@ export class WebPubPreferences implements ConfigurablePreferences { this.fontFamily = ensureString(preferences.fontFamily); this.fontWeight = ensureValueInRange(preferences.fontWeight, fontWeightRangeConfig.range); this.hyphens = ensureBoolean(preferences.hyphens); + this.iOSPatch = ensureBoolean(preferences.iOSPatch); + this.iPadOSPatch = ensureBoolean(preferences.iPadOSPatch); this.letterSpacing = ensureNonNegative(preferences.letterSpacing); this.ligatures = ensureBoolean(preferences.ligatures); this.lineHeight = ensureNonNegative(preferences.lineHeight); diff --git a/navigator/src/webpub/preferences/WebPubPreferencesEditor.ts b/navigator/src/webpub/preferences/WebPubPreferencesEditor.ts index 10158a4d..0af0038d 100644 --- a/navigator/src/webpub/preferences/WebPubPreferencesEditor.ts +++ b/navigator/src/webpub/preferences/WebPubPreferencesEditor.ts @@ -74,6 +74,28 @@ export class WebPubPreferencesEditor implements IPreferencesEditor { }); } + get iOSPatch(): BooleanPreference { + return new BooleanPreference({ + initialValue: this.preferences.iOSPatch, + effectiveValue: this.settings.iOSPatch || false, + isEffective: true, + 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: true, + onChange: (newValue: boolean | null | undefined) => { + this.updatePreference("iPadOSPatch", newValue || null); + } + }); + } + get letterSpacing(): RangePreference { return new RangePreference({ initialValue: this.preferences.letterSpacing, diff --git a/navigator/src/webpub/preferences/WebPubSettings.ts b/navigator/src/webpub/preferences/WebPubSettings.ts index ad9b598f..1214b2a7 100644 --- a/navigator/src/webpub/preferences/WebPubSettings.ts +++ b/navigator/src/webpub/preferences/WebPubSettings.ts @@ -3,10 +3,14 @@ import { TextAlignment } from "../../preferences/Types"; import { WebPubDefaults } from "./WebPubDefaults"; import { WebPubPreferences } from "./WebPubPreferences"; +import { sMLWithRequest } from "../../helpers"; + export interface IWebPubSettings { fontFamily?: string | null, fontWeight?: number | null, hyphens?: boolean | null, + iOSPatch?: boolean | null, + iPadOSPatch?: boolean | null, letterSpacing?: number | null, ligatures?: boolean | null, lineHeight?: number | null, @@ -23,6 +27,8 @@ export class WebPubSettings implements ConfigurableSettings { fontFamily: string | null = null; fontWeight: number | null = null; hyphens: boolean | null = null; + iOSPatch: boolean | null = null; + iPadOSPatch: boolean | null = null; letterSpacing: number | null = null; ligatures: boolean | null = null; lineHeight: number | null = null; @@ -45,6 +51,16 @@ export class WebPubSettings implements ConfigurableSettings { this.hyphens = typeof preferences.hyphens === "boolean" ? preferences.hyphens : defaults.hyphens ?? null; + this.iOSPatch = preferences.iOSPatch === false + ? false + : preferences.iOSPatch === true + ? ((sMLWithRequest.OS.iOS || sMLWithRequest.OS.iPadOS) && sMLWithRequest.iOSRequest === "mobile") + : defaults.iOSPatch; + this.iPadOSPatch = preferences.iPadOSPatch === false + ? false + : preferences.iPadOSPatch === true + ? (sMLWithRequest.OS.iPadOS && sMLWithRequest.iOSRequest === "desktop") + : defaults.iPadOSPatch; this.letterSpacing = preferences.letterSpacing !== undefined ? preferences.letterSpacing : defaults.letterSpacing !== undefined @@ -81,6 +97,7 @@ export class WebPubSettings implements ConfigurableSettings { ? defaults.wordSpacing : null; } + this.zoom = preferences.zoom !== undefined ? preferences.zoom : defaults.zoom !== undefined From f42a042f013abd302f7b8079f5ac7933f52784c9 Mon Sep 17 00:00:00 2001 From: Jiminy Panoz Date: Thu, 6 Nov 2025 16:25:49 +0100 Subject: [PATCH 3/4] Update ligatures isEffective check --- .../epub/preferences/EpubPreferencesEditor.ts | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/navigator/src/epub/preferences/EpubPreferencesEditor.ts b/navigator/src/epub/preferences/EpubPreferencesEditor.ts index dbd26cc6..7bb0c6f2 100644 --- a/navigator/src/epub/preferences/EpubPreferencesEditor.ts +++ b/navigator/src/epub/preferences/EpubPreferencesEditor.ts @@ -256,8 +256,30 @@ export class EpubPreferencesEditor implements IPreferencesEditor { return new BooleanPreference({ initialValue: this.preferences.ligatures, effectiveValue: this.settings.ligatures || true, - isEffective: this.layout !== Layout.fixed - && this.preferences.ligatures !== null || false, + 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); } From dad5e6ec725cd91f98a98eb9550c9efb9acb7072 Mon Sep 17 00:00:00 2001 From: Jiminy Panoz Date: Thu, 6 Nov 2025 16:48:26 +0100 Subject: [PATCH 4/4] Complete changelog --- navigator/CHANGELOG.MD | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/navigator/CHANGELOG.MD b/navigator/CHANGELOG.MD index 607474ef..df5e4a36 100644 --- a/navigator/CHANGELOG.MD +++ b/navigator/CHANGELOG.MD @@ -7,10 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [2.2.4] - 2025-11-06 +### Added + +- Added the handling of iOS and iPadOS patches to the WebPub Preferences API + ### Changed - Updated ReadiumCSS to `v2.0.0-beta.22` - Switched experimental WebPublication Navigator Preferences to ReadiumCSS +- Updated ligatures handling in EpubPreferencesEditor to match ReadiumCSS ## [2.2.3] - 2025-11-01