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

@redwilly/fontscanner

v1.0.1

Published

Cross-platform Node.js library for discovering and searching system fonts

Readme

Font Scanner

Cross-platform Node.js library for discovering and searching system fonts

TypeScript Node.js License: MIT

Features

  • 🌍 Cross-platform: Works on Windows, macOS, and Linux
  • Fast: Built-in caching for improved performance with async support
  • 🔍 Smart search: Find fonts by name with exact and partial matching
  • 📁 Flexible: Support for system fonts, user fonts, and custom directories
  • 🎯 Type-safe: Full TypeScript support with comprehensive type definitions
  • 📊 Format support: TTF, OTF, WOFF, WOFF2, EOT, TTC, PostScript, Bitmap fonts
  • 🛠️ Configurable: Extensive options for customizing font discovery
  • 🔄 Async/Sync: Both synchronous and asynchronous APIs available
  • 📈 Rich metadata: Extract font family, weight, style, version, and more
  • 🚨 Error reporting: Comprehensive error tracking and statistics
  • 👨‍💻 Developer-friendly: Detailed scan results and performance metrics

Installation

# Using bun (recommended)
bun add @redwilly/fontscanner

# Using npm
npm install @redwilly/fontscanner

# Using yarn
yarn add @redwilly/fontscanner

Quick Start

import FontScanner from '@redwilly/fontscanner';

// Simple usage - get all fonts
const fonts = FontScanner.scan().getFonts();
console.log(`Found ${fonts.length} fonts`);

// Chainable API - filter and search
const boldTtfFonts = FontScanner
  .scan({ useCache: false })
  .filterByFormat('ttf')
  .filterByWeight(700)
  .getFonts();

// Search for specific fonts
const arialFonts = FontScanner
  .scan()
  .searchByName('Arial')
  .getFonts();

// Get only monospace fonts
const monoFonts = FontScanner
  .scan()
  .onlyMonospace()
  .getFonts();

// Async usage (same API)
const asyncFonts = await FontScanner
  .scan()
  .filterByFormat('otf')
  .getFontsAsync();

API Reference

Types

FontEntry

Represents a font with its metadata.

interface FontEntry {
  /** The display name of the font family */
  name: string;
  /** The full file system path to the font file */
  path: string;
  /** The font file format (ttf, otf, etc.) */
  format?: string;
  /** Font family name (e.g., 'Arial', 'Times New Roman') */
  family?: string;
  /** Font weight (100-900, where 400 is normal, 700 is bold) */
  weight?: number;
  /** Font style */
  style?: 'normal' | 'italic' | 'oblique';
  /** Font version string */
  version?: string;
  /** Copyright information */
  copyright?: string;
  /** Whether the font is monospaced */
  isMonospace?: boolean;
  /** Font file size in bytes */
  fileSize?: number;
  /** Last modified timestamp */
  lastModified?: Date;
}

FontScanOptions

Options for customizing font scanning behavior.

interface FontScanOptions {
  /** Whether to use cached results (default: true) */
  useCache?: boolean;
  /** Whether to include system fonts (default: true) */
  includeSystemFonts?: boolean;
  /** Whether to include user fonts (default: true) */
  includeUserFonts?: boolean;
  /** Custom directories to scan */
  customDirs?: string[];
}

interface FontError {
  /** Path to the font file that caused the error */
  path: string;
  /** Error message */
  message: string;
  /** Error code for programmatic handling */
  code: 'FILE_NOT_FOUND' | 'PERMISSION_DENIED' | 'INVALID_FONT' | 'PARSE_ERROR' | 'UNKNOWN';
}

interface FontScanResult {
  /** Successfully parsed fonts */
  fonts: FontEntry[];
  /** Errors encountered during scanning */
  errors: FontError[];
  /** Scan statistics */
  stats: {
    /** Total directories scanned */
    directoriesScanned: number;
    /** Total files processed */
    filesProcessed: number;
    /** Successfully parsed fonts */
    fontsFound: number;
    /** Files that failed to parse */
    filesFailed: number;
    /** Total scan time in milliseconds */
    scanTimeMs: number;
  };
}

FontScanner Class

The main FontScanner class provides a fluent, chainable API for font discovery and filtering.

FontScanner.scan(options?: FontScanOptions): FontScanner

Static method to create a new FontScanner instance.

import FontScanner from '@redwilly/fontscanner';

// Basic usage
const scanner = FontScanner.scan();

// With options
const scanner = FontScanner.scan({
  useCache: false,
  includeUserFonts: true,
  customDirs: ['/path/to/fonts']
});

Chainable Filter Methods

All filter methods return the FontScanner instance for chaining:

// Filter by font format
scanner.filterByFormat('ttf')

// Filter by font weight (100-900)
scanner.filterByWeight(700)

// Filter to only monospace fonts
scanner.onlyMonospace()

// Search by name (partial match)
scanner.searchByName('Arial')

// Match font name exactly
scanner.matchNameExactly('Courier New')

Result Methods

Get the final results after applying filters:

// Get fonts synchronously
const fonts: FontEntry[] = scanner.getFonts();

// Get fonts asynchronously
const fonts: FontEntry[] = await scanner.getFontsAsync();

// Get detailed results with error reporting (sync)
const result: FontScanResult = scanner.getResult();

// Get detailed results with error reporting (async)
const result: FontScanResult = await scanner.getResultAsync();

Complete Examples

import FontScanner from '@redwilly/fontscanner';

// Example 1: Find all bold TTF fonts
const boldTtfFonts = FontScanner
  .scan({ useCache: false })
  .filterByFormat('ttf')
  .filterByWeight(700)
  .getFonts();

// Example 2: Search for Arial variants
const arialFonts = FontScanner
  .scan()
  .searchByName('Arial')
  .getFonts();

// Example 3: Get monospace fonts with error details
const result = FontScanner
  .scan()
  .onlyMonospace()
  .getResult();

console.log(`Found ${result.fonts.length} monospace fonts`);
console.log(`${result.errors.length} errors occurred`);

// Example 4: Async usage with multiple filters
const specificFonts = await FontScanner
  .scan({ includeUserFonts: false })
  .filterByFormat('otf')
  .filterByWeight(400)
  .searchByName('Times')
  .getFontsAsync();

clearFontCache(): void

import { clearFontCache } from '@redwilly/fontscanner';

// Clear cache
clearFontCache();

getCachedFontCount(): number

Get the number of fonts currently in cache.

import { getCachedFontCount } from '@redwilly/fontscanner';

const count = getCachedFontCount();
console.log(`${count} fonts in cache`);

isCacheValid(): boolean

Check if the current cache is valid (exists and not expired).

import { isCacheValid } from '@redwilly/fontscanner';

if (isCacheValid()) {
  console.log('Cache is valid');
} else {
  console.log('Cache is invalid or expired');
}

Platform Support

Windows

  • System fonts: C:\Windows\Fonts
  • User fonts: %LOCALAPPDATA%\Microsoft\Windows\Fonts
  • Registry-based font discovery for additional locations

macOS

  • System fonts: /System/Library/Fonts, /Library/Fonts
  • User fonts: ~/Library/Fonts
  • Asset catalog fonts: /System/Library/Assets/com_apple_MobileAsset_Font6

Linux

  • System fonts: /usr/share/fonts, /usr/local/share/fonts
  • User fonts: ~/.fonts, ~/.local/share/fonts
  • X11 fonts: /usr/X11R6/lib/X11/fonts

Examples

Basic Usage

import FontScanner from '@redwilly/fontscanner';

// List all fonts
const fonts = FontScanner.scan().getFonts();
fonts.forEach(font => {
  console.log(`${font.name} (${font.format}) - ${font.path}`);
});

// Find specific fonts
const commonFonts = ['Arial', 'Times New Roman', 'Helvetica', 'Calibri'];
for (const fontName of commonFonts) {
  const matchingFonts = FontScanner
    .scan()
    .matchNameExactly(fontName)
    .getFonts();
  
  if (matchingFonts.length > 0 && matchingFonts[0]) {
    console.log(`✓ ${fontName}: ${matchingFonts[0].path}`);
  } else {
    console.log(`✗ ${fontName}: Not found`);
  }
}

Performance Optimization

import FontScanner, { isCacheValid, clearFontCache } from '@redwilly/fontscanner';

// Check cache before scanning
if (!isCacheValid()) {
  console.log('Cache expired, scanning fonts...');
}

const startTime = Date.now();
const fonts = FontScanner.scan().getFonts();
const scanTime = Date.now() - startTime;

console.log(`Found ${fonts.length} fonts in ${scanTime}ms`);

// Example: Force refresh when needed
const needsFreshData = fonts.length === 0; // Example condition
if (needsFreshData) {
  console.log('Refreshing font cache...');
  clearFontCache();
  const freshFonts = FontScanner.scan({ useCache: false }).getFonts();
  console.log(`Found ${freshFonts.length} fonts after refresh`);
}

Custom Font Directories

import FontScanner from '@redwilly/fontscanner';

// Scan additional directories
const fonts = FontScanner.scan({
  customDirs: [
    '/opt/fonts',
    '/home/user/custom-fonts',
    'C:\\MyFonts'
  ],
  includeSystemFonts: true,
  includeUserFonts: true
}).getFonts();

Filter by Format

import FontScanner from '@redwilly/fontscanner';

// Get and display fonts by specific formats
const ttfFonts = FontScanner.scan().filterByFormat('ttf').getFonts();
const otfFonts = FontScanner.scan().filterByFormat('otf').getFonts();
const webFonts = FontScanner.scan().filterByFormat('woff').getFonts();

console.log(`TTF fonts: ${ttfFonts.length}`);
console.log(`OTF fonts: ${otfFonts.length}`);
console.log(`WOFF fonts: ${webFonts.length}`);

// Count all fonts by format
const allFonts = FontScanner.scan().getFonts();
const formatCounts = allFonts.reduce((acc, font) => {
  const format = font.format || 'unknown';
  acc[format] = (acc[format] || 0) + 1;
  return acc;
}, {} as Record<string, number>);

console.log('All fonts by format:', formatCounts);

Performance Notes

  • Caching: Results are cached for 5 minutes by default to improve performance
  • Lazy Loading: Fonts are only parsed when accessed
  • Memory Efficient: Uses streaming for large font directories
  • Error Handling: Gracefully handles corrupted or inaccessible font files

Requirements

  • Node.js 16.0 or higher
  • TypeScript 5.0 or higher (for development)
  • Bun runtime (recommended) or Node.js

Development

# Clone the repository
git clone <repository-url>
cd fontscanner

# Install dependencies
bun install

# Run development version
bun run dev

# Build the library
bun run build

# Run tests
bun run test

License

MIT License - see the LICENSE file for details.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Changelog

1.0.0

  • Initial release
  • Cross-platform font discovery
  • Caching support
  • TypeScript definitions
  • Comprehensive API