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

react-reviso

v0.3.16

Published

React component for document restoration QA, review, and correction

Readme

react-reviso

An embeddable React component for reviewing and correcting OCR text on restored document images. Designed for QA workflows — users preview restored documents, validate regions, compare original vs restored, and edit corrections when needed.

Features

  • Preview-First UX — default view shows restored documents for QA review, editing is entered explicitly
  • Side-by-Side Comparison — original (left) vs restored (right) with independent zoom/pan
  • Slider Comparison — drag slider overlay comparing original vs restored, horizontal or vertical orientation
  • Comparison Sources — toggle between Overlay (original image with corrected text) and Synthetic Reconstruct (white background with text only)
  • Region Validation — click checkmarks to mark regions as validated; progress bar tracks review completion
  • Inline Editing — click a region to edit text, Tab/Shift+Tab to navigate between regions
  • Region Management — create, resize, move, delete text regions; customise font, color, border, background, text position
  • Auto Background Detection — restored preview automatically detects and fills region backgrounds from the document image
  • Export — Synthetic Reconstruct, Overlay, Original, and Document JSON export types; PDF or PNG format with multi-page PNG auto-zipped; also available as a headless exportDocument() API
  • Undo/Redo — Ctrl+Z / Ctrl+Shift+Z with full snapshot history
  • Fit to View — reset zoom across all view modes from toolbar
  • Theme Integration — inherits host app's MUI theme, accepts theme overrides
  • Feature Toggles — enable/disable editing, region creation, comparison, export via props
  • Keyboard Shortcuts — press ? to see all available shortcuts

Workflow

  1. Preview (default) — review restored documents in side-by-side or slider comparison mode
  2. Validate — click checkmarks on regions to confirm OCR corrections are accurate
  3. Edit (on demand) — enter edit mode to fix text, create/delete regions, adjust styles
  4. Export — download as Synthetic Reconstruct, Overlay, Original, or Document JSON in PDF or PNG format

Installation

npm install react-reviso

Peer dependencies

npm install react react-dom @mui/material @mui/icons-material @emotion/react @emotion/styled framer-motion

Usage

import { Reviso } from 'react-reviso';
import type { RevisoDocument } from 'react-reviso';

const document: RevisoDocument = {
  id: 'doc-1',
  name: 'My Document',
  pages: [
    {
      id: 'page-1',
      pageNumber: 1,
      imageSrc: '/path/to/restored-image.png',
      originalImageSrc: '/path/to/original-image.png',
      width: 1200,
      height: 1600,
      regions: [
        {
          id: 'region-1',
          x: 100,
          y: 200,
          width: 300,
          height: 40,
          text: 'Corrected text',
          originalText: 'Original OCR text',
        },
      ],
    },
  ],
};

function App() {
  return (
    <Reviso
      document={document}
      onChange={(dirtyPages) => console.log('Dirty pages:', dirtyPages)}
      onPageChange={(pageId) => console.log('Page:', pageId)}
      onSelectionChange={(regionId) => console.log('Selection:', regionId)}
    />
  );
}

Theming

Reviso automatically inherits the MUI theme from a parent ThemeProvider. You can also pass a theme prop for component-level overrides — these are deep-merged on top of the inherited theme.

Option 1: Inherit from host app theme

Wrap your app (or a parent component) with MUI's ThemeProvider. Reviso picks up the palette, typography, and other tokens automatically.

import { ThemeProvider, createTheme } from '@mui/material/styles';

const appTheme = createTheme({
  palette: {
    mode: 'dark',
    primary: { main: '#90caf9' },
  },
});

function App() {
  return (
    <ThemeProvider theme={appTheme}>
      <Reviso document={document} />
    </ThemeProvider>
  );
}

Option 2: Override via theme prop

Pass MUI ThemeOptions directly to the theme prop. These overrides are deep-merged on top of whatever theme Reviso inherits from the parent.

<Reviso
  document={document}
  theme={{
    palette: {
      mode: 'dark',
      primary: { main: '#ce93d8' },
      background: { default: '#1a1a2e', paper: '#16213e' },
    },
    typography: {
      fontFamily: '"Fira Code", monospace',
    },
  }}
/>

Option 3: Combine both

Use a host theme for global styles and the theme prop for Reviso-specific tweaks.

const appTheme = createTheme({
  palette: { mode: 'dark' },
  typography: { fontFamily: '"Inter", sans-serif' },
});

<ThemeProvider theme={appTheme}>
  <Reviso
    document={document}
    theme={{
      palette: {
        primary: { main: '#ff7043' },
      },
    }}
  />
</ThemeProvider>

In this example, Reviso uses the host's dark mode and Inter font, but overrides the primary color to deep orange.

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | document | RevisoDocument | required | The document to display and review | | editable | boolean | true | Enable/disable editing (when false, preview and validation only) | | showSidebar | boolean | true | Show/hide page thumbnail sidebar | | showToolbar | boolean | true | Show/hide the inline toolbar | | features | { comparison?, export?, regionCreation? } | all true | Feature toggles | | defaultRegionStyles | object | — | Default styles for new regions (see below) | | theme | ThemeOptions | — | MUI theme overrides | | initialPageId | string | first page | Initial page to display | | onChange | (dirtyPages: RevisoPage[]) => void | — | Fired on any change, returns only modified pages | | onRegionChange | (event) => void | — | Granular per-region change event ({ type, pageId, regionId, region? }) | | onPageChange | (pageId: string) => void | — | Fired on page navigation | | onSelectionChange | (regionId: string \| null) => void | — | Fired on region select/deselect | | onExport | (format: 'json' \| 'pdf' \| 'png', data: Blob) => void | — | Intercept export (replaces auto-download) |

defaultRegionStyles

| Property | Type | Default | Description | |----------|------|---------|-------------| | fontColor | string | '#1565c0' | Text color (blue) | | fontFamily | string | 'Inter' | Font family | | fontWeight | 'normal' \| 'bold' | 'normal' | Font weight | | fontStyle | 'normal' \| 'italic' | 'normal' | Font style | | textDecoration | 'none' \| 'line-through' | 'none' | Text decoration | | borderColor | string | '#4caf50' | Region border color (green) | | borderVisible | boolean | true | Show/hide region border | | backgroundColor | string | 'transparent' | Region background fill | | textPosition | 'inside' \| 'top' \| 'bottom' | 'top' | Where text renders relative to the region box |

RevisoRegion

| Property | Type | Required | Description | |----------|------|----------|-------------| | id | string | yes | Unique region identifier | | x | number | yes | X position (pixels from left) | | y | number | yes | Y position (pixels from top) | | width | number | yes | Region width in pixels | | height | number | yes | Region height in pixels | | text | string | yes | Current text content | | originalText | string | no | Original text (for diff tracking) | | fontColor | string | no | Text color | | fontFamily | string | no | Font family | | fontWeight | 'normal' \| 'bold' | no | Font weight | | fontStyle | 'normal' \| 'italic' | no | Font style | | textDecoration | 'none' \| 'line-through' | no | Text decoration | | borderColor | string | no | Border color | | borderVisible | boolean | no | Show/hide border | | backgroundColor | string | no | Background fill | | textPosition | 'inside' \| 'top' \| 'bottom' \| 'left' \| 'right' | no | Text placement relative to region | | isValidated | boolean | no | Whether the region has been reviewed and confirmed |

When does onChange fire?

onChange fires once per discrete user action — it does not fire continuously during drag operations. It returns only dirty (modified) pages, not the full document. A page is considered dirty if any region was edited, created, or deleted. Dirty flags are reset after each onChange call, so the same changes are not re-emitted.

| Action | When it fires | |--------|---------------| | Edit text | On commit (Enter, Tab, or blur — not on every keystroke) | | Move region | On mouse release (not during drag) | | Resize region | On mouse release (not during drag) | | Create region | When the new region is added | | Delete region | Immediately on delete | | Change style | Immediately on each change (bold, italic, color, text position, etc.) | | Toggle validation | Immediately when region checkmark is clicked | | Undo / Redo | Immediately on restore |

View Modes

Preview Mode (default)

The default landing view for QA review. Two comparison layouts:

  • Side-by-Side — original image (left) + restored image (right) with independent zoom/pan. Validation checkmarks overlay the restored pane.
  • Slider — single overlay with a draggable comparison slider. Supports horizontal (left/right) and vertical (top/bottom) orientation. No validation checkmarks in this mode.

Each layout supports two comparison sources via toolbar toggle:

  • Overlay — original page image with corrected text regions rendered on top
  • Synthetic Reconstruct — white background with only the corrected text at original positions

Edit Mode

Entered via the "Edit" button in the toolbar or Ctrl+E. Full editing capabilities:

  • Select, edit text, create/resize/delete regions
  • Undo/redo, style controls, text visibility toggle
  • Return to preview via the "Preview" button or Ctrl+E

Keyboard Shortcuts

| Shortcut | Action | |----------|--------| | Ctrl+E | Toggle Preview / Edit mode | | Escape | Exit edit mode (when nothing selected) | | ← / → | Previous / Next page | | PageUp / PageDown | Previous / Next page | | Ctrl+↑ / Ctrl+↓ | Previous / Next document | | Ctrl+Z | Undo | | Ctrl+Shift+Z | Redo | | N | Toggle create mode | | Delete | Delete selected region | | Tab / Shift+Tab | Next / Previous region | | Enter | Confirm edit | | ? | Show keyboard shortcuts help |

Export API

Export functionality is available in two forms — a ready-made dialog component and a headless function — both usable outside the Reviso editor.

Export Dialog

Use ExportDocumentDialog to show the same export dialog used inside the editor. It accepts a RevisoDocument directly — no Reviso component or stores needed.

import { useState } from 'react';
import { ExportDocumentDialog } from 'react-reviso';
import type { RevisoDocument } from 'react-reviso';

const doc: RevisoDocument = { /* ... */ };

function MyPage() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen(true)}>Export</button>
      <ExportDocumentDialog
        open={open}
        onClose={() => setOpen(false)}
        document={doc}
        onExport={(format, blob) => {
          // Optional: intercept instead of auto-downloading
          console.log(format, blob);
        }}
      />
    </>
  );
}

| Prop | Type | Required | Description | |------|------|----------|-------------| | open | boolean | yes | Whether the dialog is visible | | onClose | () => void | yes | Called when dialog should close | | document | RevisoDocument \| null | yes | The document to export | | onExport | (format: 'json' \| 'pdf' \| 'png', data: Blob) => void | no | Intercept export (replaces auto-download) |

Headless Function

Use exportDocument() for full programmatic control — useful for batch export, custom UIs, or server-side workflows.

import { exportDocument } from 'react-reviso';
import type { RevisoDocument } from 'react-reviso';

const doc: RevisoDocument = { /* ... */ };

const result = await exportDocument({
  documents: [doc],
  type: 'synthetic',  // 'synthetic' | 'overlay' | 'original' | 'json'
  format: 'pdf',      // 'pdf' | 'png' (ignored for 'json')
});

// result: { blob: Blob, filename: string, mimeType: string }

// Download it
const url = URL.createObjectURL(result.blob);
const link = document.createElement('a');
link.href = url;
link.download = result.filename;
link.click();
URL.revokeObjectURL(url);

Export Types

| Type | Description | |------|-------------| | synthetic | White background with corrected text at original positions | | overlay | Original page image with corrected text regions overlaid | | original | Original page image as-is | | json | Structured document JSON with all region data |

Export Formats

| Format | Behaviour | |--------|-----------| | pdf | All pages combined into a single PDF | | png | Single PNG for one page; ZIP archive for multiple pages |

PDF Font Support

PDF export uses Noto Sans font variants for multi-script text rendering. Fonts are loaded lazily — only the fonts needed for the detected scripts are loaded.

Supported scripts:

| Script | Font | Covers | |--------|------|--------| | Latin | Noto Sans | English, European languages | | CJK | Noto Sans SC | Chinese, Japanese kanji | | Tamil | Noto Sans Tamil | Tamil | | Khmer | Noto Sans Khmer | Khmer | | Thai | Noto Sans Thai | Thai |

Font resolution order:

  1. Local path (/assets/ by default, or custom path via setFontBasePath)
  2. StandardFonts fallback (Helvetica — Latin only)

Known limitations:

  • Korean (Hangul) is not currently supported in PDF export due to font size constraints (~10MB). Korean text will fall back to placeholder characters.
  • PDF export uses Noto Sans fonts regardless of the font selected in the editor. The editor font choice (e.g. Inter, Arial) only applies to on-screen rendering and PNG export.
  • Each text region uses a single font based on its dominant script. Mixed non-Latin scripts within a single region (e.g. Tamil + Chinese in one region) are not supported — use separate regions for different scripts.
  • fontWeight (bold) and fontStyle (italic) are respected. fontFamily is not used in PDF output.

Font setup (required for non-Latin PDF export):

PDF export loads font files at runtime via fetch(). These fonts are included in the npm package under dist/assets/ but must be copied to your app's public directory so they can be served over HTTP.

Step 1 — Copy font assets to your public directory:

cp -r node_modules/react-reviso/dist/assets/ public/assets/

This copies the following font files:

  • NotoSans-Regular.ttf, NotoSans-Bold.ttf (Latin)
  • NotoSansSC-Regular.ttf, NotoSansSC-Bold.ttf (CJK)
  • NotoSansTamil-Regular.ttf, NotoSansTamil-Bold.ttf (Tamil)
  • NotoSansKhmer-Regular.ttf, NotoSansKhmer-Bold.ttf (Khmer)
  • NotoSansThai-Regular.ttf, NotoSansThai-Bold.ttf (Thai)

By default, fonts are loaded from /assets/ at runtime. If you skip this step, non-Latin text in PDF exports will fall back to Helvetica (Latin only) and other scripts will render as placeholder characters.

Step 2 (optional) — Custom font path:

If your fonts are served from a different location (e.g. a subfolder or CDN), call setFontBasePath before exporting:

import { setFontBasePath } from 'react-reviso';

// Point to your custom font directory
setFontBasePath('/assets/fonts/');

// Or use a CDN
setFontBasePath('https://cdn.example.com/fonts/');

Framework-specific examples:

| Framework | Public directory | Copy command | |-----------|-----------------|--------------| | Vite | public/ | cp -r node_modules/react-reviso/dist/assets/ public/assets/ | | Next.js | public/ | cp -r node_modules/react-reviso/dist/assets/ public/assets/ | | Create React App | public/ | cp -r node_modules/react-reviso/dist/assets/ public/assets/ |

Tip: Add the copy command to a postinstall script in your package.json so fonts are copied automatically after npm install:

{
  "scripts": {
    "postinstall": "cp -r node_modules/react-reviso/dist/assets/ public/assets/"
  }
}

Development

Prerequisites

  • Node.js >= 18
  • npm >= 8

Setup

git clone https://github.com/zhernrong92/reviso.git
cd reviso
npm install
npm run dev

The dev server runs two demo routes:

  • / — Legacy standalone demo with file upload, multi-document support
  • /reviso — Embeddable component demo with a simulated host app layout

Commands

| Command | Description | |---------|-------------| | npm run dev | Start Vite dev server | | npm run build | Production build (type-check + bundle) | | npm run build:lib | Build library for publishing | | npm run preview | Preview production build | | npm run type-check | TypeScript type checking | | npm run lint | ESLint check |

Building the Library

The library build is separate from the demo app build. It produces ESM (.mjs) and CommonJS (.cjs) bundles with TypeScript declarations.

# Build the library (outputs to dist/)
npm run build:lib

This generates:

  • dist/index.mjs — ES module
  • dist/index.cjs — CommonJS module
  • dist/index.d.ts — TypeScript declarations

Peer dependencies (React, MUI, Emotion, Framer Motion) are not bundled — consumers must install them separately.

Testing Locally

To test the library in another project before publishing:

# 1. Build and pack
npm run build:lib
npm pack

# 2. In the consumer project, install from the tgz
npm install /path/to/react-reviso-x.x.x.tgz

Publishing to npm

# 1. Login
npm login

# 2. Bump version
npm version patch --no-git-tag-version

# 3. Build and publish
npm run build:lib
npm publish --access public

Tech Stack

| Technology | Purpose | |------------|---------| | React 18 | UI framework | | TypeScript 5 (strict) | Type safety | | Vite 5 | Dev server + library bundler | | MUI 6 | Component library, theming | | Zustand 5 + Immer | State management | | Framer Motion | Page transitions | | react-zoom-pan-pinch | Document viewer zoom/pan | | react-compare-slider | Before/after comparison slider | | pdf-lib | PDF export generation | | fflate | ZIP compression for multi-page PNG export | | nanoid | Unique ID generation |