From 8438ea2d8f9a11d712ec1af92569e6a031c64cb2 Mon Sep 17 00:00:00 2001 From: Seito Tanaka Date: Sun, 9 Jan 2022 14:59:23 +0900 Subject: [PATCH] Add `ambiguousIsNarrow` option (#34) Co-authored-by: Sindre Sorhus --- index.d.ts | 11 ++++++++++- index.js | 21 ++++++++++++++------- index.test-d.ts | 2 ++ package.json | 2 +- readme.md | 21 +++++++++++++++++++++ test.js | 2 ++ 6 files changed, 50 insertions(+), 9 deletions(-) diff --git a/index.d.ts b/index.d.ts index f595679..d8da3ce 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,3 +1,12 @@ +export interface Options { + /** + Count [ambiguous width characters](https://www.unicode.org/reports/tr11/#Ambiguous) as having narrow width (count of 1) instead of wide width (count of 2). + + @default false + */ + readonly ambiguousIsNarrow: boolean; +} + /** Get the visual width of a string - the number of columns required to display it. @@ -17,4 +26,4 @@ stringWidth('\u001B[1m古\u001B[22m'); //=> 2 ``` */ -export default function stringWidth(string: string): number; +export default function stringWidth(string: string, options?: Options): number; diff --git a/index.js b/index.js index 155645b..35882b0 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,8 @@ import stripAnsi from 'strip-ansi'; -import isFullwidthCodePoint from 'is-fullwidth-code-point'; +import eastAsianWidth from 'eastasianwidth'; import emojiRegex from 'emoji-regex'; -export default function stringWidth(string) { +export default function stringWidth(string, options = {}) { if (typeof string !== 'string' || string.length === 0) { return 0; } @@ -15,6 +15,7 @@ export default function stringWidth(string) { string = string.replace(emojiRegex(), ' '); + const ambiguousCharWidth = options.ambiguousIsNarrow ? 1 : 2; let width = 0; for (let index = 0; index < string.length; index++) { @@ -30,12 +31,18 @@ export default function stringWidth(string) { continue; } - // Surrogates - if (codePoint > 0xFFFF) { - index++; + const code = eastAsianWidth.eastAsianWidth(string.charAt(index)); + switch (code) { + case 'F': + case 'W': + width += 2; + break; + case 'A': + width += ambiguousCharWidth; + break; + default: + width += 1; } - - width += isFullwidthCodePoint(codePoint) ? 2 : 1; } return width; diff --git a/index.test-d.ts b/index.test-d.ts index 450ea7c..a713789 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -2,3 +2,5 @@ import {expectType} from 'tsd'; import stringWidth from './index.js'; expectType(stringWidth('古')); + +expectType(stringWidth('★', {ambiguousIsNarrow: true})); diff --git a/package.json b/package.json index 6d729c3..248b435 100644 --- a/package.json +++ b/package.json @@ -47,8 +47,8 @@ "fixed-width" ], "dependencies": { + "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", - "is-fullwidth-code-point": "^4.0.0", "strip-ansi": "^7.0.1" }, "devDependencies": { diff --git a/readme.md b/readme.md index 3a15864..52910df 100644 --- a/readme.md +++ b/readme.md @@ -27,6 +27,27 @@ stringWidth('\u001B[1m古\u001B[22m'); //=> 2 ``` +## API + +### stringWidth(string, options?) + +#### string + +Type: `string` + +The string to be counted. + +#### options + +Type: `object` + +##### ambiguousIsNarrow + +Type: `boolean`\ +Default: `false` + +Count [ambiguous width characters](https://www.unicode.org/reports/tr11/#Ambiguous) as having narrow width (count of 1) instead of wide width (count of 2). + ## Related - [string-width-cli](https://github.com/sindresorhus/string-width-cli) - CLI for this module diff --git a/test.js b/test.js index cc1f570..c12fa8e 100644 --- a/test.js +++ b/test.js @@ -5,6 +5,8 @@ test('main', t => { t.is(stringWidth('abcde'), 5); t.is(stringWidth('古池や'), 6); t.is(stringWidth('あいうabc'), 9); + t.is(stringWidth('あいう★'), 8); + t.is(stringWidth('あいう★', {ambiguousIsNarrow: true}), 7); t.is(stringWidth('ノード.js'), 9); t.is(stringWidth('你好'), 4); t.is(stringWidth('안녕하세요'), 10);