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

cmyk-preview-toolkit

v2.0.1

Published

Dual CMYK/sRGB color handling for browser-based PDF editors. Convert between CMYK, RGB, HSL, and CIE Lab color spaces with press-accurate preview and palette snapping.

Readme

cmyk-preview-toolkit

Press-accurate CMYK colors in the browser — zero dependencies, full TypeScript.

Browsers render everything in sRGB. When pdf.js rasterizes a CMYK PDF, the preview canvas contains converted RGB pixels. If you sample those pixels and use them directly for export, CMYK fidelity is lost. This toolkit solves the problem by maintaining dual representations — a press-accurate source color alongside an sRGB preview — through every step of your pipeline.

npm version npm downloads bundle size CI TypeScript MIT License


Who Is This For?

  • 🖨️ PDF editor developers who need accurate CMYK export from a browser-based canvas
  • 🎨 Print & prepress tool builders who want color-accurate previews without ICC profile complexity
  • ⚛️ React developers building color pickers that should snap to a brand palette
  • 🔧 Anyone converting between CMYK, RGB, HSL, or CIE Lab in JavaScript/TypeScript

Why cmyk-preview-toolkit?

| Approach | Bundle Size | CMYK Fidelity | Browser-Native | Setup | |---|---|---|---|---| | cmyk-preview-toolkit | ~3 KB | ✅ Dual representation | ✅ Pure JS | npm install | | MuPDF / PDFium WASM | 5–15 MB | ✅ Full ICC | ❌ WASM required | Complex | | Manual conversion | 0 KB | ⚠️ Ad-hoc, error-prone | ✅ | DIY | | Ignoring CMYK | 0 KB | ❌ Colors shift | ✅ | None |

Features

  • pdf.js-compatible CMYK → sRGB polynomial conversion
  • RGB → CMYK (GCR approximation) for reverse conversion
  • HSL color space helpers (hexToHsl, hslToHex)
  • CIE76 Delta-E perceptual distance metric for palette snapping
  • Palette snapping — match EyeDropper / color-input samples to the nearest source color
  • Immutable state helpers for text, background, and border color roles
  • React hook (usePaletteColor) for automatic snapping in React apps
  • Full TypeScript types and JSDoc
  • ESM + CJS output with tree-shaking support
  • Zero runtime dependencies
  • GitHub Actions CI on Node 18/20/22

Installation

npm install cmyk-preview-toolkit
# or with yarn / pnpm
yarn add cmyk-preview-toolkit
pnpm add cmyk-preview-toolkit

Quick Start

Plain JavaScript / TypeScript

import {
  normalizePalette,
  findNearestPaletteEntry,
  shouldSnapToPalette,
  applyPaletteColor,
  applyCustomHexColor,
} from 'cmyk-preview-toolkit';

// 1. Build a palette from your PDF template colors
const palette = normalizePalette([
  { source: { type: 'cmyk', c: 0.11, m: 0.24, y: 0.0, k: 0.13 } },
  { source: { type: 'rgb', r: 12, g: 34, b: 56 } },
]);

// 2. User picks a color via EyeDropper or color input
const element = { color: '#000000', colorSource: null };
const hexInput = '#1dc4e2';

// 3. Snap to nearest palette entry if close enough
const nearest = findNearestPaletteEntry(palette, hexInput);
const updated =
  nearest.entry && shouldSnapToPalette(nearest)
    ? applyPaletteColor(element, 'text', nearest.entry)
    : applyCustomHexColor(element, 'text', hexInput);

console.log(updated);
// => {
//   color: '#...',
//   colorSource: { type: 'cmyk', c: 0.11, m: 0.24, y: 0.0, k: 0.13 },
//   colorPreviewRgb: { r: ..., g: ..., b: ... }
// }

React

import { usePaletteColor } from 'cmyk-preview-toolkit/react';

const ColorPicker = ({ element, palette, onChange }) => {
  const { setHexColor } = usePaletteColor(element, {
    palette,
    onUpdate: onChange,
    options: { maxDistance: 80, dominanceRatio: 0.55 },
  });

  return (
    <input
      type="color"
      value={element.color ?? '#000000'}
      onChange={(e) => setHexColor('text', e.target.value)}
    />
  );
};

Color Conversion Utilities

import {
  deviceCmykToRgb,
  rgbToCmyk,
  hexToHsl,
  hslToHex,
  rgbToLab,
  deltaE76,
} from 'cmyk-preview-toolkit';

// CMYK → RGB
const [r, g, b] = deviceCmykToRgb(0.11, 0.24, 0.0, 0.13);

// RGB → CMYK
const { c, m, y, k } = rgbToCmyk(200, 100, 50);

// Hex → HSL → Hex
const hsl = hexToHsl('#ff6347');  // { h: 9, s: 100, l: 64 }
const hex = hslToHex(9, 100, 64); // '#ff6347'

// Perceptual color distance (CIE76)
const distance = deltaE76({ r: 255, g: 0, b: 0 }, { r: 250, g: 10, b: 5 });

API Reference

Color Transforms (cmyk-preview-toolkit)

| Export | Description | |---|---| | deviceCmykToRgb(c, m, y, k) | Convert CMYK (0..1) to sRGB bytes using the pdf.js polynomial | | rgbToCmyk(r, g, b) | Convert sRGB bytes to CMYK using GCR approximation | | normalizeHex(input) | Normalize any hex string to lowercase 6-digit #rrggbb | | hexToRgb(hex) | Parse hex to { r, g, b } | | rgbToHex(r, g, b) | Format RGB bytes as hex | | hexToHsl(hex) | Parse hex to { h, s, l } (h: 0..360, s/l: 0..100) | | hslToHex(h, s, l) | Format HSL values as hex | | toPreviewHex(color) | Get sRGB hex preview from a CMYKColor or RGBColor | | toPreviewRgb(color) | Get sRGB { r, g, b } preview from a CMYKColor or RGBColor | | clamp01(n) | Clamp to 0..1 | | clampByte(n) | Clamp to 0..255 and round |

Lab & Perceptual Distance

| Export | Description | |---|---| | rgbToLab(r, g, b) | Convert sRGB bytes to CIE Lab (D65 illuminant) | | deltaE76(a, b) | CIE76 Delta-E distance between two { r, g, b } colors |

Palette (cmyk-preview-toolkit/palette)

| Export | Description | |---|---| | buildPaletteEntry(source, extra?) | Build a PaletteEntry with computed preview fields | | normalizePalette(entries) | Fill missing preview fields for an array of partial entries | | findNearestPaletteEntry(palette, hex, options?) | Find the closest palette entry (supports RGB and Delta-E distance) | | shouldSnapToPalette(result, options?) | Decide if the nearest match is close enough to snap | | rgbDistanceSq(a, b) | Squared Euclidean distance between two RGB colors |

State (cmyk-preview-toolkit/state)

| Export | Description | |---|---| | applyPaletteColor(element, role, entry) | Set preview + source from a palette entry (immutable) | | applyCustomHexColor(element, role, hex) | Set preview from hex, clear source (immutable) |

React (cmyk-preview-toolkit/react)

| Export | Description | |---|---| | usePaletteColor(element, options) | Hook returning { setHexColor } with auto-snap logic |

Types

type CMYKColor = { type: 'cmyk'; c: number; m: number; y: number; k: number };
type RGBColor  = { type: 'rgb';  r: number; g: number; b: number };
type HSLColor  = { type: 'hsl';  h: number; s: number; l: number };
type ColorRole = 'text' | 'background' | 'border';

interface SnapOptions {
  maxDistance?: number;        // default: 75 (RGB) or ~10 (Delta-E)
  dominanceRatio?: number;    // default: 0.6
  distanceMetric?: 'rgb' | 'deltaE76';  // default: 'rgb'
}

Snap Configuration

| Option | Default | Description | |---|---|---| | maxDistance | 75 | Max distance for a direct snap | | dominanceRatio | 0.6 | Snap if bestDistance < secondBestDistance × ratio | | distanceMetric | 'rgb' | 'rgb' for Euclidean sRGB, 'deltaE76' for perceptual Lab |

Integrating with pdf-lib

When exporting with pdf-lib, use the colorSource field (not the preview hex):

import { PDFDocument, cmyk, rgb } from 'pdf-lib';

const source = element.colorSource;
if (source?.type === 'cmyk') {
  page.drawText('Hello', { color: cmyk(source.c, source.m, source.y, source.k) });
} else {
  const hex = element.color ?? '#000000';
  const { r, g, b } = hexToRgb(hex) ?? { r: 0, g: 0, b: 0 };
  page.drawText('Hello', { color: rgb(r / 255, g / 255, b / 255) });
}

Data Model Invariants

  • All hex values are lowercase 6-digit strings (#1dc4e2)
  • previewRgb always corresponds to previewHex
  • source is the authority for export — CMYK channels are 0..1 fractions, RGB channels are 0..255 bytes
  • Undo/redo and copy/paste must keep both preview and source in sync

Why Not Full ICC Profiles?

Full color-managed renderers (MuPDF, PDFium, Ghostscript) deliver pixel-perfect CMYK previews but require large WASM bundles, ICC profile management, and custom hit-testing. The dual representation approach is lightweight, browser-native, and good enough for most web-based PDF editors.

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

Quick version:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Commit your changes (git commit -m 'feat: add my feature')
  4. Push to the branch (git push origin feature/my-feature)
  5. Open a Pull Request

Please ensure all tests pass (npm test) and the type check succeeds (npm run typecheck) before submitting.

Community & Support

Author

Manojkumar Vishwakarma

Changelog

See CHANGELOG.md for version history.

License

MIT © 2026 Manojkumar Vishwakarma