diff --git a/src/EscapeSequenceParser.ts b/src/EscapeSequenceParser.ts index ec7a9da7..7b65624d 100644 --- a/src/EscapeSequenceParser.ts +++ b/src/EscapeSequenceParser.ts @@ -6,7 +6,7 @@ import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types'; import { IDisposable } from 'xterm'; import { Disposable } from './common/Lifecycle'; -import { utf32ToString } from './common/TypedArrayUtils'; +import { utf32ToString } from './core/input/TextDecoder'; interface IHandlerCollection { [key: string]: T[]; diff --git a/src/InputHandler.ts b/src/InputHandler.ts index a702e2a6..2f53cfcb 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -14,8 +14,8 @@ import { EscapeSequenceParser } from './EscapeSequenceParser'; import { ICharset } from './core/Types'; import { IDisposable } from 'xterm'; import { Disposable } from './common/Lifecycle'; -import { concat, utf32ToString } from './common/TypedArrayUtils'; -import { StringToUtf32, stringFromCodePoint } from './core/input/TextDecoder'; +import { concat } from './common/TypedArrayUtils'; +import { StringToUtf32, stringFromCodePoint, utf32ToString } from './core/input/TextDecoder'; /** * Map collect to glevel. Used in `selectCharset`. diff --git a/src/common/TypedArrayUtils.test.ts b/src/common/TypedArrayUtils.test.ts index 79546ca9..99b0fd82 100644 --- a/src/common/TypedArrayUtils.test.ts +++ b/src/common/TypedArrayUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ import { assert } from 'chai'; -import { fillFallback, concat, utf32ToString } from './TypedArrayUtils'; +import { fillFallback, concat } from './TypedArrayUtils'; type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array @@ -94,12 +94,4 @@ describe('typed array convenience functions', () => { const merged = concat(a, b); deepEquals(merged, new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])); }); - it('utf16ToString', () => { - const s = 'abcdefg'; - const data = new Uint16Array(s.length); - for (let i = 0; i < s.length; ++i) { - data[i] = s.charCodeAt(i); - } - assert.equal(utf32ToString(data), s); - }); }); diff --git a/src/common/TypedArrayUtils.ts b/src/common/TypedArrayUtils.ts index 5f5be782..380dff35 100644 --- a/src/common/TypedArrayUtils.ts +++ b/src/common/TypedArrayUtils.ts @@ -2,10 +2,7 @@ * Copyright (c) 2018 The xterm.js authors. All rights reserved. * @license MIT */ - -type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray - | Int8Array | Int16Array | Int32Array - | Float32Array | Float64Array; +import { TypedArray } from './Types'; /** * polyfill for TypedArray.fill @@ -49,20 +46,3 @@ export function concat(a: T, b: T): T { result.set(b, a.length); return result; } - -/** - * Convert UTF32 char codes into JS string. - */ -export function utf32ToString(data: T, start: number = 0, end: number = data.length): string { - let result = ''; - let cp; - for (let i = start; i < end; ++i) { - if ((cp = data[i]) > 0xFFFF) { - cp -= 0x10000; - result += String.fromCharCode((cp >> 10) + 0xD800) + String.fromCharCode((cp % 0x400) + 0xDC00); - } else { - result += String.fromCharCode(cp); - } - } - return result; -} diff --git a/src/common/Types.ts b/src/common/Types.ts index 8a416bf1..29b5febb 100644 --- a/src/common/Types.ts +++ b/src/common/Types.ts @@ -35,3 +35,7 @@ export interface ICircularList extends IEventEmitter { trimStart(count: number): void; shiftElements(start: number, count: number, offset: number): void; } + +export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray + | Int8Array | Int16Array | Int32Array + | Float32Array | Float64Array; diff --git a/src/core/input/TextDecoder.test.ts b/src/core/input/TextDecoder.test.ts index f69fbded..12f3099a 100644 --- a/src/core/input/TextDecoder.test.ts +++ b/src/core/input/TextDecoder.test.ts @@ -4,64 +4,64 @@ */ import { assert } from 'chai'; -import { StringToUtf32, stringFromCodePoint } from './TextDecoder'; +import { StringToUtf32, stringFromCodePoint, utf32ToString } from './TextDecoder'; - -// convert UTF32 codepoints to string -function toString(data: Uint32Array, length: number): string { - if ((String as any).fromCodePoint) { - return (String as any).fromCodePoint.apply(null, data.subarray(0, length)); - } - let result = ''; - for (let i = 0; i < length; ++i) { - result += stringFromCodePoint(data[i]); - } - return result; -} - -describe('StringToUtf32 Decoder', () => { - describe('full codepoint test', () => { - it('0..65535', () => { - const decoder = new StringToUtf32(); - const target = new Uint32Array(5); - for (let i = 0; i < 65536; ++i) { - // skip surrogate pairs - if (i >= 0xD800 && i <= 0xDFFF) { - continue; - } - const length = decoder.decode(String.fromCharCode(i), target); - assert.equal(length, 1); - assert.equal(target[0], i); - assert.equal(toString(target, length), String.fromCharCode(i)); - decoder.clear(); - } - }); - it('65536..0x10FFFF (surrogates)', function(): void { - this.timeout(20000); - const decoder = new StringToUtf32(); - const target = new Uint32Array(5); - for (let i = 65536; i < 0x10FFFF; ++i) { - const codePoint = i - 0x10000; - const s = String.fromCharCode((codePoint >> 10) + 0xD800) + String.fromCharCode((codePoint % 0x400) + 0xDC00); - const length = decoder.decode(s, target); - assert.equal(length, 1); - assert.equal(target[0], i); - assert.equal(toString(target, length), s); - decoder.clear(); - } - }); +describe('text encodings', () => { + it('stringFromCodePoint/utf32ToString', () => { + const s = 'abcdefg'; + const data = new Uint32Array(s.length); + for (let i = 0; i < s.length; ++i) { + data[i] = s.charCodeAt(i); + assert.equal(stringFromCodePoint(data[i]), s[i]); + } + assert.equal(utf32ToString(data), s); }); - describe('stream handling', () => { - it('surrogates mixed advance by 1', () => { - const decoder = new StringToUtf32(); - const target = new Uint32Array(5); - const input = 'Ä€𝄞Ö𝄞€Ü𝄞€'; - let decoded = ''; - for (let i = 0; i < input.length; ++i) { - const written = decoder.decode(input[i], target); - decoded += toString(target, written); - } - assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€'); + + describe('StringToUtf32 Decoder', () => { + describe('full codepoint test', () => { + it('0..65535', () => { + const decoder = new StringToUtf32(); + const target = new Uint32Array(5); + for (let i = 0; i < 65536; ++i) { + // skip surrogate pairs + if (i >= 0xD800 && i <= 0xDFFF) { + continue; + } + const length = decoder.decode(String.fromCharCode(i), target); + assert.equal(length, 1); + assert.equal(target[0], i); + assert.equal(utf32ToString(target, 0, length), String.fromCharCode(i)); + decoder.clear(); + } + }); + it('65536..0x10FFFF (surrogates)', function(): void { + this.timeout(20000); + const decoder = new StringToUtf32(); + const target = new Uint32Array(5); + for (let i = 65536; i < 0x10FFFF; ++i) { + const codePoint = i - 0x10000; + const s = String.fromCharCode((codePoint >> 10) + 0xD800) + String.fromCharCode((codePoint % 0x400) + 0xDC00); + const length = decoder.decode(s, target); + assert.equal(length, 1); + assert.equal(target[0], i); + assert.equal(utf32ToString(target, 0, length), s); + decoder.clear(); + } + }); + }); + + describe('stream handling', () => { + it('surrogates mixed advance by 1', () => { + const decoder = new StringToUtf32(); + const target = new Uint32Array(5); + const input = 'Ä€𝄞Ö𝄞€Ü𝄞€'; + let decoded = ''; + for (let i = 0; i < input.length; ++i) { + const written = decoder.decode(input[i], target); + decoded += utf32ToString(target, written); + } + assert(decoded, 'Ä€𝄞Ö𝄞€Ü𝄞€'); + }); }); }); }); diff --git a/src/core/input/TextDecoder.ts b/src/core/input/TextDecoder.ts index 77e6971c..f83959e6 100644 --- a/src/core/input/TextDecoder.ts +++ b/src/core/input/TextDecoder.ts @@ -2,6 +2,7 @@ * Copyright (c) 2019 The xterm.js authors. All rights reserved. * @license MIT */ +import { TypedArray } from '../../common/Types'; /** * StringToUtf32 - decodes UTF16 sequences into UTF32 codepoints. @@ -73,15 +74,31 @@ export class StringToUtf32 { } /** - * Polyfill - Convert UTF32 codepoint into JS string. + * Convert UTF32 codepoint into JS string. */ export function stringFromCodePoint(codePoint: number): string { - if ((String as any).fromCodePoint) { - return (String as any).fromCodePoint(codePoint); - } if (codePoint > 0xFFFF) { codePoint -= 0x10000; return String.fromCharCode((codePoint >> 10) + 0xD800) + String.fromCharCode((codePoint % 0x400) + 0xDC00); } return String.fromCharCode(codePoint); } + +/** + * Convert UTF32 char codes into JS string. + * Basically the same as `stringFromCodePoint` but for multiple codepoints + * in a loop (which is a lot faster). + */ +export function utf32ToString(data: T, start: number = 0, end: number = data.length): string { + let result = ''; + let cp; + for (let i = start; i < end; ++i) { + if ((cp = data[i]) > 0xFFFF) { + cp -= 0x10000; + result += String.fromCharCode((cp >> 10) + 0xD800) + String.fromCharCode((cp % 0x400) + 0xDC00); + } else { + result += String.fromCharCode(cp); + } + } + return result; +}