npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ofd-reader

v0.2.0

Published

A lightweight browser OFD reader component with SVG rendering.

Readme

OFD Reader

English

轻量、框架无关的 Web OFD 阅读组件。当前项目处于 0.x 预览开发阶段,重点是稳定基础阅读能力和 TypeScript API。

功能状态

  • OFD ZIP/XML 解析。
  • SVG 页面渲染。
  • 本地 OFD 文件加载。
  • 缩放、适宽、适页。
  • 浏览器打印。
  • 页码跳转和当前页事件。
  • 缩略图导航。
  • 搜索高亮,支持基础 API 模式和内置右侧搜索面板。
  • loading、empty、error 状态。
  • 基础 Clips 裁剪支持。

文本选择、签章展示和验签仍在规划中。

安装

npm install ofd-reader

当前 0.x 为预览版,适合先在真实业务环境中验证浏览、缩放、页码跳转、缩略图、打印和搜索能力。

许可证

本项目使用 AGPL-3.0-or-later 许可证。

AGPL 允许商业使用,但如果将本项目或其修改版本用于网络服务、Web 系统或分发场景,需要按照 AGPL 要求向用户提供对应源码。若需要闭源商业授权,请联系维护者另行授权。

基础使用

import { createOFDViewer } from 'ofd-reader'
import 'ofd-reader/style.css'

const container = document.getElementById('app')

if (!container) {
  throw new Error('Container not found')
}

const viewer = createOFDViewer(container, {
  defaultScale: 'fit-width',
  maxFitWidth: 980,
  thumbnails: {
    enabled: true,
    width: 110
  }
})

await viewer.load(file)

load() 接收浏览器 FileBlobArrayBuffer。如果你需要从远程地址加载,可以先自行 fetch

const response = await fetch('/demo.ofd')
const blob = await response.blob()

await viewer.load(blob)

自定义工具栏

组件不内置键盘快捷键。业务侧可以通过 API 绑定按钮或快捷键:

zoomInButton.onclick = () => viewer.zoomIn()
zoomOutButton.onclick = () => viewer.zoomOut()
fitWidthButton.onclick = () => viewer.fitWidth()
fitPageButton.onclick = () => viewer.fitPage()

window.addEventListener('keydown', event => {
  if (event.ctrlKey && event.key === '=') {
    event.preventDefault()
    viewer.zoomIn()
  }
})

打印

文档加载完成后可以调用 viewer.print()

await viewer.print({
  title: 'invoice.ofd'
})

打印流程会将 OFD 重新渲染到隔离的打印文档中,因此当前阅读缩放比例、滚动位置、缩略图和宿主页面样式不会影响打印页面。建议业务侧在按钮点击等用户操作中调用,降低浏览器拦截打印对话框的概率。

浏览器打印仍受浏览器、操作系统和打印机驱动控制。组件不能实现静默打印,也不能保证硬件边距、混合页面尺寸或打印机专有设置完全可控。

搜索

每个 viewer 实例都提供搜索 API。业务侧可以完全自定义搜索框、按钮和结果列表:

viewer.search('发票')
viewer.nextSearchResult()
viewer.previousSearchResult()
viewer.goToSearchResult(0)

const state = viewer.getSearchState()
const results = viewer.getSearchResults()
const summary = viewer.getSearchSummary()

如果需要使用内置右侧搜索面板:

const viewer = createOFDViewer(container, {
  search: {
    mode: 'panel',
    defaultOpen: true,
    width: 280,
    panel: {
      placeholder: '查找',
      labels: {
        previous: '上一个',
        next: '下一个',
        clear: '清空',
        noDocument: '未加载文档',
        noSearch: '输入关键词搜索',
        noMatches: '无匹配结果',
        page: pageNumber => `第 ${pageNumber} 页`
      }
    }
  }
})

内置面板也可以由代码控制。调用 search() 会同步更新高亮、当前命中和内置结果列表:

viewer.showSearchPanel()
viewer.search('发票')
viewer.hideSearchPanel()
viewer.toggleSearchPanel(true)

搜索选项:

viewer.search('发票', {
  caseSensitive: false,
  wholeWord: false,
  includeInvisibleText: false,
  highlightAll: true,
  maxResults: 1000
})

getSearchSummary() 返回按页聚合的结果,适合业务侧自定义摘要列表。每条摘要包含全局 resultIndex,可传给 goToSearchResult(resultIndex) 跳转。

配置项

interface OFDViewerOptions {
  defaultScale?: number | 'fit-width' | 'fit-page'
  minScale?: number
  maxScale?: number
  scaleStep?: number
  maxFitWidth?: number
  thumbnails?: boolean | {
    enabled?: boolean
    width?: number
  }
  search?: boolean | {
    mode?: 'api' | 'panel'
    defaultOpen?: boolean
    width?: number
    panel?: {
      placeholder?: string
      labels?: {
        previous?: string
        previousAriaLabel?: string
        next?: string
        nextAriaLabel?: string
        clear?: string
        clearAriaLabel?: string
        noDocument?: string
        noSearch?: string
        noMatches?: string
        page?: (pageNumber: number, item: OFDSearchSummaryItem) => string
      }
    }
  }
  stateMessages?: OFDViewerStateMessages
  className?: string
}
  • defaultScale:初始缩放比例,或使用适宽/适页模式。
  • minScale / maxScale:缩放上下限,默认 0.254
  • scaleStepzoomIn() / zoomOut() 的步进,默认 0.1
  • maxFitWidth:限制 fit-width 在大屏下的最大阅读宽度。
  • thumbnails:是否启用缩略图,或配置缩略图宽度。
  • search:启用内置搜索面板,或保持 API 模式让业务侧自定义 UI。
  • stateMessages:自定义 empty/loading/error 文案。
  • className:附加到 viewer 根节点的 class。

事件监听

viewer.on('pagechange', ({ pageIndex, previousPageIndex, pageCount }) => {
  console.log(pageIndex + 1, previousPageIndex + 1, pageCount)
})

viewer.on('scalechange', ({ scale, previousScale, mode }) => {
  console.log(scale, previousScale, mode)
})

viewer.on('statuschange', ({ status, previousStatus, error }) => {
  console.log(status, previousStatus, error)
})

viewer.on('printstart', ({ pageCount }) => {
  console.log(`正在准备打印 ${pageCount} 页`)
})

viewer.on('printend', ({ result }) => {
  console.log(result.pageCount, result.printedAt)
})

viewer.on('printerror', ({ error }) => {
  console.error(error)
})

viewer.on('searchchange', ({ state, summary }) => {
  console.log(state.keyword, state.total, summary.length)
})

viewer.on('searchresultchange', ({ state, result }) => {
  console.log(state.currentIndex, result?.pageNumber)
})

事件 payload:

interface OFDViewerEventMap {
  load: {
    package: OFDPackage
    pageCount: number
  }
  error: {
    error: unknown
  }
  scalechange: {
    scale: number
    previousScale: number
    mode: 'fit-width' | 'fit-page' | null
  }
  pagechange: {
    pageIndex: number
    previousPageIndex: number
    pageCount: number
  }
  statuschange: {
    status: 'empty' | 'loading' | 'ready' | 'error'
    previousStatus: 'empty' | 'loading' | 'ready' | 'error'
    error?: unknown
  }
  printstart: {
    package: OFDPackage
    pageCount: number
  }
  printend: {
    result: OFDPrintResult
  }
  printerror: {
    error: unknown
  }
  searchchange: {
    state: OFDSearchState
    results: OFDSearchResult[]
    summary: OFDSearchSummaryItem[]
  }
  searchresultchange: {
    state: OFDSearchState
    result: OFDSearchResult | null
    previousIndex: number
  }
}

公开 API

parse(file: ArrayBuffer | Blob): Promise<OFDPackage>
render(pkg: OFDPackage, container: HTMLElement, options?: RenderOptions): Promise<void>
print(pkg: OFDPackage, options?: OFDPrintOptions): Promise<OFDPrintResult>
createOFDViewer(container: HTMLElement, options?: OFDViewerOptions): OFDViewer

普通业务集成优先使用 createOFDViewerparserender 更适合调试、二次封装或高级场景。

OFDPackage 中的 zip 字段被视为内部资源,公开类型中不会绑定到具体 ZIP 实现。

低层 API 示例:

import { parse, render, print } from 'ofd-reader'

const pkg = await parse(file)
await render(pkg, container)
await print(pkg, {
  title: 'invoice.ofd'
})

Viewer 方法

  • load(file)
  • clear()
  • destroy()
  • setScale(scale)
  • zoomIn()
  • zoomOut()
  • fitWidth()
  • fitPage()
  • goToPage(pageIndex)
  • previousPage()
  • nextPage()
  • showThumbnails()
  • hideThumbnails()
  • toggleThumbnails(force?)
  • showSearchPanel()
  • hideSearchPanel()
  • toggleSearchPanel(force?)
  • print(options?)
  • search(keyword, options?)
  • clearSearch()
  • nextSearchResult()
  • previousSearchResult()
  • goToSearchResult(index)
  • getScale()
  • getScaleMode()
  • getCurrentPage()
  • getPageCount()
  • getPackage()
  • getStatus()
  • getSearchState()
  • getSearchResults()
  • getSearchSummary()
  • on(event, handler)
  • off(event, handler)

样式

阅读器默认样式需要单独引入:

import 'ofd-reader/style.css'

发布样式仅作用于 .ofd-viewer,不包含本地 demo 的 body#app、工具栏或文件选择器样式。如需定制主题,可在 .ofd-viewer 或通过 className 传入的自定义类上覆盖 --ofd-* 颜色变量,并按需同步设置浅色背景、进度轨道和选择色。

组件不依赖任何 UI 框架,外层容器的宽高由业务页面控制。viewer 会在传入容器内部创建自己的阅读区域和缩略图区域。

浏览器兼容

当前实现面向现代浏览器,同时对 Chrome 69+ 做了基础兼容处理。主要依赖:

  • Blob
  • URL.createObjectURL
  • FontFace
  • ResizeObserver
  • IntersectionObserver
  • SVG 渲染能力
  • SVG 文本几何能力,用于搜索高亮

推荐运行环境:

  • Chrome / Edge 90+
  • Firefox 90+
  • Safari 15+

基础兼容目标:

  • Chrome 69+

Chrome 69+ 可用于基础阅读、缩放、适宽/适页、页码跳转和缩略图导航。由于老版本浏览器在 CSS、字体和滚动行为上仍可能存在细节差异,发布前建议在目标 WebView 或浏览器中做一次视觉回归检查。

如果目标环境缺少 ResizeObserverIntersectionObserver,基础页面渲染仍可工作,但适宽/适页重算、缩略图懒加载等体验可能降级。组件目前不提供内置 polyfill,业务项目可按目标浏览器自行引入。

已知限制

  • 暂无正式单元测试。
  • 搜索基于已渲染 SVG 文本,不支持模糊搜索、拼音搜索、正则搜索或未渲染页面虚拟化搜索。
  • 文本选择尚未实现。
  • CompositeObject、复杂颜色管理、Pattern Reflect 等长尾能力尚未完整支持。
  • 签章展示、签名解析和验签属于后期增强能力。