@somecat/epub-reader 0.1.5 → 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.
package/dist/core.cjs CHANGED
@@ -3,33 +3,52 @@
3
3
  require('foliate-js/view.js');
4
4
 
5
5
  // src/core/reader.ts
6
- var getContentCSS = (fontSize, isDark, lineHeight, letterSpacing, extraCSS) => `
6
+ var getContentCSS = (fontSize, isDark, lineHeight, letterSpacing, extraCSS) => {
7
+ const scale = fontSize / 100;
8
+ return `
7
9
  @namespace epub "http://www.idpf.org/2007/ops";
8
- html {
10
+ :root:root {
9
11
  color-scheme: ${isDark ? "dark" : "light"} !important;
10
12
  }
11
- body {
13
+ :root:root body {
12
14
  background-color: transparent !important;
13
15
  color: ${isDark ? "#e0e0e0" : "black"} !important;
14
16
  font-size: ${fontSize}% !important;
15
17
  line-height: ${lineHeight} !important;
16
18
  letter-spacing: ${letterSpacing}em !important;
19
+ -webkit-text-size-adjust: 100% !important;
20
+ text-size-adjust: 100% !important;
17
21
  }
18
- p {
22
+
23
+ :root:root body :is(p, li, blockquote) {
19
24
  line-height: inherit !important;
25
+ }
26
+
27
+ :root:root body p {
20
28
  margin-bottom: 1em;
21
29
  }
22
- a {
30
+
31
+ :root:root body a {
23
32
  color: ${isDark ? "#64b5f6" : "#2563eb"} !important;
24
33
  }
25
- img {
34
+
35
+ :root:root body img {
26
36
  max-width: 100%;
27
37
  height: auto;
28
38
  object-fit: contain;
29
39
  ${isDark ? "filter: brightness(0.8) contrast(1.2);" : ""}
30
40
  }
41
+
42
+ @supports (zoom: 1) {
43
+ :root:root body[data-epub-reader-force-zoom='true'] {
44
+ zoom: ${scale};
45
+ font-size: 100% !important;
46
+ }
47
+ }
48
+
31
49
  ${extraCSS ?? ""}
32
50
  `;
51
+ };
33
52
  function createEBookReader(container, options = {}) {
34
53
  if (!container) throw new Error("container is required");
35
54
  if (!customElements.get("foliate-view")) throw new Error("foliate-view is not defined");
@@ -53,6 +72,8 @@ function createEBookReader(container, options = {}) {
53
72
  let lineHeight = initialLineHeight;
54
73
  let letterSpacing = initialLetterSpacing;
55
74
  let searchToken = 0;
75
+ let activeDoc = null;
76
+ let forceZoomEnabled = false;
56
77
  container.innerHTML = "";
57
78
  const viewer = document.createElement("foliate-view");
58
79
  viewer.style.display = "block";
@@ -60,21 +81,59 @@ function createEBookReader(container, options = {}) {
60
81
  viewer.style.height = "100%";
61
82
  viewer.setAttribute("margin", "48");
62
83
  viewer.setAttribute("gap", "0.07");
63
- const applyStyles = () => {
84
+ const pickSampleEl = (doc) => {
85
+ const candidates = doc.querySelectorAll("p, li, blockquote, span, div");
86
+ for (const el of candidates) {
87
+ const text = el.textContent?.trim();
88
+ if (!text) continue;
89
+ if (text.length < 24) continue;
90
+ return el;
91
+ }
92
+ return doc.body;
93
+ };
94
+ const readFontSizePx = (doc) => {
95
+ const el = pickSampleEl(doc);
96
+ if (!el) return null;
97
+ const px = Number.parseFloat(getComputedStyle(el).fontSize);
98
+ return Number.isFinite(px) ? px : null;
99
+ };
100
+ const applyForceZoomIfNeeded = () => {
101
+ if (!forceZoomEnabled) return;
102
+ if (!activeDoc?.body) return;
103
+ activeDoc.body.setAttribute("data-epub-reader-force-zoom", "true");
104
+ };
105
+ const applyStyles = (check) => {
64
106
  if (destroyed) return;
65
107
  if (!viewer.renderer?.setStyles) return;
108
+ applyForceZoomIfNeeded();
66
109
  viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS));
67
110
  requestAnimationFrame(() => {
68
111
  setTimeout(() => {
69
112
  if (destroyed) return;
70
113
  viewer.renderer?.render?.();
71
114
  viewer.renderer?.expand?.();
115
+ if (!check) return;
116
+ if (forceZoomEnabled) return;
117
+ if (!activeDoc) return;
118
+ const { beforePx, beforeFontSize, afterFontSize } = check;
119
+ const afterPx = readFontSizePx(activeDoc);
120
+ if (beforePx == null || afterPx == null) return;
121
+ const isMeaningfulChange = Math.abs(afterFontSize - beforeFontSize) >= 10;
122
+ const isIneffective = Math.abs(afterPx - beforePx) < 0.5;
123
+ if (isMeaningfulChange && isIneffective) {
124
+ forceZoomEnabled = true;
125
+ applyForceZoomIfNeeded();
126
+ viewer.renderer?.setStyles?.(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS));
127
+ viewer.renderer?.render?.();
128
+ viewer.renderer?.expand?.();
129
+ }
72
130
  }, 50);
73
131
  });
74
132
  };
75
133
  const handleLoad = (e) => {
76
- applyStyles();
77
134
  const detail = e.detail;
135
+ activeDoc = detail?.doc ?? null;
136
+ applyStyles();
78
137
  if (detail?.doc) onContentLoad?.(detail.doc);
79
138
  };
80
139
  const handleRelocate = (e) => {
@@ -91,6 +150,8 @@ function createEBookReader(container, options = {}) {
91
150
  try {
92
151
  viewer.clearSearch?.();
93
152
  searchToken++;
153
+ activeDoc = null;
154
+ forceZoomEnabled = false;
94
155
  await viewer.open?.(file);
95
156
  const nextToc = viewer.book?.toc ?? [];
96
157
  toc = nextToc;
@@ -136,8 +197,10 @@ function createEBookReader(container, options = {}) {
136
197
  },
137
198
  setFontSize(nextFontSize) {
138
199
  const safe = Math.min(300, Math.max(50, nextFontSize));
200
+ const beforeFontSize = fontSize;
201
+ const beforePx = activeDoc && !forceZoomEnabled ? readFontSizePx(activeDoc) : null;
139
202
  fontSize = safe;
140
- applyStyles();
203
+ applyStyles({ beforePx, beforeFontSize, afterFontSize: safe });
141
204
  },
142
205
  setLineHeight(nextLineHeight) {
143
206
  const safe = Math.min(3, Math.max(1, nextLineHeight));
package/dist/core.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/reader.ts"],"names":[],"mappings":"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","file":"core.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":"core.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/core.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/core.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/reader.ts"],"names":[],"mappings":"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","file":"core.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":"core.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/index.cjs CHANGED
@@ -3,33 +3,52 @@
3
3
  require('foliate-js/view.js');
4
4
 
5
5
  // src/core/reader.ts
6
- var getContentCSS = (fontSize, isDark, lineHeight, letterSpacing, extraCSS) => `
6
+ var getContentCSS = (fontSize, isDark, lineHeight, letterSpacing, extraCSS) => {
7
+ const scale = fontSize / 100;
8
+ return `
7
9
  @namespace epub "http://www.idpf.org/2007/ops";
8
- html {
10
+ :root:root {
9
11
  color-scheme: ${isDark ? "dark" : "light"} !important;
10
12
  }
11
- body {
13
+ :root:root body {
12
14
  background-color: transparent !important;
13
15
  color: ${isDark ? "#e0e0e0" : "black"} !important;
14
16
  font-size: ${fontSize}% !important;
15
17
  line-height: ${lineHeight} !important;
16
18
  letter-spacing: ${letterSpacing}em !important;
19
+ -webkit-text-size-adjust: 100% !important;
20
+ text-size-adjust: 100% !important;
17
21
  }
18
- p {
22
+
23
+ :root:root body :is(p, li, blockquote) {
19
24
  line-height: inherit !important;
25
+ }
26
+
27
+ :root:root body p {
20
28
  margin-bottom: 1em;
21
29
  }
22
- a {
30
+
31
+ :root:root body a {
23
32
  color: ${isDark ? "#64b5f6" : "#2563eb"} !important;
24
33
  }
25
- img {
34
+
35
+ :root:root body img {
26
36
  max-width: 100%;
27
37
  height: auto;
28
38
  object-fit: contain;
29
39
  ${isDark ? "filter: brightness(0.8) contrast(1.2);" : ""}
30
40
  }
41
+
42
+ @supports (zoom: 1) {
43
+ :root:root body[data-epub-reader-force-zoom='true'] {
44
+ zoom: ${scale};
45
+ font-size: 100% !important;
46
+ }
47
+ }
48
+
31
49
  ${extraCSS ?? ""}
32
50
  `;
51
+ };
33
52
  function createEBookReader(container, options = {}) {
34
53
  if (!container) throw new Error("container is required");
35
54
  if (!customElements.get("foliate-view")) throw new Error("foliate-view is not defined");
@@ -53,6 +72,8 @@ function createEBookReader(container, options = {}) {
53
72
  let lineHeight = initialLineHeight;
54
73
  let letterSpacing = initialLetterSpacing;
55
74
  let searchToken = 0;
75
+ let activeDoc = null;
76
+ let forceZoomEnabled = false;
56
77
  container.innerHTML = "";
57
78
  const viewer = document.createElement("foliate-view");
58
79
  viewer.style.display = "block";
@@ -60,21 +81,59 @@ function createEBookReader(container, options = {}) {
60
81
  viewer.style.height = "100%";
61
82
  viewer.setAttribute("margin", "48");
62
83
  viewer.setAttribute("gap", "0.07");
63
- const applyStyles = () => {
84
+ const pickSampleEl = (doc) => {
85
+ const candidates = doc.querySelectorAll("p, li, blockquote, span, div");
86
+ for (const el of candidates) {
87
+ const text = el.textContent?.trim();
88
+ if (!text) continue;
89
+ if (text.length < 24) continue;
90
+ return el;
91
+ }
92
+ return doc.body;
93
+ };
94
+ const readFontSizePx = (doc) => {
95
+ const el = pickSampleEl(doc);
96
+ if (!el) return null;
97
+ const px = Number.parseFloat(getComputedStyle(el).fontSize);
98
+ return Number.isFinite(px) ? px : null;
99
+ };
100
+ const applyForceZoomIfNeeded = () => {
101
+ if (!forceZoomEnabled) return;
102
+ if (!activeDoc?.body) return;
103
+ activeDoc.body.setAttribute("data-epub-reader-force-zoom", "true");
104
+ };
105
+ const applyStyles = (check) => {
64
106
  if (destroyed) return;
65
107
  if (!viewer.renderer?.setStyles) return;
108
+ applyForceZoomIfNeeded();
66
109
  viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS));
67
110
  requestAnimationFrame(() => {
68
111
  setTimeout(() => {
69
112
  if (destroyed) return;
70
113
  viewer.renderer?.render?.();
71
114
  viewer.renderer?.expand?.();
115
+ if (!check) return;
116
+ if (forceZoomEnabled) return;
117
+ if (!activeDoc) return;
118
+ const { beforePx, beforeFontSize, afterFontSize } = check;
119
+ const afterPx = readFontSizePx(activeDoc);
120
+ if (beforePx == null || afterPx == null) return;
121
+ const isMeaningfulChange = Math.abs(afterFontSize - beforeFontSize) >= 10;
122
+ const isIneffective = Math.abs(afterPx - beforePx) < 0.5;
123
+ if (isMeaningfulChange && isIneffective) {
124
+ forceZoomEnabled = true;
125
+ applyForceZoomIfNeeded();
126
+ viewer.renderer?.setStyles?.(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS));
127
+ viewer.renderer?.render?.();
128
+ viewer.renderer?.expand?.();
129
+ }
72
130
  }, 50);
73
131
  });
74
132
  };
75
133
  const handleLoad = (e) => {
76
- applyStyles();
77
134
  const detail = e.detail;
135
+ activeDoc = detail?.doc ?? null;
136
+ applyStyles();
78
137
  if (detail?.doc) onContentLoad?.(detail.doc);
79
138
  };
80
139
  const handleRelocate = (e) => {
@@ -91,6 +150,8 @@ function createEBookReader(container, options = {}) {
91
150
  try {
92
151
  viewer.clearSearch?.();
93
152
  searchToken++;
153
+ activeDoc = null;
154
+ forceZoomEnabled = false;
94
155
  await viewer.open?.(file);
95
156
  const nextToc = viewer.book?.toc ?? [];
96
157
  toc = nextToc;
@@ -136,8 +197,10 @@ function createEBookReader(container, options = {}) {
136
197
  },
137
198
  setFontSize(nextFontSize) {
138
199
  const safe = Math.min(300, Math.max(50, nextFontSize));
200
+ const beforeFontSize = fontSize;
201
+ const beforePx = activeDoc && !forceZoomEnabled ? readFontSizePx(activeDoc) : null;
139
202
  fontSize = safe;
140
- applyStyles();
203
+ applyStyles({ beforePx, beforeFontSize, afterFontSize: safe });
141
204
  },
142
205
  setLineHeight(nextLineHeight) {
143
206
  const safe = Math.min(3, Math.max(1, nextLineHeight));