diff --git a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx index e855fc63..10d7ff52 100644 --- a/apps/readest-app/src/app/reader/components/FoliateViewer.tsx +++ b/apps/readest-app/src/app/reader/components/FoliateViewer.tsx @@ -13,7 +13,12 @@ import { usePagination } from '../hooks/usePagination'; import { useFoliateEvents } from '../hooks/useFoliateEvents'; import { useProgressSync } from '../hooks/useProgressSync'; import { useProgressAutoSave } from '../hooks/useProgressAutoSave'; -import { applyTranslationStyles, getStyles, transformStylesheet } from '@/utils/style'; +import { + applyImageStyle, + applyTranslationStyle, + getStyles, + transformStylesheet, +} from '@/utils/style'; import { mountAdditionalFonts } from '@/utils/font'; import { getBookDirFromLanguage, getBookDirFromWritingMode } from '@/utils/book'; import { useUICSS } from '@/hooks/useUICSS'; @@ -129,6 +134,8 @@ const FoliateViewer: React.FC<{ mountAdditionalFonts(detail.doc, isCJKLang(bookData.book?.primaryLanguage)); + applyImageStyle(detail.doc); + // Inline scripts in tauri platforms are not executed by default if (viewSettings.allowScript && isTauriAppPlatform()) { evalInlineScripts(detail.doc); @@ -242,7 +249,7 @@ const FoliateViewer: React.FC<{ const height = viewHeight - insets.top - insets.bottom; book.transformTarget?.addEventListener('data', getDocTransformHandler({ width, height })); view.renderer.setStyles?.(getStyles(viewSettings)); - applyTranslationStyles(viewSettings); + applyTranslationStyle(viewSettings); const animated = viewSettings.animated!; const maxColumnCount = viewSettings.maxColumnCount!; diff --git a/apps/readest-app/src/utils/sel.ts b/apps/readest-app/src/utils/sel.ts index 3f4b5a46..74f777d1 100644 --- a/apps/readest-app/src/utils/sel.ts +++ b/apps/readest-app/src/utils/sel.ts @@ -84,7 +84,24 @@ export const getPosition = ( const [sx, , , sy] = match?.[1]?.split(/\s*,\s*/)?.map((x) => parseFloat(x)) ?? []; const frame = frameElement?.getBoundingClientRect() ?? { top: 0, left: 0 }; - const rects = Array.from(target.getClientRects()); + let padding = { top: 0, right: 0, bottom: 0, left: 0 }; + if ('nodeType' in target && target.nodeType === 1) { + const computedStyle = window.getComputedStyle(target); + padding = { + top: parseInt(computedStyle.paddingTop, 10) || 0, + right: parseInt(computedStyle.paddingRight, 10) || 0, + bottom: parseInt(computedStyle.paddingBottom, 10) || 0, + left: parseInt(computedStyle.paddingLeft, 10) || 0, + }; + } + const rects = Array.from(target.getClientRects()).map((rect) => { + return { + top: rect.top + padding.top, + right: rect.right - padding.right, + bottom: rect.bottom - padding.bottom, + left: rect.left + padding.left, + }; + }); const first = frameRect(frame, rects[0], sx, sy); const last = frameRect(frame, rects.at(-1), sx, sy); diff --git a/apps/readest-app/src/utils/style.ts b/apps/readest-app/src/utils/style.ts index 05e0ae50..3f44609c 100644 --- a/apps/readest-app/src/utils/style.ts +++ b/apps/readest-app/src/utils/style.ts @@ -211,6 +211,11 @@ const getLayoutStyles = ( width: auto; background-color: transparent !important; } + /* enlarge the clickable area of links */ + a { + padding: 10px; + margin: -10px; + } p, blockquote, dd, div:not(:has(*:not(b, a, em, i, strong, u, span))) { line-height: ${lineSpacing} ${overrideLayout ? '!important' : ''}; word-spacing: ${wordSpacing}px ${overrideLayout ? '!important' : ''}; @@ -286,13 +291,12 @@ const getLayoutStyles = ( } /* inline images without dimension */ - p > img, span > img, sup img { + sup img { height: 1em; } - p:has(> img:only-child) img, span:has(> img:only-child) img, - p:has(> a:first-child + img:last-child) img, - span:has(> a:first-child + img:last-child) img { - height: auto; + img.has-text-siblings { + height: 1em; + vertical-align: text-bottom; } .ie6 img { width: auto; @@ -328,6 +332,8 @@ export const getFootnoteStyles = () => ` a:any-link { text-decoration: none; + padding: unset; + margin: unset; } ol { @@ -446,8 +452,8 @@ export const getStyles = (viewSettings: ViewSettings, themeCode?: ThemeCode) => return `${layoutStyles}\n${fontStyles}\n${colorStyles}\n${translationStyles}\n${userStylesheet}`; }; -export const applyTranslationStyles = (viewSettings: ViewSettings) => { - const styleId = 'translation-styles'; +export const applyTranslationStyle = (viewSettings: ViewSettings) => { + const styleId = 'translation-style'; const existingStyle = document.getElementById(styleId); if (existingStyle) { @@ -509,3 +515,16 @@ export const transformStylesheet = (vw: number, vh: number, css: string) => { .replace(/[\s;]color\s*:\s*rgb\(0,\s*0,\s*0\)/gi, 'color: var(--theme-fg-color)'); return css; }; + +export const applyImageStyle = (document: Document) => { + document.querySelectorAll('img').forEach((img) => { + const parent = img.parentNode; + if (!parent || parent.nodeType !== Node.ELEMENT_NODE) return; + const hasTextSiblings = Array.from(parent.childNodes).some( + (node) => node.nodeType === Node.TEXT_NODE && node.textContent?.trim(), + ); + if (hasTextSiblings) { + img.classList.add('has-text-siblings'); + } + }); +};