Skip to content

noorjsdivs/string-width-ts

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

📏 string-width-ts

npm version TypeScript License: MIT Node.js Version

YouTube Buy Me a Coffee

A modern, TypeScript-first string width calculator with enhanced Unicode, emoji, and ANSI support


🎯 What is this?

Ever wondered why your terminal output looks misaligned when using emojis, CJK characters, or ANSI colors? string-width-ts solves this by accurately calculating the visual width of strings - how many columns they actually occupy on screen.

// ❌ This looks wrong:
console.log("Name".padEnd(10) + "Age");
console.log("古川".padEnd(10) + "25"); // Misaligned!

// ✅ This looks perfect:
import { padString } from "string-width-ts";
console.log(padString("Name", 10) + "Age");
console.log(padString("古川", 10) + "25"); // Perfectly aligned!

🚀 Quick Start

Installation

npm install string-width-ts

Basic Usage

import { stringWidth } from "string-width-ts";

// Regular characters
stringWidth("Hello"); // → 5

// Wide characters (CJK)
stringWidth("こんにちは"); // → 10 (each character = 2 columns)

// Emojis
stringWidth("Hello 👋"); // → 7 (emoji = 2 columns)

// ANSI codes (ignored by default)
stringWidth("\x1b[31mRed text\x1b[0m"); // → 8 (ANSI codes don't affect width)

✨ Why Choose string-width-ts?

🆚 Comparison with Alternatives

Feature string-width wcwidth string-width-ts
TypeScript Native ⚠️ Types only Built with TS
Zero Dependencies 100% self-contained
Modern ES Features ES2023 & latest TS
Enhanced Emoji Support ⚠️ Basic Complex emoji sequences
Multi-line Support Built-in utilities
Detailed Analysis Comprehensive info
String Manipulation Truncate, pad, align
Custom Width Rules Configurable mapping

🎯 Key Features

  • 🎯 Pixel-Perfect Accuracy: Handles fullwidth, emojis, combining characters, and ANSI codes
  • 🌍 Complete Unicode Support: East Asian characters, complex scripts, modern emojis
  • 🎨 ANSI-Aware: Properly handles terminal colors and formatting
  • 📊 Detailed Analytics: Get comprehensive string composition analysis
  • 🔧 Highly Configurable: Custom width rules, normalization, edge case handling
  • ⚡ Zero Dependencies: No external packages required
  • 🛡️ Type Safe: Full TypeScript support with strict typing
  • 🧪 Battle Tested: Comprehensive test suite with 28+ test cases

📖 Complete API Reference

Core Functions

stringWidth(string, options?)

Calculate the visual width of a string.

import { stringWidth } from "string-width-ts";

// Examples
stringWidth("a"); // → 1
stringWidth("古"); // → 2
stringWidth("👨‍👩‍👧‍👦"); // → 2 (family emoji)
stringWidth("\u001B[1m古\u001B[22m"); // → 2 (ignores ANSI)

// With options
stringWidth("🌈", { emojiAsNarrow: true }); // → 1 instead of 2
stringWidth("古", { ambiguousIsNarrow: false }); // → 2 (wide mode)

getStringWidthInfo(string, options?)

Get detailed analysis of string composition.

import { getStringWidthInfo } from "string-width-ts";

const info = getStringWidthInfo("Hello 👋 世界");
console.log(info);
// {
//   width: 11,           // Total visual width
//   characters: 9,       // Character count
//   graphemes: 9,        // Grapheme clusters
//   ansiSequences: 0,    // ANSI escape codes
//   emojis: 1,          // Emoji count
//   zeroWidthChars: 0,   // Zero-width characters
//   combiningChars: 0,   // Combining characters
//   widthBreakdown: {
//     width0: 0,         // Zero-width chars
//     width1: 7,         // Narrow chars
//     width2: 2          // Wide chars
//   }
// }

Multi-line Support

getWidestLineWidth(string, options?)

Find the width of the widest line in multi-line text.

import { getWidestLineWidth } from "string-width-ts";

const text = `
Hello World
こんにちは 👋
Short
`;

getWidestLineWidth(text); // → 12 (from "こんにちは 👋")

getMultiLineWidthInfo(string, options?)

Comprehensive analysis for multi-line strings.

import { getMultiLineWidthInfo } from "string-width-ts";

const info = getMultiLineWidthInfo("Hello\n世界\nTest 👋");
console.log(info);
// {
//   lines: [
//     { content: "Hello", width: 5, /* ... */ },
//     { content: "世界", width: 4, /* ... */ },
//     { content: "Test 👋", width: 7, /* ... */ }
//   ],
//   maxWidth: 7,         // Widest line
//   lineCount: 3,        // Number of lines
//   averageWidth: 5.33   // Average width
// }

String Manipulation

truncateString(string, targetWidth, options?, suffix?)

Intelligently truncate strings to fit specific widths.

import { truncateString } from "string-width-ts";

// Basic truncation
truncateString("Hello World", 8); // → "Hello..."

// With wide characters
truncateString("Hello 世界", 6); // → "Hel..."

// Custom suffix
truncateString("Hello World", 8, {}, "…"); // → "Hello W…"

// Preserve words
truncateString("Hello beautiful world", 10, { preserveWords: true }); // → "Hello..."

padString(string, targetWidth, options?, padString?, position?)

Pad strings to specific visual widths.

import { padString } from "string-width-ts";

// Right padding (default)
padString("世界", 6); // → "世界  "

// Left padding
padString("世界", 6, {}, " ", "start"); // → "  世界"

// Center padding
padString("世界", 8, {}, " ", "both"); // → "  世界  "

// Custom padding character
padString("Hello", 10, {}, "-"); // → "Hello-----"

⚙️ Configuration Options

interface StringWidthOptions {
  // Treat ambiguous characters as narrow (1) instead of wide (2)
  ambiguousIsNarrow?: boolean; // default: true

  // Include ANSI escape codes in width calculation
  countAnsiEscapeCodes?: boolean; // default: false

  // Include zero-width characters in calculation
  includeZeroWidth?: boolean; // default: false

  // Treat emojis as narrow (width 1) instead of wide (width 2)
  emojiAsNarrow?: boolean; // default: false

  // Custom width rules for specific Unicode code points
  customWidthMap?: Map<number, number>;

  // Unicode normalization before calculation
  normalize?: boolean | "NFC" | "NFD" | "NFKC" | "NFKD"; // default: false
}

🎯 Real-World Examples

1. Terminal Tables with Perfect Alignment

import { stringWidth, padString } from "string-width-ts";

const data = [
  { name: "John", city: "New York", emoji: "🇺🇸" },
  { name: "田中", city: "東京", emoji: "🇯🇵" },
  { name: "José", city: "Madrid", emoji: "🇪🇸" },
];

// Calculate column widths
const nameWidth = Math.max(...data.map((row) => stringWidth(row.name))) + 2;
const cityWidth = Math.max(...data.map((row) => stringWidth(row.city))) + 2;

// Print perfectly aligned table
data.forEach((row) => {
  const line = [
    padString(row.name, nameWidth),
    padString(row.city, cityWidth),
    row.emoji,
  ].join(" | ");
  console.log(line);
});

2. CLI Progress Bars with Unicode

import { stringWidth, truncateString } from "string-width-ts";

function createProgressBar(
  label: string,
  progress: number,
  totalWidth: number
) {
  const progressChars = Math.floor((progress / 100) * 20);
  const bar = "█".repeat(progressChars) + "░".repeat(20 - progressChars);

  const availableWidth = totalWidth - 30; // Reserve space for bar and percentage
  const truncatedLabel = truncateString(label, availableWidth);

  return `${truncatedLabel} [${bar}] ${progress.toFixed(1)}%`;
}

console.log(createProgressBar("Processing 大きなファイル.txt", 75.5, 60));
// → "Processing 大きなファイ... [███████████████░░░░░] 75.5%"

3. Smart Text Wrapping

import { stringWidth, getStringWidthInfo } from "string-width-ts";

function wrapText(text: string, maxWidth: number): string[] {
  const words = text.split(" ");
  const lines: string[] = [];
  let currentLine = "";

  for (const word of words) {
    const testLine = currentLine ? `${currentLine} ${word}` : word;

    if (stringWidth(testLine) <= maxWidth) {
      currentLine = testLine;
    } else {
      if (currentLine) lines.push(currentLine);
      currentLine = word;
    }
  }

  if (currentLine) lines.push(currentLine);
  return lines;
}

const text = "Hello 世界 this is a long text with 絵文字 👋 and symbols";
const wrapped = wrapText(text, 20);
console.log(wrapped);
// Each line fits within 20 visual columns

4. Advanced Emoji Handling

import { getStringWidthInfo } from "string-width-ts";

// Complex emoji sequences
const family = "👨‍👩‍👧‍👦"; // Family emoji with ZWJ sequences
const flag = "🇺🇸"; // Flag emoji (regional indicators)
const skinTone = "👋🏽"; // Emoji with skin tone modifier

const familyInfo = getStringWidthInfo(family);
console.log(
  `Family emoji: width=${familyInfo.width}, graphemes=${familyInfo.graphemes}`
);
// → Family emoji: width=2, graphemes=1

const flagInfo = getStringWidthInfo(flag);
console.log(
  `Flag emoji: width=${flagInfo.width}, characters=${flagInfo.characters}`
);
// → Flag emoji: width=2, characters=2

🔧 Advanced Configuration

Custom Width Mapping

import { stringWidth } from "string-width-ts";

// Define custom width rules
const customMap = new Map([
  [65, 3], // 'A' takes 3 columns
  [0x1f600, 1], // 😀 takes 1 column instead of 2
  [0x4e00, 3], // 一 (CJK) takes 3 columns
]);

const result = stringWidth("A😀一B", { customWidthMap: customMap });
console.log(result); // → 8 (3+1+3+1)

Unicode Normalization

import { stringWidth } from "string-width-ts";

// These look the same but are different Unicode sequences
const precomposed = "é"; // Single character U+00E9
const decomposed = "e\u0301"; // e + combining acute accent

console.log(stringWidth(precomposed, { normalize: "NFD" })); // Normalize to decomposed
console.log(stringWidth(decomposed, { normalize: "NFC" })); // Normalize to composed

// Useful for consistent width calculation across different input sources

🧪 Testing & Development

# Run all tests
npm test

# Run tests in watch mode
npm run test:watch

# Build the project
npm run build

# Development mode (watch for changes)
npm run dev

# Type checking only
npm run lint

📋 Requirements

  • Node.js: 18.0.0 or higher
  • TypeScript: 5.9.2 (latest) for development
  • Zero runtime dependencies 🎉

🤝 Contributing

We welcome contributions! Please see our contributing guidelines for details.

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

📚 Learn More

📺 Video Tutorials

Check out our YouTube channel for in-depth tutorials and examples!

☕ Support the Project

If this project helps you, consider buying me a coffee!

🔗 Related Projects

📜 License

MIT © ReactJS BD


Made with ❤️ using TypeScript 5.9.2

GitHub stars GitHub forks

Returns

  • number: The visual width of the string

Options

interface StringWidthOptions {
  // Count ambiguous characters as narrow (1) instead of wide (2)
  ambiguousIsNarrow?: boolean; // default: true

  // Whether to count ANSI escape codes in width calculation
  countAnsiEscapeCodes?: boolean; // default: false

  // Whether to include zero-width characters
  includeZeroWidth?: boolean; // default: false

  // Treat emojis as narrow (width 1) instead of wide (width 2)
  emojiAsNarrow?: boolean; // default: false

  // Custom width mapping for specific characters
  customWidthMap?: Map<number, number>;

  // Normalize Unicode before calculation
  normalize?: boolean | "NFC" | "NFD" | "NFKC" | "NFKD"; // default: false
}

getStringWidthInfo(string, options?)

Get detailed information about string width calculation.

import { getStringWidthInfo } from "string-width-ts";

const info = getStringWidthInfo("Hello 👋 世界");
console.log(info);
// {
//   width: 11,
//   characters: 9,
//   graphemes: 9,
//   ansiSequences: 0,
//   emojis: 1,
//   zeroWidthChars: 0,
//   combiningChars: 0,
//   widthBreakdown: {
//     width0: 0,  // zero-width characters
//     width1: 7,  // narrow characters
//     width2: 2   // wide characters
//   }
// }

getWidestLineWidth(string, options?)

Get the width of the widest line in a multi-line string.

import { getWidestLineWidth } from "string-width-ts";

const width = getWidestLineWidth("Hello\n世界\nTest");
console.log(width); // 5

getMultiLineWidthInfo(string, options?)

Get comprehensive width analysis for multi-line strings.

import { getMultiLineWidthInfo } from "string-width-ts";

const info = getMultiLineWidthInfo("Hello\n世界\nTest 👋");
console.log(info);
// {
//   lines: [...],      // detailed info for each line
//   maxWidth: 7,       // width of widest line
//   lineCount: 3,      // number of lines
//   averageWidth: 5.33 // average width across lines
// }

truncateString(string, targetWidth, options?, suffix?)

Truncate a string to a specific visual width.

import { truncateString } from "string-width-ts";

truncateString("Hello World", 8); // 'Hello...'
truncateString("Hello 世界", 6); // 'Hel...'
truncateString("Hello World", 8, {}, "…"); // 'Hello W…'

padString(string, targetWidth, options?, padString?, position?)

Pad a string to a specific visual width.

import { padString } from "string-width-ts";

padString("世界", 6); // '世界  '
padString("世界", 6, {}, " ", "start"); // '  世界'
padString("世界", 6, {}, " ", "both"); // ' 世界 '

🎯 Use Cases

Terminal Applications

import { stringWidth, padString } from "string-width-ts";

// Create aligned columns
const items = ["Item", "项目", "アイテム", "Element"];
const maxWidth = Math.max(...items.map((item) => stringWidth(item)));

items.forEach((item) => {
  console.log(padString(item, maxWidth + 2) + "| Description");
});

CLI Progress Bars

import { stringWidth, truncateString } from "string-width-ts";

function createProgressBar(label: string, progress: number, width: number) {
  const availableWidth = width - 10; // Reserve space for percentage
  const truncatedLabel = truncateString(label, availableWidth);
  const padding = " ".repeat(availableWidth - stringWidth(truncatedLabel));

  return `${truncatedLabel}${padding} ${progress.toFixed(1)}%`;
}

console.log(createProgressBar("Processing 文件.txt", 75.5, 50));

Text Layout

import { getMultiLineWidthInfo, padString } from "string-width-ts";

function centerText(text: string, containerWidth: number): string {
  const info = getMultiLineWidthInfo(text);

  return info.lines
    .map((line) => padString("", containerWidth, {}, " ", "both"))
    .join("\n");
}

🆚 Comparison with string-width

Feature string-width string-width-ts
TypeScript Support ✅ (types included) ✅ (built with TS)
Basic Width Calculation
ANSI Escape Handling
Emoji Support ✅ Enhanced
Detailed Analysis
Multi-line Utilities
String Manipulation
Custom Width Mapping
Unicode Normalization
Zero Dependencies
Modern ES Features

🔧 Advanced Usage

Custom Width Mapping

import { stringWidth } from "string-width-ts";

// Define custom widths for specific characters
const customMap = new Map([
  [65, 3], // 'A' has width 3
  [0x1f600, 1], // 😀 has width 1 instead of 2
]);

stringWidth("A😀B", { customWidthMap: customMap }); // 5 (3+1+1)

Unicode Normalization

import { stringWidth } from "string-width-ts";

const str1 = "é"; // Precomposed
const str2 = "e\u0301"; // Decomposed (e + combining acute)

stringWidth(str1, { normalize: "NFD" }); // Normalize to decomposed
stringWidth(str2, { normalize: "NFC" }); // Normalize to composed

Working with Complex Emojis

import { getStringWidthInfo } from "string-width-ts";

// Family emoji with zero-width joiners
const family = "👨‍👩‍👧‍👦";
const info = getStringWidthInfo(family);

console.log(`Width: ${info.width}`); // 2
console.log(`Graphemes: ${info.graphemes}`); // 1
console.log(`Characters: ${info.characters}`); // 11

🧪 Testing

npm test        # Run all tests
npm run test:watch  # Run tests in watch mode

🏗 Building

npm run build   # Build TypeScript to JavaScript
npm run dev     # Build in watch mode

📋 Requirements

  • Node.js 16.0.0 or higher
  • TypeScript 5.0.0 or higher (for development)

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

📜 License

MIT © Your Name

🙏 Acknowledgments

  • Inspired by sindresorhus/string-width
  • Built with modern TypeScript and enhanced features
  • Thanks to the Unicode Consortium for character width specifications

📚 Related

About

The definitive Visual Width calculator. Perfect alignment for Emojis, CJK, and ANSI codes.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published