@somecat/epub-reader 0.1.6 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/reader.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import 'foliate-js/view.js'\r\nimport type { EBookReaderHandle, EBookReaderOptions, ProgressInfo, SearchOptions, SearchResult, TocItem } from './types.js'\r\n\r\ntype FoliateViewElement = HTMLElement & {\r\n book?: { toc?: TocItem[] }\r\n renderer?: {\r\n prevSection?: () => void\r\n nextSection?: () => void\r\n setStyles?: (css: string) => void\r\n render?: () => void\r\n expand?: () => void\r\n }\r\n open?: (file: File) => Promise<void>\r\n init?: (options?: unknown) => Promise<void>\r\n goLeft?: () => void\r\n goRight?: () => void\r\n goTo?: (target: string) => Promise<void> | void\r\n goToFraction?: (fraction: number) => Promise<void> | void\r\n search?: (options: unknown) => AsyncIterable<unknown>\r\n clearSearch?: () => void\r\n}\r\n\r\nconst getContentCSS = (fontSize: number, isDark: boolean, lineHeight: number, letterSpacing: number, extraCSS?: string) => `\n@namespace epub \"http://www.idpf.org/2007/ops\";\r\nhtml {\r\n color-scheme: ${isDark ? 'dark' : 'light'} !important;\r\n}\r\nbody {\r\n background-color: transparent !important;\r\n color: ${isDark ? '#e0e0e0' : 'black'} !important;\r\n font-size: ${fontSize}% !important;\r\n line-height: ${lineHeight} !important;\n letter-spacing: ${letterSpacing}em !important;\n}\r\np {\r\n line-height: inherit !important;\n margin-bottom: 1em;\r\n}\r\na {\r\n color: ${isDark ? '#64b5f6' : '#2563eb'} !important;\r\n}\r\nimg {\r\n max-width: 100%;\r\n height: auto;\r\n object-fit: contain;\r\n ${isDark ? 'filter: brightness(0.8) contrast(1.2);' : ''}\r\n}\r\n${extraCSS ?? ''}\r\n`\r\n\r\nexport function createEBookReader(container: HTMLElement, options: EBookReaderOptions = {}): EBookReaderHandle {\r\n if (!container) throw new Error('container is required')\r\n if (!customElements.get('foliate-view')) throw new Error('foliate-view is not defined')\r\n\r\n const {\r\n darkMode: initialDarkMode = false,\r\n fontSize: initialFontSize = 100,\r\n lineHeight: initialLineHeight = 1.6,\n letterSpacing: initialLetterSpacing = 0,\n extraContentCSS,\r\n onReady,\r\n onError,\r\n onProgress,\r\n onToc,\r\n onSearchProgress,\r\n onContentLoad,\r\n } = options\r\n\r\n let destroyed = false\r\n let toc: TocItem[] = []\r\n let fontSize = initialFontSize\r\n let darkMode = initialDarkMode\r\n let lineHeight = initialLineHeight\n let letterSpacing = initialLetterSpacing\n let searchToken = 0\r\n\r\n container.innerHTML = ''\r\n\r\n const viewer = document.createElement('foliate-view') as FoliateViewElement\r\n viewer.style.display = 'block'\r\n viewer.style.width = '100%'\r\n viewer.style.height = '100%'\r\n viewer.setAttribute('margin', '48')\r\n viewer.setAttribute('gap', '0.07')\r\n\r\n const applyStyles = () => {\r\n if (destroyed) return\r\n if (!viewer.renderer?.setStyles) return\r\n viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS))\n requestAnimationFrame(() => {\r\n setTimeout(() => {\r\n if (destroyed) return\r\n viewer.renderer?.render?.()\r\n viewer.renderer?.expand?.()\r\n }, 50)\r\n })\r\n }\r\n\r\n const handleLoad = (e: Event) => {\r\n applyStyles()\r\n const detail = (e as CustomEvent).detail as { doc?: Document } | undefined\r\n if (detail?.doc) onContentLoad?.(detail.doc)\r\n }\r\n\r\n const handleRelocate = (e: Event) => {\r\n const detail = (e as CustomEvent).detail as ProgressInfo\r\n onProgress?.(detail)\r\n }\r\n\r\n viewer.addEventListener('load', handleLoad as EventListener)\r\n viewer.addEventListener('relocate', handleRelocate as EventListener)\r\n\r\n container.appendChild(viewer)\r\n\r\n const handle: EBookReaderHandle = {\r\n async open(file) {\r\n if (destroyed) return\r\n if (!file) return\r\n\r\n try {\r\n viewer.clearSearch?.()\r\n searchToken++\r\n\r\n await viewer.open?.(file)\r\n\r\n const nextToc = viewer.book?.toc ?? []\r\n toc = nextToc\r\n onToc?.(toc)\r\n\r\n await viewer.init?.({ showTextStart: true })\r\n applyStyles()\r\n } catch (error) {\r\n onError?.(error)\r\n throw error\r\n }\r\n },\r\n destroy() {\r\n if (destroyed) return\r\n destroyed = true\r\n searchToken++\r\n viewer.removeEventListener('load', handleLoad)\r\n viewer.removeEventListener('relocate', handleRelocate as EventListener)\r\n container.innerHTML = ''\r\n },\r\n prevPage() {\r\n viewer.goLeft?.()\r\n },\r\n nextPage() {\r\n viewer.goRight?.()\r\n },\r\n prevSection() {\r\n viewer.renderer?.prevSection?.()\r\n },\r\n nextSection() {\r\n viewer.renderer?.nextSection?.()\r\n },\r\n goTo(target) {\r\n if (!target) return\r\n viewer.goTo?.(target)\r\n },\r\n goToFraction(fraction) {\r\n const safe = Math.min(1, Math.max(0, fraction))\r\n viewer.goToFraction?.(safe)\r\n },\r\n setDarkMode(nextDarkMode) {\r\n darkMode = nextDarkMode\r\n applyStyles()\r\n },\r\n setFontSize(nextFontSize) {\r\n const safe = Math.min(300, Math.max(50, nextFontSize))\r\n fontSize = safe\r\n applyStyles()\r\n },\r\n setLineHeight(nextLineHeight) {\n const safe = Math.min(3, Math.max(1, nextLineHeight))\n lineHeight = safe\n applyStyles()\n },\n setLetterSpacing(nextLetterSpacing) {\n const safe = Math.min(0.3, Math.max(0, nextLetterSpacing))\n letterSpacing = safe\n applyStyles()\n },\n async search(query, opts: SearchOptions = {}) {\r\n const normalized = query.trim()\r\n if (!normalized) {\r\n viewer.clearSearch?.()\r\n return []\r\n }\r\n\r\n const token = ++searchToken\r\n const results: SearchResult[] = []\r\n\r\n try {\r\n for await (const item of viewer.search?.({\r\n query: normalized,\r\n matchCase: Boolean(opts.matchCase),\r\n matchWholeWords: Boolean(opts.wholeWords),\r\n matchDiacritics: Boolean(opts.matchDiacritics),\r\n }) ?? []) {\r\n if (destroyed || token !== searchToken) return results\r\n\r\n if (item === 'done') {\r\n onSearchProgress?.({ done: true, progress: 1 })\r\n break\r\n }\r\n\r\n if (typeof item === 'object' && item && 'progress' in (item as any)) {\r\n const progress = (item as any).progress\r\n if (typeof progress === 'number') onSearchProgress?.({ progress })\r\n continue\r\n }\r\n\r\n const anyItem = item as any\r\n if (anyItem?.subitems?.length) {\r\n for (const sub of anyItem.subitems) {\r\n results.push({\r\n label: anyItem.label,\r\n cfi: sub?.cfi,\r\n excerpt: sub?.excerpt,\r\n title: sub?.title,\r\n })\r\n }\r\n } else if (anyItem?.cfi) {\r\n results.push({\r\n cfi: anyItem.cfi,\r\n excerpt: anyItem.excerpt,\r\n title: anyItem.title,\r\n })\r\n }\r\n }\r\n } catch (error) {\r\n onError?.(error)\r\n throw error\r\n }\r\n\r\n return results\r\n },\r\n cancelSearch() {\r\n searchToken++\r\n },\r\n clearSearch() {\r\n viewer.clearSearch?.()\r\n },\r\n getToc() {\r\n return toc\r\n },\r\n }\r\n\r\n onReady?.(handle)\r\n return handle\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/core/reader.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import 'foliate-js/view.js'\r\nimport type { EBookReaderHandle, EBookReaderOptions, ProgressInfo, SearchOptions, SearchResult, TocItem } from './types.js'\r\n\r\ntype FoliateViewElement = HTMLElement & {\r\n book?: { toc?: TocItem[] }\r\n renderer?: {\r\n prevSection?: () => void\r\n nextSection?: () => void\r\n setStyles?: (css: string) => void\r\n render?: () => void\r\n expand?: () => void\r\n }\r\n open?: (file: File) => Promise<void>\r\n init?: (options?: unknown) => Promise<void>\r\n goLeft?: () => void\r\n goRight?: () => void\r\n goTo?: (target: string) => Promise<void> | void\r\n goToFraction?: (fraction: number) => Promise<void> | void\r\n search?: (options: unknown) => AsyncIterable<unknown>\r\n clearSearch?: () => void\r\n}\r\n\r\nconst getContentCSS = (fontSize: number, isDark: boolean, lineHeight: number, letterSpacing: number, extraCSS?: string) => {\r\n const scale = fontSize / 100\r\n\r\n return `\r\n@namespace epub \"http://www.idpf.org/2007/ops\";\r\n:root:root {\r\n color-scheme: ${isDark ? 'dark' : 'light'} !important;\r\n}\r\n:root:root body {\r\n background-color: transparent !important;\r\n color: ${isDark ? '#e0e0e0' : 'black'} !important;\r\n font-size: ${fontSize}% !important;\r\n line-height: ${lineHeight} !important;\r\n letter-spacing: ${letterSpacing}em !important;\r\n -webkit-text-size-adjust: 100% !important;\r\n text-size-adjust: 100% !important;\r\n}\r\n\r\n:root:root body :is(p, li, blockquote) {\r\n line-height: inherit !important;\r\n}\r\n\r\n:root:root body p {\r\n margin-bottom: 1em;\r\n}\r\n\r\n:root:root body a {\r\n color: ${isDark ? '#64b5f6' : '#2563eb'} !important;\r\n}\r\n\r\n:root:root body img {\r\n max-width: 100%;\r\n height: auto;\r\n object-fit: contain;\r\n ${isDark ? 'filter: brightness(0.8) contrast(1.2);' : ''}\r\n}\r\n\r\n@supports (zoom: 1) {\r\n :root:root body[data-epub-reader-force-zoom='true'] {\r\n zoom: ${scale};\r\n font-size: 100% !important;\r\n }\r\n}\r\n\r\n${extraCSS ?? ''}\r\n`\r\n}\r\n\r\nexport function createEBookReader(container: HTMLElement, options: EBookReaderOptions = {}): EBookReaderHandle {\r\n if (!container) throw new Error('container is required')\r\n if (!customElements.get('foliate-view')) throw new Error('foliate-view is not defined')\r\n\r\n const {\r\n darkMode: initialDarkMode = false,\r\n fontSize: initialFontSize = 100,\r\n lineHeight: initialLineHeight = 1.6,\r\n letterSpacing: initialLetterSpacing = 0,\r\n extraContentCSS,\r\n onReady,\r\n onError,\r\n onProgress,\r\n onToc,\r\n onSearchProgress,\r\n onContentLoad,\r\n } = options\r\n\r\n let destroyed = false\r\n let toc: TocItem[] = []\r\n let fontSize = initialFontSize\r\n let darkMode = initialDarkMode\r\n let lineHeight = initialLineHeight\r\n let letterSpacing = initialLetterSpacing\r\n let searchToken = 0\r\n let activeDoc: Document | null = null\r\n let forceZoomEnabled = false\r\n\r\n container.innerHTML = ''\r\n\r\n const viewer = document.createElement('foliate-view') as FoliateViewElement\r\n viewer.style.display = 'block'\r\n viewer.style.width = '100%'\r\n viewer.style.height = '100%'\r\n viewer.setAttribute('margin', '48')\r\n viewer.setAttribute('gap', '0.07')\r\n\r\n const pickSampleEl = (doc: Document) => {\r\n const candidates = doc.querySelectorAll('p, li, blockquote, span, div')\r\n for (const el of candidates) {\r\n const text = el.textContent?.trim()\r\n if (!text) continue\r\n if (text.length < 24) continue\r\n return el as HTMLElement\r\n }\r\n return doc.body\r\n }\r\n\r\n const readFontSizePx = (doc: Document) => {\r\n const el = pickSampleEl(doc)\r\n if (!el) return null\r\n const px = Number.parseFloat(getComputedStyle(el).fontSize)\r\n return Number.isFinite(px) ? px : null\r\n }\r\n\r\n const applyForceZoomIfNeeded = () => {\r\n if (!forceZoomEnabled) return\r\n if (!activeDoc?.body) return\r\n activeDoc.body.setAttribute('data-epub-reader-force-zoom', 'true')\r\n }\r\n\r\n const applyStyles = (check?: { beforePx: number | null; beforeFontSize: number; afterFontSize: number }) => {\r\n if (destroyed) return\r\n if (!viewer.renderer?.setStyles) return\r\n\r\n applyForceZoomIfNeeded()\r\n\r\n viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS))\r\n requestAnimationFrame(() => {\r\n setTimeout(() => {\r\n if (destroyed) return\r\n viewer.renderer?.render?.()\r\n viewer.renderer?.expand?.()\r\n\r\n if (!check) return\r\n if (forceZoomEnabled) return\r\n if (!activeDoc) return\r\n\r\n const { beforePx, beforeFontSize, afterFontSize } = check\r\n const afterPx = readFontSizePx(activeDoc)\r\n if (beforePx == null || afterPx == null) return\r\n\r\n const isMeaningfulChange = Math.abs(afterFontSize - beforeFontSize) >= 10\r\n const isIneffective = Math.abs(afterPx - beforePx) < 0.5\r\n if (isMeaningfulChange && isIneffective) {\r\n forceZoomEnabled = true\r\n applyForceZoomIfNeeded()\r\n viewer.renderer?.setStyles?.(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS))\r\n viewer.renderer?.render?.()\r\n viewer.renderer?.expand?.()\r\n }\r\n }, 50)\r\n })\r\n }\r\n\r\n const handleLoad = (e: Event) => {\r\n const detail = (e as CustomEvent).detail as { doc?: Document } | undefined\r\n activeDoc = detail?.doc ?? null\r\n applyStyles()\r\n if (detail?.doc) onContentLoad?.(detail.doc)\r\n }\r\n\r\n const handleRelocate = (e: Event) => {\r\n const detail = (e as CustomEvent).detail as ProgressInfo\r\n onProgress?.(detail)\r\n }\r\n\r\n viewer.addEventListener('load', handleLoad as EventListener)\r\n viewer.addEventListener('relocate', handleRelocate as EventListener)\r\n\r\n container.appendChild(viewer)\r\n\r\n const handle: EBookReaderHandle = {\r\n async open(file) {\r\n if (destroyed) return\r\n if (!file) return\r\n\r\n try {\r\n viewer.clearSearch?.()\r\n searchToken++\r\n activeDoc = null\r\n forceZoomEnabled = false\r\n\r\n await viewer.open?.(file)\r\n\r\n const nextToc = viewer.book?.toc ?? []\r\n toc = nextToc\r\n onToc?.(toc)\r\n\r\n await viewer.init?.({ showTextStart: true })\r\n applyStyles()\r\n } catch (error) {\r\n onError?.(error)\r\n throw error\r\n }\r\n },\r\n destroy() {\r\n if (destroyed) return\r\n destroyed = true\r\n searchToken++\r\n viewer.removeEventListener('load', handleLoad)\r\n viewer.removeEventListener('relocate', handleRelocate as EventListener)\r\n container.innerHTML = ''\r\n },\r\n prevPage() {\r\n viewer.goLeft?.()\r\n },\r\n nextPage() {\r\n viewer.goRight?.()\r\n },\r\n prevSection() {\r\n viewer.renderer?.prevSection?.()\r\n },\r\n nextSection() {\r\n viewer.renderer?.nextSection?.()\r\n },\r\n goTo(target) {\r\n if (!target) return\r\n viewer.goTo?.(target)\r\n },\r\n goToFraction(fraction) {\r\n const safe = Math.min(1, Math.max(0, fraction))\r\n viewer.goToFraction?.(safe)\r\n },\r\n setDarkMode(nextDarkMode) {\r\n darkMode = nextDarkMode\r\n applyStyles()\r\n },\r\n setFontSize(nextFontSize) {\r\n const safe = Math.min(300, Math.max(50, nextFontSize))\r\n const beforeFontSize = fontSize\r\n const beforePx = activeDoc && !forceZoomEnabled ? readFontSizePx(activeDoc) : null\r\n fontSize = safe\r\n applyStyles({ beforePx, beforeFontSize, afterFontSize: safe })\r\n },\r\n setLineHeight(nextLineHeight) {\r\n const safe = Math.min(3, Math.max(1, nextLineHeight))\r\n lineHeight = safe\r\n applyStyles()\r\n },\r\n setLetterSpacing(nextLetterSpacing) {\r\n const safe = Math.min(0.3, Math.max(0, nextLetterSpacing))\r\n letterSpacing = safe\r\n applyStyles()\r\n },\r\n async search(query, opts: SearchOptions = {}) {\r\n const normalized = query.trim()\r\n if (!normalized) {\r\n viewer.clearSearch?.()\r\n return []\r\n }\r\n\r\n const token = ++searchToken\r\n const results: SearchResult[] = []\r\n\r\n try {\r\n for await (const item of viewer.search?.({\r\n query: normalized,\r\n matchCase: Boolean(opts.matchCase),\r\n matchWholeWords: Boolean(opts.wholeWords),\r\n matchDiacritics: Boolean(opts.matchDiacritics),\r\n }) ?? []) {\r\n if (destroyed || token !== searchToken) return results\r\n\r\n if (item === 'done') {\r\n onSearchProgress?.({ done: true, progress: 1 })\r\n break\r\n }\r\n\r\n if (typeof item === 'object' && item && 'progress' in (item as any)) {\r\n const progress = (item as any).progress\r\n if (typeof progress === 'number') onSearchProgress?.({ progress })\r\n continue\r\n }\r\n\r\n const anyItem = item as any\r\n if (anyItem?.subitems?.length) {\r\n for (const sub of anyItem.subitems) {\r\n results.push({\r\n label: anyItem.label,\r\n cfi: sub?.cfi,\r\n excerpt: sub?.excerpt,\r\n title: sub?.title,\r\n })\r\n }\r\n } else if (anyItem?.cfi) {\r\n results.push({\r\n cfi: anyItem.cfi,\r\n excerpt: anyItem.excerpt,\r\n title: anyItem.title,\r\n })\r\n }\r\n }\r\n } catch (error) {\r\n onError?.(error)\r\n throw error\r\n }\r\n\r\n return results\r\n },\r\n cancelSearch() {\r\n searchToken++\r\n },\r\n clearSearch() {\r\n viewer.clearSearch?.()\r\n },\r\n getToc() {\r\n return toc\r\n },\r\n }\r\n\r\n onReady?.(handle)\r\n return handle\r\n}\r\n"]}
package/dist/index.js CHANGED
@@ -1,33 +1,52 @@
1
1
  import 'foliate-js/view.js';
2
2
 
3
3
  // src/core/reader.ts
4
- var getContentCSS = (fontSize, isDark, lineHeight, letterSpacing, extraCSS) => `
4
+ var getContentCSS = (fontSize, isDark, lineHeight, letterSpacing, extraCSS) => {
5
+ const scale = fontSize / 100;
6
+ return `
5
7
  @namespace epub "http://www.idpf.org/2007/ops";
6
- html {
8
+ :root:root {
7
9
  color-scheme: ${isDark ? "dark" : "light"} !important;
8
10
  }
9
- body {
11
+ :root:root body {
10
12
  background-color: transparent !important;
11
13
  color: ${isDark ? "#e0e0e0" : "black"} !important;
12
14
  font-size: ${fontSize}% !important;
13
15
  line-height: ${lineHeight} !important;
14
16
  letter-spacing: ${letterSpacing}em !important;
17
+ -webkit-text-size-adjust: 100% !important;
18
+ text-size-adjust: 100% !important;
15
19
  }
16
- p {
20
+
21
+ :root:root body :is(p, li, blockquote) {
17
22
  line-height: inherit !important;
23
+ }
24
+
25
+ :root:root body p {
18
26
  margin-bottom: 1em;
19
27
  }
20
- a {
28
+
29
+ :root:root body a {
21
30
  color: ${isDark ? "#64b5f6" : "#2563eb"} !important;
22
31
  }
23
- img {
32
+
33
+ :root:root body img {
24
34
  max-width: 100%;
25
35
  height: auto;
26
36
  object-fit: contain;
27
37
  ${isDark ? "filter: brightness(0.8) contrast(1.2);" : ""}
28
38
  }
39
+
40
+ @supports (zoom: 1) {
41
+ :root:root body[data-epub-reader-force-zoom='true'] {
42
+ zoom: ${scale};
43
+ font-size: 100% !important;
44
+ }
45
+ }
46
+
29
47
  ${extraCSS ?? ""}
30
48
  `;
49
+ };
31
50
  function createEBookReader(container, options = {}) {
32
51
  if (!container) throw new Error("container is required");
33
52
  if (!customElements.get("foliate-view")) throw new Error("foliate-view is not defined");
@@ -51,6 +70,8 @@ function createEBookReader(container, options = {}) {
51
70
  let lineHeight = initialLineHeight;
52
71
  let letterSpacing = initialLetterSpacing;
53
72
  let searchToken = 0;
73
+ let activeDoc = null;
74
+ let forceZoomEnabled = false;
54
75
  container.innerHTML = "";
55
76
  const viewer = document.createElement("foliate-view");
56
77
  viewer.style.display = "block";
@@ -58,21 +79,59 @@ function createEBookReader(container, options = {}) {
58
79
  viewer.style.height = "100%";
59
80
  viewer.setAttribute("margin", "48");
60
81
  viewer.setAttribute("gap", "0.07");
61
- const applyStyles = () => {
82
+ const pickSampleEl = (doc) => {
83
+ const candidates = doc.querySelectorAll("p, li, blockquote, span, div");
84
+ for (const el of candidates) {
85
+ const text = el.textContent?.trim();
86
+ if (!text) continue;
87
+ if (text.length < 24) continue;
88
+ return el;
89
+ }
90
+ return doc.body;
91
+ };
92
+ const readFontSizePx = (doc) => {
93
+ const el = pickSampleEl(doc);
94
+ if (!el) return null;
95
+ const px = Number.parseFloat(getComputedStyle(el).fontSize);
96
+ return Number.isFinite(px) ? px : null;
97
+ };
98
+ const applyForceZoomIfNeeded = () => {
99
+ if (!forceZoomEnabled) return;
100
+ if (!activeDoc?.body) return;
101
+ activeDoc.body.setAttribute("data-epub-reader-force-zoom", "true");
102
+ };
103
+ const applyStyles = (check) => {
62
104
  if (destroyed) return;
63
105
  if (!viewer.renderer?.setStyles) return;
106
+ applyForceZoomIfNeeded();
64
107
  viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS));
65
108
  requestAnimationFrame(() => {
66
109
  setTimeout(() => {
67
110
  if (destroyed) return;
68
111
  viewer.renderer?.render?.();
69
112
  viewer.renderer?.expand?.();
113
+ if (!check) return;
114
+ if (forceZoomEnabled) return;
115
+ if (!activeDoc) return;
116
+ const { beforePx, beforeFontSize, afterFontSize } = check;
117
+ const afterPx = readFontSizePx(activeDoc);
118
+ if (beforePx == null || afterPx == null) return;
119
+ const isMeaningfulChange = Math.abs(afterFontSize - beforeFontSize) >= 10;
120
+ const isIneffective = Math.abs(afterPx - beforePx) < 0.5;
121
+ if (isMeaningfulChange && isIneffective) {
122
+ forceZoomEnabled = true;
123
+ applyForceZoomIfNeeded();
124
+ viewer.renderer?.setStyles?.(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS));
125
+ viewer.renderer?.render?.();
126
+ viewer.renderer?.expand?.();
127
+ }
70
128
  }, 50);
71
129
  });
72
130
  };
73
131
  const handleLoad = (e) => {
74
- applyStyles();
75
132
  const detail = e.detail;
133
+ activeDoc = detail?.doc ?? null;
134
+ applyStyles();
76
135
  if (detail?.doc) onContentLoad?.(detail.doc);
77
136
  };
78
137
  const handleRelocate = (e) => {
@@ -89,6 +148,8 @@ function createEBookReader(container, options = {}) {
89
148
  try {
90
149
  viewer.clearSearch?.();
91
150
  searchToken++;
151
+ activeDoc = null;
152
+ forceZoomEnabled = false;
92
153
  await viewer.open?.(file);
93
154
  const nextToc = viewer.book?.toc ?? [];
94
155
  toc = nextToc;
@@ -134,8 +195,10 @@ function createEBookReader(container, options = {}) {
134
195
  },
135
196
  setFontSize(nextFontSize) {
136
197
  const safe = Math.min(300, Math.max(50, nextFontSize));
198
+ const beforeFontSize = fontSize;
199
+ const beforePx = activeDoc && !forceZoomEnabled ? readFontSizePx(activeDoc) : null;
137
200
  fontSize = safe;
138
- applyStyles();
201
+ applyStyles({ beforePx, beforeFontSize, afterFontSize: safe });
139
202
  },
140
203
  setLineHeight(nextLineHeight) {
141
204
  const safe = Math.min(3, Math.max(1, nextLineHeight));
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/reader.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import 'foliate-js/view.js'\r\nimport type { EBookReaderHandle, EBookReaderOptions, ProgressInfo, SearchOptions, SearchResult, TocItem } from './types.js'\r\n\r\ntype FoliateViewElement = HTMLElement & {\r\n book?: { toc?: TocItem[] }\r\n renderer?: {\r\n prevSection?: () => void\r\n nextSection?: () => void\r\n setStyles?: (css: string) => void\r\n render?: () => void\r\n expand?: () => void\r\n }\r\n open?: (file: File) => Promise<void>\r\n init?: (options?: unknown) => Promise<void>\r\n goLeft?: () => void\r\n goRight?: () => void\r\n goTo?: (target: string) => Promise<void> | void\r\n goToFraction?: (fraction: number) => Promise<void> | void\r\n search?: (options: unknown) => AsyncIterable<unknown>\r\n clearSearch?: () => void\r\n}\r\n\r\nconst getContentCSS = (fontSize: number, isDark: boolean, lineHeight: number, letterSpacing: number, extraCSS?: string) => `\n@namespace epub \"http://www.idpf.org/2007/ops\";\r\nhtml {\r\n color-scheme: ${isDark ? 'dark' : 'light'} !important;\r\n}\r\nbody {\r\n background-color: transparent !important;\r\n color: ${isDark ? '#e0e0e0' : 'black'} !important;\r\n font-size: ${fontSize}% !important;\r\n line-height: ${lineHeight} !important;\n letter-spacing: ${letterSpacing}em !important;\n}\r\np {\r\n line-height: inherit !important;\n margin-bottom: 1em;\r\n}\r\na {\r\n color: ${isDark ? '#64b5f6' : '#2563eb'} !important;\r\n}\r\nimg {\r\n max-width: 100%;\r\n height: auto;\r\n object-fit: contain;\r\n ${isDark ? 'filter: brightness(0.8) contrast(1.2);' : ''}\r\n}\r\n${extraCSS ?? ''}\r\n`\r\n\r\nexport function createEBookReader(container: HTMLElement, options: EBookReaderOptions = {}): EBookReaderHandle {\r\n if (!container) throw new Error('container is required')\r\n if (!customElements.get('foliate-view')) throw new Error('foliate-view is not defined')\r\n\r\n const {\r\n darkMode: initialDarkMode = false,\r\n fontSize: initialFontSize = 100,\r\n lineHeight: initialLineHeight = 1.6,\n letterSpacing: initialLetterSpacing = 0,\n extraContentCSS,\r\n onReady,\r\n onError,\r\n onProgress,\r\n onToc,\r\n onSearchProgress,\r\n onContentLoad,\r\n } = options\r\n\r\n let destroyed = false\r\n let toc: TocItem[] = []\r\n let fontSize = initialFontSize\r\n let darkMode = initialDarkMode\r\n let lineHeight = initialLineHeight\n let letterSpacing = initialLetterSpacing\n let searchToken = 0\r\n\r\n container.innerHTML = ''\r\n\r\n const viewer = document.createElement('foliate-view') as FoliateViewElement\r\n viewer.style.display = 'block'\r\n viewer.style.width = '100%'\r\n viewer.style.height = '100%'\r\n viewer.setAttribute('margin', '48')\r\n viewer.setAttribute('gap', '0.07')\r\n\r\n const applyStyles = () => {\r\n if (destroyed) return\r\n if (!viewer.renderer?.setStyles) return\r\n viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS))\n requestAnimationFrame(() => {\r\n setTimeout(() => {\r\n if (destroyed) return\r\n viewer.renderer?.render?.()\r\n viewer.renderer?.expand?.()\r\n }, 50)\r\n })\r\n }\r\n\r\n const handleLoad = (e: Event) => {\r\n applyStyles()\r\n const detail = (e as CustomEvent).detail as { doc?: Document } | undefined\r\n if (detail?.doc) onContentLoad?.(detail.doc)\r\n }\r\n\r\n const handleRelocate = (e: Event) => {\r\n const detail = (e as CustomEvent).detail as ProgressInfo\r\n onProgress?.(detail)\r\n }\r\n\r\n viewer.addEventListener('load', handleLoad as EventListener)\r\n viewer.addEventListener('relocate', handleRelocate as EventListener)\r\n\r\n container.appendChild(viewer)\r\n\r\n const handle: EBookReaderHandle = {\r\n async open(file) {\r\n if (destroyed) return\r\n if (!file) return\r\n\r\n try {\r\n viewer.clearSearch?.()\r\n searchToken++\r\n\r\n await viewer.open?.(file)\r\n\r\n const nextToc = viewer.book?.toc ?? []\r\n toc = nextToc\r\n onToc?.(toc)\r\n\r\n await viewer.init?.({ showTextStart: true })\r\n applyStyles()\r\n } catch (error) {\r\n onError?.(error)\r\n throw error\r\n }\r\n },\r\n destroy() {\r\n if (destroyed) return\r\n destroyed = true\r\n searchToken++\r\n viewer.removeEventListener('load', handleLoad)\r\n viewer.removeEventListener('relocate', handleRelocate as EventListener)\r\n container.innerHTML = ''\r\n },\r\n prevPage() {\r\n viewer.goLeft?.()\r\n },\r\n nextPage() {\r\n viewer.goRight?.()\r\n },\r\n prevSection() {\r\n viewer.renderer?.prevSection?.()\r\n },\r\n nextSection() {\r\n viewer.renderer?.nextSection?.()\r\n },\r\n goTo(target) {\r\n if (!target) return\r\n viewer.goTo?.(target)\r\n },\r\n goToFraction(fraction) {\r\n const safe = Math.min(1, Math.max(0, fraction))\r\n viewer.goToFraction?.(safe)\r\n },\r\n setDarkMode(nextDarkMode) {\r\n darkMode = nextDarkMode\r\n applyStyles()\r\n },\r\n setFontSize(nextFontSize) {\r\n const safe = Math.min(300, Math.max(50, nextFontSize))\r\n fontSize = safe\r\n applyStyles()\r\n },\r\n setLineHeight(nextLineHeight) {\n const safe = Math.min(3, Math.max(1, nextLineHeight))\n lineHeight = safe\n applyStyles()\n },\n setLetterSpacing(nextLetterSpacing) {\n const safe = Math.min(0.3, Math.max(0, nextLetterSpacing))\n letterSpacing = safe\n applyStyles()\n },\n async search(query, opts: SearchOptions = {}) {\r\n const normalized = query.trim()\r\n if (!normalized) {\r\n viewer.clearSearch?.()\r\n return []\r\n }\r\n\r\n const token = ++searchToken\r\n const results: SearchResult[] = []\r\n\r\n try {\r\n for await (const item of viewer.search?.({\r\n query: normalized,\r\n matchCase: Boolean(opts.matchCase),\r\n matchWholeWords: Boolean(opts.wholeWords),\r\n matchDiacritics: Boolean(opts.matchDiacritics),\r\n }) ?? []) {\r\n if (destroyed || token !== searchToken) return results\r\n\r\n if (item === 'done') {\r\n onSearchProgress?.({ done: true, progress: 1 })\r\n break\r\n }\r\n\r\n if (typeof item === 'object' && item && 'progress' in (item as any)) {\r\n const progress = (item as any).progress\r\n if (typeof progress === 'number') onSearchProgress?.({ progress })\r\n continue\r\n }\r\n\r\n const anyItem = item as any\r\n if (anyItem?.subitems?.length) {\r\n for (const sub of anyItem.subitems) {\r\n results.push({\r\n label: anyItem.label,\r\n cfi: sub?.cfi,\r\n excerpt: sub?.excerpt,\r\n title: sub?.title,\r\n })\r\n }\r\n } else if (anyItem?.cfi) {\r\n results.push({\r\n cfi: anyItem.cfi,\r\n excerpt: anyItem.excerpt,\r\n title: anyItem.title,\r\n })\r\n }\r\n }\r\n } catch (error) {\r\n onError?.(error)\r\n throw error\r\n }\r\n\r\n return results\r\n },\r\n cancelSearch() {\r\n searchToken++\r\n },\r\n clearSearch() {\r\n viewer.clearSearch?.()\r\n },\r\n getToc() {\r\n return toc\r\n },\r\n }\r\n\r\n onReady?.(handle)\r\n return handle\r\n}\r\n"]}
1
+ {"version":3,"sources":["../src/core/reader.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import 'foliate-js/view.js'\r\nimport type { EBookReaderHandle, EBookReaderOptions, ProgressInfo, SearchOptions, SearchResult, TocItem } from './types.js'\r\n\r\ntype FoliateViewElement = HTMLElement & {\r\n book?: { toc?: TocItem[] }\r\n renderer?: {\r\n prevSection?: () => void\r\n nextSection?: () => void\r\n setStyles?: (css: string) => void\r\n render?: () => void\r\n expand?: () => void\r\n }\r\n open?: (file: File) => Promise<void>\r\n init?: (options?: unknown) => Promise<void>\r\n goLeft?: () => void\r\n goRight?: () => void\r\n goTo?: (target: string) => Promise<void> | void\r\n goToFraction?: (fraction: number) => Promise<void> | void\r\n search?: (options: unknown) => AsyncIterable<unknown>\r\n clearSearch?: () => void\r\n}\r\n\r\nconst getContentCSS = (fontSize: number, isDark: boolean, lineHeight: number, letterSpacing: number, extraCSS?: string) => {\r\n const scale = fontSize / 100\r\n\r\n return `\r\n@namespace epub \"http://www.idpf.org/2007/ops\";\r\n:root:root {\r\n color-scheme: ${isDark ? 'dark' : 'light'} !important;\r\n}\r\n:root:root body {\r\n background-color: transparent !important;\r\n color: ${isDark ? '#e0e0e0' : 'black'} !important;\r\n font-size: ${fontSize}% !important;\r\n line-height: ${lineHeight} !important;\r\n letter-spacing: ${letterSpacing}em !important;\r\n -webkit-text-size-adjust: 100% !important;\r\n text-size-adjust: 100% !important;\r\n}\r\n\r\n:root:root body :is(p, li, blockquote) {\r\n line-height: inherit !important;\r\n}\r\n\r\n:root:root body p {\r\n margin-bottom: 1em;\r\n}\r\n\r\n:root:root body a {\r\n color: ${isDark ? '#64b5f6' : '#2563eb'} !important;\r\n}\r\n\r\n:root:root body img {\r\n max-width: 100%;\r\n height: auto;\r\n object-fit: contain;\r\n ${isDark ? 'filter: brightness(0.8) contrast(1.2);' : ''}\r\n}\r\n\r\n@supports (zoom: 1) {\r\n :root:root body[data-epub-reader-force-zoom='true'] {\r\n zoom: ${scale};\r\n font-size: 100% !important;\r\n }\r\n}\r\n\r\n${extraCSS ?? ''}\r\n`\r\n}\r\n\r\nexport function createEBookReader(container: HTMLElement, options: EBookReaderOptions = {}): EBookReaderHandle {\r\n if (!container) throw new Error('container is required')\r\n if (!customElements.get('foliate-view')) throw new Error('foliate-view is not defined')\r\n\r\n const {\r\n darkMode: initialDarkMode = false,\r\n fontSize: initialFontSize = 100,\r\n lineHeight: initialLineHeight = 1.6,\r\n letterSpacing: initialLetterSpacing = 0,\r\n extraContentCSS,\r\n onReady,\r\n onError,\r\n onProgress,\r\n onToc,\r\n onSearchProgress,\r\n onContentLoad,\r\n } = options\r\n\r\n let destroyed = false\r\n let toc: TocItem[] = []\r\n let fontSize = initialFontSize\r\n let darkMode = initialDarkMode\r\n let lineHeight = initialLineHeight\r\n let letterSpacing = initialLetterSpacing\r\n let searchToken = 0\r\n let activeDoc: Document | null = null\r\n let forceZoomEnabled = false\r\n\r\n container.innerHTML = ''\r\n\r\n const viewer = document.createElement('foliate-view') as FoliateViewElement\r\n viewer.style.display = 'block'\r\n viewer.style.width = '100%'\r\n viewer.style.height = '100%'\r\n viewer.setAttribute('margin', '48')\r\n viewer.setAttribute('gap', '0.07')\r\n\r\n const pickSampleEl = (doc: Document) => {\r\n const candidates = doc.querySelectorAll('p, li, blockquote, span, div')\r\n for (const el of candidates) {\r\n const text = el.textContent?.trim()\r\n if (!text) continue\r\n if (text.length < 24) continue\r\n return el as HTMLElement\r\n }\r\n return doc.body\r\n }\r\n\r\n const readFontSizePx = (doc: Document) => {\r\n const el = pickSampleEl(doc)\r\n if (!el) return null\r\n const px = Number.parseFloat(getComputedStyle(el).fontSize)\r\n return Number.isFinite(px) ? px : null\r\n }\r\n\r\n const applyForceZoomIfNeeded = () => {\r\n if (!forceZoomEnabled) return\r\n if (!activeDoc?.body) return\r\n activeDoc.body.setAttribute('data-epub-reader-force-zoom', 'true')\r\n }\r\n\r\n const applyStyles = (check?: { beforePx: number | null; beforeFontSize: number; afterFontSize: number }) => {\r\n if (destroyed) return\r\n if (!viewer.renderer?.setStyles) return\r\n\r\n applyForceZoomIfNeeded()\r\n\r\n viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS))\r\n requestAnimationFrame(() => {\r\n setTimeout(() => {\r\n if (destroyed) return\r\n viewer.renderer?.render?.()\r\n viewer.renderer?.expand?.()\r\n\r\n if (!check) return\r\n if (forceZoomEnabled) return\r\n if (!activeDoc) return\r\n\r\n const { beforePx, beforeFontSize, afterFontSize } = check\r\n const afterPx = readFontSizePx(activeDoc)\r\n if (beforePx == null || afterPx == null) return\r\n\r\n const isMeaningfulChange = Math.abs(afterFontSize - beforeFontSize) >= 10\r\n const isIneffective = Math.abs(afterPx - beforePx) < 0.5\r\n if (isMeaningfulChange && isIneffective) {\r\n forceZoomEnabled = true\r\n applyForceZoomIfNeeded()\r\n viewer.renderer?.setStyles?.(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS))\r\n viewer.renderer?.render?.()\r\n viewer.renderer?.expand?.()\r\n }\r\n }, 50)\r\n })\r\n }\r\n\r\n const handleLoad = (e: Event) => {\r\n const detail = (e as CustomEvent).detail as { doc?: Document } | undefined\r\n activeDoc = detail?.doc ?? null\r\n applyStyles()\r\n if (detail?.doc) onContentLoad?.(detail.doc)\r\n }\r\n\r\n const handleRelocate = (e: Event) => {\r\n const detail = (e as CustomEvent).detail as ProgressInfo\r\n onProgress?.(detail)\r\n }\r\n\r\n viewer.addEventListener('load', handleLoad as EventListener)\r\n viewer.addEventListener('relocate', handleRelocate as EventListener)\r\n\r\n container.appendChild(viewer)\r\n\r\n const handle: EBookReaderHandle = {\r\n async open(file) {\r\n if (destroyed) return\r\n if (!file) return\r\n\r\n try {\r\n viewer.clearSearch?.()\r\n searchToken++\r\n activeDoc = null\r\n forceZoomEnabled = false\r\n\r\n await viewer.open?.(file)\r\n\r\n const nextToc = viewer.book?.toc ?? []\r\n toc = nextToc\r\n onToc?.(toc)\r\n\r\n await viewer.init?.({ showTextStart: true })\r\n applyStyles()\r\n } catch (error) {\r\n onError?.(error)\r\n throw error\r\n }\r\n },\r\n destroy() {\r\n if (destroyed) return\r\n destroyed = true\r\n searchToken++\r\n viewer.removeEventListener('load', handleLoad)\r\n viewer.removeEventListener('relocate', handleRelocate as EventListener)\r\n container.innerHTML = ''\r\n },\r\n prevPage() {\r\n viewer.goLeft?.()\r\n },\r\n nextPage() {\r\n viewer.goRight?.()\r\n },\r\n prevSection() {\r\n viewer.renderer?.prevSection?.()\r\n },\r\n nextSection() {\r\n viewer.renderer?.nextSection?.()\r\n },\r\n goTo(target) {\r\n if (!target) return\r\n viewer.goTo?.(target)\r\n },\r\n goToFraction(fraction) {\r\n const safe = Math.min(1, Math.max(0, fraction))\r\n viewer.goToFraction?.(safe)\r\n },\r\n setDarkMode(nextDarkMode) {\r\n darkMode = nextDarkMode\r\n applyStyles()\r\n },\r\n setFontSize(nextFontSize) {\r\n const safe = Math.min(300, Math.max(50, nextFontSize))\r\n const beforeFontSize = fontSize\r\n const beforePx = activeDoc && !forceZoomEnabled ? readFontSizePx(activeDoc) : null\r\n fontSize = safe\r\n applyStyles({ beforePx, beforeFontSize, afterFontSize: safe })\r\n },\r\n setLineHeight(nextLineHeight) {\r\n const safe = Math.min(3, Math.max(1, nextLineHeight))\r\n lineHeight = safe\r\n applyStyles()\r\n },\r\n setLetterSpacing(nextLetterSpacing) {\r\n const safe = Math.min(0.3, Math.max(0, nextLetterSpacing))\r\n letterSpacing = safe\r\n applyStyles()\r\n },\r\n async search(query, opts: SearchOptions = {}) {\r\n const normalized = query.trim()\r\n if (!normalized) {\r\n viewer.clearSearch?.()\r\n return []\r\n }\r\n\r\n const token = ++searchToken\r\n const results: SearchResult[] = []\r\n\r\n try {\r\n for await (const item of viewer.search?.({\r\n query: normalized,\r\n matchCase: Boolean(opts.matchCase),\r\n matchWholeWords: Boolean(opts.wholeWords),\r\n matchDiacritics: Boolean(opts.matchDiacritics),\r\n }) ?? []) {\r\n if (destroyed || token !== searchToken) return results\r\n\r\n if (item === 'done') {\r\n onSearchProgress?.({ done: true, progress: 1 })\r\n break\r\n }\r\n\r\n if (typeof item === 'object' && item && 'progress' in (item as any)) {\r\n const progress = (item as any).progress\r\n if (typeof progress === 'number') onSearchProgress?.({ progress })\r\n continue\r\n }\r\n\r\n const anyItem = item as any\r\n if (anyItem?.subitems?.length) {\r\n for (const sub of anyItem.subitems) {\r\n results.push({\r\n label: anyItem.label,\r\n cfi: sub?.cfi,\r\n excerpt: sub?.excerpt,\r\n title: sub?.title,\r\n })\r\n }\r\n } else if (anyItem?.cfi) {\r\n results.push({\r\n cfi: anyItem.cfi,\r\n excerpt: anyItem.excerpt,\r\n title: anyItem.title,\r\n })\r\n }\r\n }\r\n } catch (error) {\r\n onError?.(error)\r\n throw error\r\n }\r\n\r\n return results\r\n },\r\n cancelSearch() {\r\n searchToken++\r\n },\r\n clearSearch() {\r\n viewer.clearSearch?.()\r\n },\r\n getToc() {\r\n return toc\r\n },\r\n }\r\n\r\n onReady?.(handle)\r\n return handle\r\n}\r\n"]}
package/dist/react.cjs CHANGED
@@ -5,33 +5,52 @@ require('foliate-js/view.js');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
 
7
7
  // src/react/EBookReader.tsx
8
- var getContentCSS = (fontSize, isDark, lineHeight, letterSpacing, extraCSS) => `
8
+ var getContentCSS = (fontSize, isDark, lineHeight, letterSpacing, extraCSS) => {
9
+ const scale = fontSize / 100;
10
+ return `
9
11
  @namespace epub "http://www.idpf.org/2007/ops";
10
- html {
12
+ :root:root {
11
13
  color-scheme: ${isDark ? "dark" : "light"} !important;
12
14
  }
13
- body {
15
+ :root:root body {
14
16
  background-color: transparent !important;
15
17
  color: ${isDark ? "#e0e0e0" : "black"} !important;
16
18
  font-size: ${fontSize}% !important;
17
19
  line-height: ${lineHeight} !important;
18
20
  letter-spacing: ${letterSpacing}em !important;
21
+ -webkit-text-size-adjust: 100% !important;
22
+ text-size-adjust: 100% !important;
19
23
  }
20
- p {
24
+
25
+ :root:root body :is(p, li, blockquote) {
21
26
  line-height: inherit !important;
27
+ }
28
+
29
+ :root:root body p {
22
30
  margin-bottom: 1em;
23
31
  }
24
- a {
32
+
33
+ :root:root body a {
25
34
  color: ${isDark ? "#64b5f6" : "#2563eb"} !important;
26
35
  }
27
- img {
36
+
37
+ :root:root body img {
28
38
  max-width: 100%;
29
39
  height: auto;
30
40
  object-fit: contain;
31
41
  ${isDark ? "filter: brightness(0.8) contrast(1.2);" : ""}
32
42
  }
43
+
44
+ @supports (zoom: 1) {
45
+ :root:root body[data-epub-reader-force-zoom='true'] {
46
+ zoom: ${scale};
47
+ font-size: 100% !important;
48
+ }
49
+ }
50
+
33
51
  ${extraCSS ?? ""}
34
52
  `;
53
+ };
35
54
  function createEBookReader(container, options = {}) {
36
55
  if (!container) throw new Error("container is required");
37
56
  if (!customElements.get("foliate-view")) throw new Error("foliate-view is not defined");
@@ -55,6 +74,8 @@ function createEBookReader(container, options = {}) {
55
74
  let lineHeight = initialLineHeight;
56
75
  let letterSpacing = initialLetterSpacing;
57
76
  let searchToken = 0;
77
+ let activeDoc = null;
78
+ let forceZoomEnabled = false;
58
79
  container.innerHTML = "";
59
80
  const viewer = document.createElement("foliate-view");
60
81
  viewer.style.display = "block";
@@ -62,21 +83,59 @@ function createEBookReader(container, options = {}) {
62
83
  viewer.style.height = "100%";
63
84
  viewer.setAttribute("margin", "48");
64
85
  viewer.setAttribute("gap", "0.07");
65
- const applyStyles = () => {
86
+ const pickSampleEl = (doc) => {
87
+ const candidates = doc.querySelectorAll("p, li, blockquote, span, div");
88
+ for (const el of candidates) {
89
+ const text = el.textContent?.trim();
90
+ if (!text) continue;
91
+ if (text.length < 24) continue;
92
+ return el;
93
+ }
94
+ return doc.body;
95
+ };
96
+ const readFontSizePx = (doc) => {
97
+ const el = pickSampleEl(doc);
98
+ if (!el) return null;
99
+ const px = Number.parseFloat(getComputedStyle(el).fontSize);
100
+ return Number.isFinite(px) ? px : null;
101
+ };
102
+ const applyForceZoomIfNeeded = () => {
103
+ if (!forceZoomEnabled) return;
104
+ if (!activeDoc?.body) return;
105
+ activeDoc.body.setAttribute("data-epub-reader-force-zoom", "true");
106
+ };
107
+ const applyStyles = (check) => {
66
108
  if (destroyed) return;
67
109
  if (!viewer.renderer?.setStyles) return;
110
+ applyForceZoomIfNeeded();
68
111
  viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS));
69
112
  requestAnimationFrame(() => {
70
113
  setTimeout(() => {
71
114
  if (destroyed) return;
72
115
  viewer.renderer?.render?.();
73
116
  viewer.renderer?.expand?.();
117
+ if (!check) return;
118
+ if (forceZoomEnabled) return;
119
+ if (!activeDoc) return;
120
+ const { beforePx, beforeFontSize, afterFontSize } = check;
121
+ const afterPx = readFontSizePx(activeDoc);
122
+ if (beforePx == null || afterPx == null) return;
123
+ const isMeaningfulChange = Math.abs(afterFontSize - beforeFontSize) >= 10;
124
+ const isIneffective = Math.abs(afterPx - beforePx) < 0.5;
125
+ if (isMeaningfulChange && isIneffective) {
126
+ forceZoomEnabled = true;
127
+ applyForceZoomIfNeeded();
128
+ viewer.renderer?.setStyles?.(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS));
129
+ viewer.renderer?.render?.();
130
+ viewer.renderer?.expand?.();
131
+ }
74
132
  }, 50);
75
133
  });
76
134
  };
77
135
  const handleLoad = (e) => {
78
- applyStyles();
79
136
  const detail = e.detail;
137
+ activeDoc = detail?.doc ?? null;
138
+ applyStyles();
80
139
  if (detail?.doc) onContentLoad?.(detail.doc);
81
140
  };
82
141
  const handleRelocate = (e) => {
@@ -93,6 +152,8 @@ function createEBookReader(container, options = {}) {
93
152
  try {
94
153
  viewer.clearSearch?.();
95
154
  searchToken++;
155
+ activeDoc = null;
156
+ forceZoomEnabled = false;
96
157
  await viewer.open?.(file);
97
158
  const nextToc = viewer.book?.toc ?? [];
98
159
  toc = nextToc;
@@ -138,8 +199,10 @@ function createEBookReader(container, options = {}) {
138
199
  },
139
200
  setFontSize(nextFontSize) {
140
201
  const safe = Math.min(300, Math.max(50, nextFontSize));
202
+ const beforeFontSize = fontSize;
203
+ const beforePx = activeDoc && !forceZoomEnabled ? readFontSizePx(activeDoc) : null;
141
204
  fontSize = safe;
142
- applyStyles();
205
+ applyStyles({ beforePx, beforeFontSize, afterFontSize: safe });
143
206
  },
144
207
  setLineHeight(nextLineHeight) {
145
208
  const safe = Math.min(3, Math.max(1, nextLineHeight));