import type { EpubRendition } from "./epubRendition"; import { normalizeHighlightColor } from "@/constants/bookmarkHighlightColors"; export interface BookmarkHighlight { id: string; epub_cfi: string; highlight_color?: string | null; } type EpubAnnotations = { highlight?: ( cfiRange: string, data: Record, callback?: () => void, id?: string, styles?: Record, ) => void; remove?: (cfiRange: string, type: string) => void; }; export type EpubRenditionWithHighlights = EpubRendition & { annotations?: EpubAnnotations; on?: (event: string, handler: () => void) => void; off?: (event: string, handler: () => void) => void; }; function annotationId(bookmarkId: string): string { return `bookmark-hl-${bookmarkId}`; } export function applyBookmarkHighlight( rendition: EpubRenditionWithHighlights, bookmark: BookmarkHighlight, ): void { const cfi = (bookmark.epub_cfi || "").trim(); if (!cfi || !rendition.annotations?.highlight) return; const color = normalizeHighlightColor(bookmark.highlight_color); try { rendition.annotations.highlight( cfi, {}, undefined, annotationId(bookmark.id), { fill: color, "fill-opacity": "0.45", "mix-blend-mode": "multiply", }, ); } catch { /* CFI may not be in the current spine slice */ } } export function removeBookmarkHighlight( rendition: EpubRenditionWithHighlights, bookmark: BookmarkHighlight, ): void { const cfi = (bookmark.epub_cfi || "").trim(); if (!cfi || !rendition.annotations?.remove) return; try { rendition.annotations.remove(cfi, "highlight"); } catch { /* ignore */ } } export function syncBookmarkHighlights( rendition: EpubRenditionWithHighlights | null, bookmarks: BookmarkHighlight[], applied: Map, ): void { if (!rendition?.annotations?.highlight) return; const nextById = new Map(bookmarks.map((b) => [b.id, b])); for (const [id, cfi] of applied) { if (!nextById.has(id)) { removeBookmarkHighlight(rendition, { id, epub_cfi: cfi }); applied.delete(id); } } for (const bookmark of bookmarks) { const prevCfi = applied.get(bookmark.id); if (prevCfi && prevCfi !== bookmark.epub_cfi) { removeBookmarkHighlight(rendition, { id: bookmark.id, epub_cfi: prevCfi }); applied.delete(bookmark.id); } if (applied.has(bookmark.id)) continue; applyBookmarkHighlight(rendition, bookmark); applied.set(bookmark.id, bookmark.epub_cfi); } }