@evilflowers/evilflowersviewer

中文文档
0.7.9发布于 2026/11/13TypeScriptES Module构建来源已验证近 30 天下载 2172

EvilFlowersViewer

A focused, extensible PDF workspace for the browser

Read, search, annotate, cite and share documents without leaving the page.

原始使用文档
文档目录13 节
  1. Features
  2. Intelligent viewer
  3. Styling & isolation
  4. Accessibility
  5. Getting started
  6. Intelligent viewer
  7. Coordinates and pages
  8. Chat — options.chatPackage
  9. Regions — options.regionPackage
  10. AI highlights — options.agentAnnotationPackage
  11. Trying it locally
  12. Contributing
  13. Acknowledgment

EvilFlowersViewer

A focused, extensible PDF workspace for the browser

Read, search, annotate, cite and share documents without leaving the page.

Latest release npm package License

EvilFlowersViewer displaying a PDF with annotations, search tools and page thumbnails

EvilFlowersViewer is a PDF viewer based on the pdf.js library that lets users view and interact with PDF documents directly in the browser. Developed by a university team, it is reliable, efficient and easy to customize.

Features

  • PDF document rendering directly in the browser
  • Zoom in and out of documents
  • Page navigation through a page thumbnail view
  • Text search within documents
  • Optional semantic / AI search shown alongside keyword results
  • Share the entire document or with selected pages
  • Citation export in BibTeX, BibLaTeX, RIS and bibliography
  • Print the entire document or with selected pages
  • Download document
  • Light / dark themes (honours the host-provided theme, else the reader's saved choice, else the system preference)
  • Fullscreen mode
  • Editing document (freehand, shapes, highlighter, eraser, undo/redo) from a drawing rail docked beside the page, so it never covers the document
  • Page bookmarks, shown on the page corner and on the thumbnails
  • Keyboard-friendly and screen-reader accessible (see Accessibility)

Intelligent viewer

Each of these appears only when the host passes the callback it needs (see Intelligent viewer):

  • Explain a selected passage, a figure, a table or an equation in a draggable popup, and hand the explanation over to the chat
  • Reading chat about the open publication, docked beside the page: streamed answers with links back into the book, recommended publications as cards, and attachments (selected text, explanations, figures, highlights) as removable chips
  • Chat history: reopen earlier conversations; chats about other publications are read-only
  • Figure, table and equation hover: outlines what the catalog extracted from the page, with a card showing the crop, caption, table or LaTeX and actions for the chat and explain
  • AI highlights: sentences the assistant marked for a prompt, drawn over the text, with a comment badge per highlight, a sidebar list to step through them, and show/hide per annotation
  • Recommendations: similar, prerequisite and advanced publications for the open page or a selection

Styling & isolation

All viewer styles are scoped under a single .efv-viewer root class and Tailwind's global preflight is disabled, so importing this library never restyles the host application (*, body, buttons, inputs and scrollbars are left untouched). It works the other way too: every utility is emitted as .efv-viewer .x (Tailwind important: '.efv-viewer'), so a host that ships its own .text-primary or .bg-secondary cannot recolour the viewer. The Inter font is self-hosted (no external Google Fonts request). Consumers just import the stylesheet — no manual scoping/wrapping required:

import '@evilflowers/evilflowersviewer/dist/style.css';

Accessibility

The viewer is built to be usable with a keyboard and assistive technologies:

  • Keyboard: all controls are reachable via Tab; page thumbnails, tools and color swatches respond to Enter/Space. Shortcuts: ←/→ page, +/- zoom, F search, T table of contents, I info, S share, C citation, Esc steps back out (explain popup → modal → panel → tool → zoom → page → close). Typing in any text field, the host's included, never triggers these shortcuts.
  • Screen readers: every icon-only button has an accessible name (localized), toggles expose aria-pressed/aria-expanded, the left rail is a role="toolbar", dialogs are role="dialog" + aria-modal with focus trapping, and loaders announce via role="status". The rasterized page canvas is aria-hidden; the selectable pdf.js text layer carries the readable content, and the current search match is marked on it (role="mark", aria-current="true").
  • Focus & motion: a visible :focus-visible ring is shown for keyboard users, and all animations respect prefers-reduced-motion.
  • The viewer root carries a lang attribute matching options.lang so pronunciation is correct. All a11y CSS is scoped under .efv-viewer and never touches the host page.

Getting started

Requirements: Node ≥ 22.13 (required by pdfjs-dist 6).

To get started with EvilFlowersViewer, follow these steps:

  1. Install EvilFLowersViewerPackage:
npm install @evilflowers/evilflowersviewer
  1. Import the default renderPDFViewer function and the stylesheet:
import renderPDFViewer from '@evilflowers/evilflowersviewer';
import '@evilflowers/evilflowersviewer/dist/style.css';
  1. Call it with a single options object. It returns the Vue App instance so you can unmount it on teardown:
const app = renderPDFViewer({
  rootId: '#my-viewer',      // CSS selector of the mount element
  data: uint8ArrayOfPdf,      // TypedArray of the PDF bytes
  options: { theme: 'light', lang: 'en' /* … */ },
  config: { download: true, share: true, print: true, edit: false },
});

// later, e.g. on route change:
app.unmount();

Viewer Options and Properties Documentation

InputDescription
rootIdID of your component where you use the renderViewer function.
dataTypedArray (e.g. Uint8Array) with the PDF bytes. Pass either data or url.
urlURL of the PDF. pdf.js fetches it itself and shows a progress bar; when the server answers HTTP Range requests (Accept-Ranges: bytes, 206), page 1 renders before the whole file has downloaded. Without Range support the file is streamed whole.
httpHeadersOptional Record<string, string> sent with every url request, e.g. Authorization.
withCredentialsOptional boolean. Send cookies with cross-origin url requests.
rangeChunkSizeOptional number. Bytes per Range request (pdf.js default 65536).
optionsOptional object consisting of theme, language, citationBib, homeFunction, etc.
options.theme'dark' or 'light'. Forces the viewer's theme. Without it the viewer uses the reader's last choice of the sun/moon toggle (saved in localStorage as efv-theme), else the system preference, which it follows live.
options.lang'sk' or 'en'. Specifies the language of the viewer.
options.citationBibString or null. Contains the citation of the document (e.g., @article{name, ...}).
options.homeFunctionFunction (type: () => void) or null. Redirects to the home view.
options.closeFunctionFunction (type: () => void) or null. Closes the viewer.
options.shareFunctionFunction (type: `(pages: string
shareFunction.pagesString with selected pages (e.g., '1,3-6,10') or null (indicating no pages).
shareFunction.expaireDateISO string specifying the lifespan of the shared document.
shareFunction => returnReturns a string containing the link to the shared document.
options.printFunctionFunction (type: `(pages: string
options.semanticSearchFunctionFunction (type: (query: string) => Promise<ISemanticSearchResult[]>) or null. Optional semantic/AI search. When supplied, the search panel shows a second column of AI results ({ page, text, score? }) next to the keyword matches; when omitted the panel stays keyword-only.
options.editPackageOptional object for managing edit features, containing the following functions:
editPackage.saveGroupFuncFunction (type: (name: string) => Promise<{ response: { id: string } }>). Saves a new group and returns its ID.
editPackage.getGroupsFuncFunction (type: () => Promise<{ id: string; name: string }[]>). Retrieves all available groups.
editPackage.saveLayerFuncFunction (type: `(svg: string, groupId: string, page: number) => Promise<ILayer
editPackage.updateLayerFuncFunction (type: (id: string, svg: string, groupId: string, page: number) => Promise<void>). Updates an existing layer in a group on a specific page.
editPackage.getLayerFuncFunction (type: `(page: number, groupId: string) => Promise<ILayer
editPackage.getLayersFuncOptional function (type: (groupId: string) => Promise<IPageLayer[]>, IPageLayer = { id, svg, page }). Returns every layer of the group in one request; the viewer loads them once on entering edit mode, pages missing from the result have no layer, and page flips no longer fetch. On failure the viewer falls back to getLayerFunc. With the catalog: GET /api/v1/annotation-items?annotation_id=<groupId>&paginate=false.
options.pageBookmarkPackageOptional { getBookmarksFunc, addBookmarkFunc, removeBookmarkFunc } persisting page bookmarks. Without it bookmarks still work but last only as long as the viewer.
options.openEntryDetailFunctionFunction (type: (entry: ISuggestedEntry) => void). Opens the host's detail view for a clicked publication card (recommendations, chat).
options.bookmarkToggleFunctionFunction (type: (entry: ISuggestedEntry) => Promise<{ isOnShelf, shelfRecordId? }>). Adds or removes a card's publication from the reader's shelf.
options.suggestionsFunctionFunction (type: (kind: SUGGESTION_KIND, context?: { selectedText?, page? }) => Promise<ISuggestedEntry[]>). Enables recommendations and the selection menu's "Similar".
options.explainFunctionFunction (type: (text: string, context?: IExplainContext) => Promise<IExplainResult>). Enables "Explain"; context carries page and, for a figure/table/equation, regionId.
options.chatPackageOptional IChatPackage. Enables the reading chat — see Intelligent viewer.
options.regionPackageOptional IRegionPackage. Enables figure/table/equation hover.
options.agentAnnotationPackageOptional IAgentAnnotationPackage. Enables the AI highlight layer and its sidebar.
configOptional configuration object controlling viewer features.
config.downloadBoolean. Enables or disables the download feature.
config.shareBoolean. Enables or disables the share feature.
config.printBoolean. Enables or disables the print feature.
config.editBoolean. Enables or disables the edit feature.

Intelligent viewer

The viewer never talks to a backend itself. Every intelligent feature is a host callback, and a feature is hidden entirely when its callback is not passed — no button, no menu entry, no layer. All types are exported from the package.

Coordinates and pages

  • Page numbers are always the reader's page numbers (1-based, as shown in the viewer). A host serving a cut or re-ordered copy converts them before answering.
  • Positions (IPageBox) are fractions 0..1 of the page width and height with a top-left origin: { x0, y0, x1, y1 }. They stay correct at any zoom.

Chat — options.chatPackage

chatPackage: {
  // Required. Stream the answer through the handlers; resolve when done.
  // chatId is the conversation to continue, or null to start one — return the id used.
  sendMessageFunc: async (message, contexts, { onChunk, onMessage, onEntries, onError }, { page, chatId }) => {
    // ...
    return { chatId };
  },
  resetFunc: () => {},                 // optional: forget the current conversation
  suggestions: ['Summarize this chapter'], // optional: prompt shortcuts in an empty chat
  getChatsFunc: async () => [...],     // optional, with getChatFunc: enables chat history
  getChatFunc: async (chatId) => [...],
  deleteChatFunc: async (chatId) => {}, // optional: delete buttons in history
}
  • The open publication is always the chat's subject and is not sent as a context. contexts carries what the reader attached: selection, explain, region or highlight (IChatContext).
  • Answer text is markdown. [[page:12]] and [[region:<id>]] render as links that jump into the book. onEntries shows publications as cards (the assistant's recommendation tool).
  • History lists IChatSummary items; aboutThisBook: false chats open read-only.

Regions — options.regionPackage

regionPackage: {
  // Called once per page and cached.
  getRegionsFunc: async (page) => [
    { id, kind: 'figure' | 'table' | 'equation', page, bbox, caption, category, markdown, latex, imageUrl },
  ],
}

Without imageUrl the card crops the region from the rendered page.

AI highlights — options.agentAnnotationPackage

agentAnnotationPackage: {
  getAnnotationsFunc: async () => [
    { id, title, prompt, summary, status: 'pending' | 'done' | 'failed', unresolvedCount,
      highlights: [{ id, seq, page, quads: [IPageBox, ...], text, note }] },
  ],
  requestAnnotationFunc: async (prompt) => annotation, // optional: "ask to highlight" box
  deleteHighlightFunc: async (annotationId, highlightId) => {}, // optional
  deleteAnnotationFunc: async (annotationId) => {},             // optional
}

Each highlight is one sentence in reading order (seq), with one quad per text line.

Trying it locally

npm run dev mounts the sample app (src/App.vue) with every feature wired to mocks in src/assets/examples/params/examples.ts. The AI highlight mock reads the sample PDF's real text lines, so highlights land on actual text; region boxes are fixed per page.

Contributing

We welcome contributions from the community to help make EvilFlowersViewer even better. To contribute, please follow these steps:

  1. Fork the EvilFlowersViewer repository
  2. Create a new branch for your changes
  3. Make your changes and commit them with a clear commit message
  4. Push your changes to your forked repository
  5. Create a pull request to merge your changes into the main EvilFlowersViewer repository

Acknowledgment

This open-source project is maintained by students and PhD candidates of the Faculty of Informatics and Information Technologies at the Slovak University of Technology. The software is utilized by the university, aligning with its educational and research activities. We appreciate the faculty's support of our work and their contribution to the open-source community.

版本与发布记录

@evilflowers/evilflowersviewer 共发布 72 个版本,首次发布于 2023/03/17;过去 12 个月发布了 18 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.7.9latest2026/11/131 天前
0.7.72026/11/095 天前
0.7.62026/11/059 天前
0.7.52026/11/059 天前
0.7.42026/11/059 天前
0.7.32026/10/3015 天前

关于 @evilflowers/evilflowersviewer 的常见问题

@evilflowers/evilflowersviewer 是什么?

EvilFlowersViewer A focused, extensible PDF workspace for the browser Read, search, annotate, cite and share documents without leaving the page.

如何安装 @evilflowers/evilflowersviewer?

在项目目录运行 npm install @evilflowers/evilflowersviewer,或使用 pnpm add / yarn add / bun add。

@evilflowers/evilflowersviewer 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@evilflowers/evilflowersviewer 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@evilflowers/evilflowersviewer 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.13.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@evilflowers/evilflowersviewer 可以商用吗?

@evilflowers/evilflowersviewer 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@evilflowers/evilflowersviewer 的下载量有多少?

根据 npm 官方下载统计,@evilflowers/evilflowersviewer 在 2026/10/14 – 2026/11/12 这 30 天内被下载 2,172 次,日均约 72 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@evilflowers/evilflowersviewer 还在维护吗?

最新版本 0.7.9 发布于 2026/11/13(1 天前);至今共发布 72 个版本,其中过去 12 个月发布了 18 个;首次发布于 2023/03/17。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。