Skip to content

Commit

Permalink
Progress on reflow smaller with wide chars
Browse files Browse the repository at this point in the history
  • Loading branch information
Tyriar committed Jan 24, 2019
1 parent 4843ca5 commit ce69dce
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 8 deletions.
35 changes: 34 additions & 1 deletion src/Buffer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,40 @@ describe('Buffer', () => {
assert.equal(buffer.lines.get(1).translateToString(false), '语汉语汉语 ');
});
it('should wrap wide characters correctly when reflowing smaller', () => {
// TODO: ..
buffer.fillViewportRows();
buffer.resize(12, 10);
for (let i = 0; i < 12; i += 4) {
buffer.lines.get(0).set(i, [null, '汉', 2, '汉'.charCodeAt(0)]);
buffer.lines.get(1).set(i, [null, '汉', 2, '汉'.charCodeAt(0)]);
}
for (let i = 2; i < 12; i += 4) {
buffer.lines.get(0).set(i, [null, '语', 2, '语'.charCodeAt(0)]);
buffer.lines.get(1).set(i, [null, '语', 2, '语'.charCodeAt(0)]);
}
for (let i = 1; i < 12; i += 2) {
buffer.lines.get(0).set(i, [null, '', 0, undefined]);
buffer.lines.get(1).set(i, [null, '', 0, undefined]);
}
buffer.lines.get(1).isWrapped = true;
// Buffer:
// 汉语汉语汉语 (wrapped)
// 汉语汉语汉语
assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉语汉语');
assert.equal(buffer.lines.get(1).translateToString(true), '汉语汉语汉语');
buffer.resize(11, 10);
assert.equal(buffer.ybase, 0);
assert.equal(buffer.lines.length, 10);
assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉语汉', '1');
assert.equal(buffer.lines.get(1).translateToString(true), '语汉语汉语', '2');
assert.equal(buffer.lines.get(2).translateToString(true), '汉语');
buffer.resize(10, 10);
assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉语汉');
assert.equal(buffer.lines.get(1).translateToString(true), '语汉语汉语');
assert.equal(buffer.lines.get(2).translateToString(true), '汉语');
buffer.resize(9, 10);
assert.equal(buffer.lines.get(0).translateToString(true), '汉语汉语');
assert.equal(buffer.lines.get(1).translateToString(true), '汉语汉语');
assert.equal(buffer.lines.get(2).translateToString(true), '汉语汉语');
});

describe('reflowLarger cases', () => {
Expand Down
24 changes: 17 additions & 7 deletions src/Buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { EventEmitter } from './common/EventEmitter';
import { IMarker } from 'xterm';
import { BufferLine } from './BufferLine';
import { DEFAULT_COLOR } from './renderer/atlas/Types';
import { reflowSmallerGetLinesNeeded, reflowSmallerGetNewLineLengths } from './BufferReflow';

export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0);
export const CHAR_DATA_ATTR_INDEX = 0;
Expand Down Expand Up @@ -386,11 +387,17 @@ export class Buffer implements IBuffer {
wrappedLines.unshift(nextLine);
}

// Determine how many lines need to be inserted at the end, based on the trimmed length of
// the last wrapped line


const lastLineLength = wrappedLines[wrappedLines.length - 1].getTrimmedLength();
const cellsNeeded = (wrappedLines.length - 1) * this._cols + lastLineLength;
const linesNeeded = Math.ceil(cellsNeeded / newCols);
// const linesNeeded = reflowSmallerGetLinesNeeded(wrappedLines, this._cols, newCols);
const destLineLengths = reflowSmallerGetNewLineLengths(wrappedLines, this._cols, newCols);
console.log(destLineLengths);
const linesNeeded = destLineLengths.length;



const linesToAdd = linesNeeded - wrappedLines.length;
let trimmedLines: number;
if (this.ybase === 0 && this.y !== this.lines.length - 1) {
Expand Down Expand Up @@ -418,8 +425,8 @@ export class Buffer implements IBuffer {
wrappedLines.push(...newLines);

// Copy buffer data to new locations, this needs to happen backwards to do in-place
let destLineIndex = Math.floor(cellsNeeded / newCols);
let destCol = cellsNeeded % newCols;
let destLineIndex = destLineLengths.length - 1; // Math.floor(cellsNeeded / newCols);
let destCol = destLineLengths[destLineIndex]; // cellsNeeded % newCols;
if (destCol === 0) {
destLineIndex--;
destCol = newCols;
Expand All @@ -432,12 +439,13 @@ export class Buffer implements IBuffer {
destCol -= cellsToCopy;
if (destCol === 0) {
destLineIndex--;
destCol = newCols;
destCol = destLineLengths[destLineIndex];
}
srcCol -= cellsToCopy;
if (srcCol === 0) {
srcLineIndex--;
srcCol = this._cols;
// TODO: srcCol shoudl take trimmed length into account
srcCol = wrappedLines[Math.max(srcLineIndex, 0)].getTrimmedLength(); // this._cols;
}
}

Expand Down Expand Up @@ -516,6 +524,8 @@ export class Buffer implements IBuffer {
}
}

// private _reflowSmallerGetLinesNeeded()

/**
* Translates a string index back to a BufferIndex.
* To get the correct buffer position the string must start at `startCol` 0
Expand Down
80 changes: 80 additions & 0 deletions src/BufferReflow.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { BufferLine } from './BufferLine';
import { reflowSmallerGetNewLineLengths } from './BufferReflow';

describe('BufferReflow', () => {
describe('reflowSmallerGetNewLineLengths', () => {
it('should return correct line lengths for a small line with wide characters', () => {
const line = new BufferLine(4);
line.set(0, [null, '汉', 2, '汉'.charCodeAt(0)]);
line.set(1, [null, '', 0, undefined]);
line.set(2, [null, '语', 2, '语'.charCodeAt(0)]);
line.set(3, [null, '', 0, undefined]);
assert.equal(line.translateToString(true), '汉语');
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 4, 3), [2, 2], 'line: 汉, 语');
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 4, 2), [2, 2], 'line: 汉, 语');
});
it('should return correct line lengths for a large line with wide characters', () => {
const line = new BufferLine(12);
for (let i = 0; i < 12; i += 4) {
line.set(i, [null, '汉', 2, '汉'.charCodeAt(0)]);
line.set(i + 2, [null, '语', 2, '语'.charCodeAt(0)]);
}
for (let i = 1; i < 12; i += 2) {
line.set(i, [null, '', 0, undefined]);
line.set(i, [null, '', 0, undefined]);
}
assert.equal(line.translateToString(), '汉语汉语汉语');
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 11), [10, 2], 'line: 汉语汉语汉, 语');
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 10), [10, 2], 'line: 汉语汉语汉, 语');
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 9), [8, 4], 'line: 汉语汉语, 汉语');
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 8), [8, 4], 'line: 汉语汉语, 汉语');
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 7), [6, 6], 'line: 汉语汉, 语汉语');
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 6), [6, 6], 'line: 汉语汉, 语汉语');
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 5), [4, 4, 4], 'line: 汉语, 汉语, 汉语');
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 4), [4, 4, 4], 'line: 汉语, 汉语, 汉语');
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 3), [2, 2, 2, 2, 2, 2], 'line: 汉, 语, 汉, 语, 汉, 语');
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 12, 2), [2, 2, 2, 2, 2, 2], 'line: 汉, 语, 汉, 语, 汉, 语');
});
it('should return correct line lengths for a string with wide and single characters', () => {
const line = new BufferLine(6);
line.set(0, [null, 'a', 1, 'a'.charCodeAt(0)]);
line.set(1, [null, '汉', 2, '汉'.charCodeAt(0)]);
line.set(2, [null, '', 0, undefined]);
line.set(3, [null, '语', 2, '语'.charCodeAt(0)]);
line.set(4, [null, '', 0, undefined]);
line.set(5, [null, 'b', 1, 'b'.charCodeAt(0)]);
assert.equal(line.translateToString(), 'a汉语b');
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 6, 5), [5, 1], 'line: a汉语b');
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 6, 4), [3, 3], 'line: a汉, 语b');
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 6, 3), [3, 3], 'line: a汉, 语b');
assert.deepEqual(reflowSmallerGetNewLineLengths([line], 6, 2), [1, 2, 2, 1], 'line: a, 汉, 语, b');
});
it('should return correct line lengths for a wrapped line with wide and single characters', () => {
const line1 = new BufferLine(6);
line1.set(0, [null, 'a', 1, 'a'.charCodeAt(0)]);
line1.set(1, [null, '汉', 2, '汉'.charCodeAt(0)]);
line1.set(2, [null, '', 0, undefined]);
line1.set(3, [null, '语', 2, '语'.charCodeAt(0)]);
line1.set(4, [null, '', 0, undefined]);
line1.set(5, [null, 'b', 1, 'b'.charCodeAt(0)]);
const line2 = new BufferLine(6, undefined, true);
line2.set(0, [null, 'a', 1, 'a'.charCodeAt(0)]);
line2.set(1, [null, '汉', 2, '汉'.charCodeAt(0)]);
line2.set(2, [null, '', 0, undefined]);
line2.set(3, [null, '语', 2, '语'.charCodeAt(0)]);
line2.set(4, [null, '', 0, undefined]);
line2.set(5, [null, 'b', 1, 'b'.charCodeAt(0)]);
assert.equal(line1.translateToString(), 'a汉语b');
assert.equal(line2.translateToString(), 'a汉语b');
assert.deepEqual(reflowSmallerGetNewLineLengths([line1, line2], 6, 5), [5, 4, 3], 'lines: a汉语, ba汉, 语b');
assert.deepEqual(reflowSmallerGetNewLineLengths([line1, line2], 6, 4), [3, 4, 4, 1], 'lines: a汉, 语ba, 汉语, b');
assert.deepEqual(reflowSmallerGetNewLineLengths([line1, line2], 6, 3), [3, 3, 3, 3], 'lines: a汉, 语b, a汉, 语b');
assert.deepEqual(reflowSmallerGetNewLineLengths([line1, line2], 6, 2), [1, 2, 2, 2, 2, 2, 1], 'lines: a, 汉, 语, ba, 汉, 语, b');
});
});
});
107 changes: 107 additions & 0 deletions src/BufferReflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/

import { BufferLine } from './BufferLine';

/**
* Determine how many lines need to be inserted at the end. This is done by finding what each
* wrapping point will be and counting the lines needed This would be a lot simpler but in the case
* of a line ending with a wide character, the wide character needs to be put on the following line
* or it would be cut in half.
* @param wrappedLines The original wrapped lines.
* @param newCols The new column count.
*/
export function reflowSmallerGetLinesNeeded(wrappedLines: BufferLine[], oldCols: number, newCols: number): number {
const lastLineLength = wrappedLines[wrappedLines.length - 1].getTrimmedLength();
// const cellsNeeded = (wrappedLines.length - 1) * this._cols + lastLineLength;

// TODO: Make faster
const cellsNeeded = wrappedLines.map(l => l.getTrimmedLength()).reduce((p, c) => p + c);

// Lines needed needs to take into account what the ending character of each new line is
let linesNeeded = 0;
let cellsAvailable = 0;
// let currentCol = 0;

// Use srcCol and srcLine to find the new wrapping point, use that to get the cellsAvailable and
// linesNeeded
let srcCol = -1;
let srcLine = 0;
while (cellsAvailable < cellsNeeded) {
// if (srcLine === wrappedLines.length - 1) {
// cellsAvailable += newCols;
// linesNeeded++;
// break;
// }

srcCol += newCols;
if (srcCol >= oldCols) {
srcCol -= oldCols;
srcLine++;
}
if (srcLine >= wrappedLines.length) {
linesNeeded++;
break;
}
const endsWithWide = wrappedLines[srcLine].getWidth(srcCol) === 2;
if (endsWithWide) {
srcCol--;
}
cellsAvailable += endsWithWide ? newCols - 1 : newCols;
linesNeeded++;
}

return linesNeeded;
// return Math.ceil(cellsNeeded / newCols);
}

/**
* Gets the new line lengths for a given wrapped line. The purpose of this function it to pre-
* compute the wrapping points since wide characters may need to be wrapped onto the following line.
* This function will return an array of numbers of where each line wraps to, the resulting array
* will only contain the values `newCols` (when the line does not end with a wide character) and
* `newCols - 1` (when the line does end with a wide character), except for the last value which
* will contain the remaining items to fill the line.
*
* Calling this with a `newCols` value of `1` will lock up.
*
* @param wrappedLines The wrapped lines to evaluate.
* @param oldCols The columns before resize.
* @param newCols The columns after resize.
*/
export function reflowSmallerGetNewLineLengths(wrappedLines: BufferLine[], oldCols: number, newCols: number): number[] {
const newLineLengths: number[] = [];

// TODO: Force cols = 2 to be minimum possible value, this will lock up

const cellsNeeded = wrappedLines.map(l => l.getTrimmedLength()).reduce((p, c) => p + c);

// Use srcCol and srcLine to find the new wrapping point, use that to get the cellsAvailable and
// linesNeeded
let srcCol = -1;
let srcLine = 0;
let cellsAvailable = 0;
while (cellsAvailable < cellsNeeded) {
srcCol += newCols;
if (srcCol >= oldCols) {
srcCol -= oldCols;
srcLine++;
}
if (srcLine >= wrappedLines.length) {
// Add the final line and exit the loop
newLineLengths.push(cellsNeeded - cellsAvailable);
break;
}
const endsWithWide = wrappedLines[srcLine].getWidth(srcCol) === 2;
if (endsWithWide) {
srcCol--;
}
const lineLength = endsWithWide ? newCols - 1 : newCols;
newLineLengths.push(lineLength);
cellsAvailable += lineLength;
}

return newLineLengths;
}

0 comments on commit ce69dce

Please sign in to comment.