public static visibleColumnFromColumn(lineContent: string, column: number, tabSize: number): number {
		let endOffset = lineContent.length;
		if (endOffset > column - 1) {
			endOffset = column - 1;
		}

		let result = 0;
		for (let i = 0; i < endOffset; i++) {
			let charCode = lineContent.charCodeAt(i);
			if (charCode === CharCode.Tab) {
				result = this.nextTabStop(result, tabSize);
			} else if (strings.isFullWidthCharacter(charCode)) {
				result = result + 2;
			} else {
				result = result + 1;
			}
		}
		return result;
	}
	public static columnFromVisibleColumn(lineContent: string, visibleColumn: number, tabSize: number): number {
		if (visibleColumn <= 0) {
			return 1;
		}

		const lineLength = lineContent.length;

		let beforeVisibleColumn = 0;
		for (let i = 0; i < lineLength; i++) {
			let charCode = lineContent.charCodeAt(i);

			let afterVisibleColumn: number;
			if (charCode === CharCode.Tab) {
				afterVisibleColumn = this.nextTabStop(beforeVisibleColumn, tabSize);
			} else if (strings.isFullWidthCharacter(charCode)) {
				afterVisibleColumn = beforeVisibleColumn + 2;
			} else {
				afterVisibleColumn = beforeVisibleColumn + 1;
			}

			if (afterVisibleColumn >= visibleColumn) {
				let prevDelta = visibleColumn - beforeVisibleColumn;
				let afterDelta = afterVisibleColumn - visibleColumn;
				if (afterDelta < prevDelta) {
					return i + 2;
				} else {
					return i + 1;
				}
			}

			beforeVisibleColumn = afterVisibleColumn;
		}

		// walked the entire string
		return lineLength + 1;
	}
	public createLineMapping(lineText: string, tabSize: number, breakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): ILineMapping {
		if (breakingColumn === -1) {
			return null;
		}

		tabSize = +tabSize; //@perf
		breakingColumn = +breakingColumn; //@perf
		columnsForFullWidthChar = +columnsForFullWidthChar; //@perf
		hardWrappingIndent = +hardWrappingIndent; //@perf

		let wrappedTextIndentVisibleColumn = 0;
		let wrappedTextIndent = '';

		let firstNonWhitespaceIndex = -1;
		if (hardWrappingIndent !== WrappingIndent.None) {
			firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText);
			if (firstNonWhitespaceIndex !== -1) {
				wrappedTextIndent = lineText.substring(0, firstNonWhitespaceIndex);
				for (let i = 0; i < firstNonWhitespaceIndex; i++) {
					wrappedTextIndentVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(wrappedTextIndentVisibleColumn, tabSize, lineText.charCodeAt(i) === CharCode.Tab, 1);
				}
				if (hardWrappingIndent === WrappingIndent.Indent) {
					wrappedTextIndent += '\t';
					wrappedTextIndentVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(wrappedTextIndentVisibleColumn, tabSize, true, 1);
				}
				// Force sticking to beginning of line if indentColumn > 66% breakingColumn
				if (wrappedTextIndentVisibleColumn > 1 / 2 * breakingColumn) {
					wrappedTextIndent = '';
					wrappedTextIndentVisibleColumn = 0;
				}
			}
		}

		let classifier = this.classifier;
		let lastBreakingOffset = 0; // Last 0-based offset in the lineText at which a break happened
		let breakingLengths: number[] = []; // The length of each broken-up line text
		let breakingLengthsIndex: number = 0; // The count of breaks already done
		let visibleColumn = 0; // Visible column since the beginning of the current line
		let niceBreakOffset = -1; // Last index of a character that indicates a break should happen before it (more desirable)
		let niceBreakVisibleColumn = 0; // visible column if a break were to be later introduced before `niceBreakOffset`
		let obtrusiveBreakOffset = -1; // Last index of a character that indicates a break should happen before it (less desirable)
		let obtrusiveBreakVisibleColumn = 0; // visible column if a break were to be later introduced before `obtrusiveBreakOffset`
		let len = lineText.length;

		for (let i = 0; i < len; i++) {
			// At this point, there is a certainty that the character before `i` fits on the current line,
			// but the character at `i` might not fit

			let charCode = lineText.charCodeAt(i);
			let charCodeIsTab = (charCode === CharCode.Tab);
			let charCodeClass = classifier.get(charCode);

			if (charCodeClass === CharacterClass.BREAK_BEFORE) {
				// This is a character that indicates that a break should happen before it
				// Since we are certain the character before `i` fits, there's no extra checking needed,
				// just mark it as a nice breaking opportunity
				niceBreakOffset = i;
				niceBreakVisibleColumn = wrappedTextIndentVisibleColumn;
			}

			// CJK breaking : before break
			if (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i > 0) {
				let prevCode = lineText.charCodeAt(i - 1);
				let prevClass = classifier.get(prevCode);
				if (prevClass !== CharacterClass.BREAK_BEFORE) { // Kinsoku Shori: Don't break after a leading character, like an open bracket
					niceBreakOffset = i;
					niceBreakVisibleColumn = wrappedTextIndentVisibleColumn;
				}
			}

			let charColumnSize = 1;
			if (strings.isFullWidthCharacter(charCode)) {
				charColumnSize = columnsForFullWidthChar;
			}

			// Advance visibleColumn with character at `i`
			visibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(visibleColumn, tabSize, charCodeIsTab, charColumnSize);

			if (visibleColumn > breakingColumn && i !== 0) {
				// We need to break at least before character at `i`:
				//  - break before niceBreakLastOffset if it exists (and re-establish a correct visibleColumn by using niceBreakVisibleColumn + charAt(i))
				//  - otherwise, break before obtrusiveBreakLastOffset if it exists (and re-establish a correct visibleColumn by using obtrusiveBreakVisibleColumn + charAt(i))
				//  - otherwise, break before i (and re-establish a correct visibleColumn by charAt(i))

				let breakBeforeOffset: number;
				let restoreVisibleColumnFrom: number;

				if (niceBreakOffset !== -1 && niceBreakVisibleColumn <= breakingColumn) {

					// We will break before `niceBreakLastOffset`
					breakBeforeOffset = niceBreakOffset;
					restoreVisibleColumnFrom = niceBreakVisibleColumn;

				} else if (obtrusiveBreakOffset !== -1 && obtrusiveBreakVisibleColumn <= breakingColumn) {

					// We will break before `obtrusiveBreakLastOffset`
					breakBeforeOffset = obtrusiveBreakOffset;
					restoreVisibleColumnFrom = obtrusiveBreakVisibleColumn;

				} else {

					// We will break before `i`
					breakBeforeOffset = i;
					restoreVisibleColumnFrom = wrappedTextIndentVisibleColumn;

				}

				// Break before character at `breakBeforeOffset`
				breakingLengths[breakingLengthsIndex++] = breakBeforeOffset - lastBreakingOffset;
				lastBreakingOffset = breakBeforeOffset;

				// Re-establish visibleColumn by taking character at `i` into account
				visibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(restoreVisibleColumnFrom, tabSize, charCodeIsTab, charColumnSize);

				// Reset markers
				niceBreakOffset = -1;
				niceBreakVisibleColumn = 0;
				obtrusiveBreakOffset = -1;
				obtrusiveBreakVisibleColumn = 0;
			}

			// At this point, there is a certainty that the character at `i` fits on the current line

			if (niceBreakOffset !== -1) {
				// Advance niceBreakVisibleColumn
				niceBreakVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(niceBreakVisibleColumn, tabSize, charCodeIsTab, charColumnSize);
			}
			if (obtrusiveBreakOffset !== -1) {
				// Advance obtrusiveBreakVisibleColumn
				obtrusiveBreakVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(obtrusiveBreakVisibleColumn, tabSize, charCodeIsTab, charColumnSize);
			}

			if (charCodeClass === CharacterClass.BREAK_AFTER && (hardWrappingIndent === WrappingIndent.None || i >= firstNonWhitespaceIndex)) {
				// This is a character that indicates that a break should happen after it
				niceBreakOffset = i + 1;
				niceBreakVisibleColumn = wrappedTextIndentVisibleColumn;
			}

			// CJK breaking : after break
			if (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i < len - 1) {
				let nextCode = lineText.charCodeAt(i + 1);
				let nextClass = classifier.get(nextCode);
				if (nextClass !== CharacterClass.BREAK_AFTER) { // Kinsoku Shori: Don't break before a trailing character, like a period
					niceBreakOffset = i + 1;
					niceBreakVisibleColumn = wrappedTextIndentVisibleColumn;
				}
			}

			if (charCodeClass === CharacterClass.BREAK_OBTRUSIVE) {
				// This is an obtrusive character that indicates that a break should happen after it
				obtrusiveBreakOffset = i + 1;
				obtrusiveBreakVisibleColumn = wrappedTextIndentVisibleColumn;
			}
		}

		if (breakingLengthsIndex === 0) {
			return null;
		}

		// Add last segment
		breakingLengths[breakingLengthsIndex++] = len - lastBreakingOffset;

		return new CharacterHardWrappingLineMapping(
			new PrefixSumComputer(toUint32Array(breakingLengths)),
			wrappedTextIndent
		);
	}
/**
 * This function is on purpose not split up into multiple functions to allow runtime type inference (i.e. performance reasons).
 * Notice how all the needed data is fully resolved and passed in (i.e. no other calls).
 */
function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): RenderLineOutput {
	const fontIsMonospace = input.fontIsMonospace;
	const containsForeignElements = input.containsForeignElements;
	const lineContent = input.lineContent;
	const len = input.len;
	const isOverflowing = input.isOverflowing;
	const parts = input.parts;
	const tabSize = input.tabSize;
	const containsRTL = input.containsRTL;
	const spaceWidth = input.spaceWidth;
	const renderWhitespace = input.renderWhitespace;
	const renderControlCharacters = input.renderControlCharacters;

	const characterMapping = new CharacterMapping(len + 1, parts.length);

	let charIndex = 0;
	let tabsCharDelta = 0;
	let charOffsetInPart = 0;

	let prevPartContentCnt = 0;
	let partAbsoluteOffset = 0;

	sb.appendASCIIString('<span>');

	for (let partIndex = 0, tokensLen = parts.length; partIndex < tokensLen; partIndex++) {
		partAbsoluteOffset += prevPartContentCnt;

		const part = parts[partIndex];
		const partEndIndex = part.endIndex;
		const partType = part.type;
		const partRendersWhitespace = (renderWhitespace !== RenderWhitespace.None && (partType.indexOf('vs-whitespace') >= 0));
		charOffsetInPart = 0;

		sb.appendASCIIString('<span class="');
		sb.appendASCIIString(partType);
		sb.appendASCII(CharCode.DoubleQuote);

		if (partRendersWhitespace) {

			let partContentCnt = 0;
			{
				let _charIndex = charIndex;
				let _tabsCharDelta = tabsCharDelta;

				for (; _charIndex < partEndIndex; _charIndex++) {
					const charCode = lineContent.charCodeAt(_charIndex);

					if (charCode === CharCode.Tab) {
						let insertSpacesCount = tabSize - (_charIndex + _tabsCharDelta) % tabSize;
						_tabsCharDelta += insertSpacesCount - 1;
						partContentCnt += insertSpacesCount;
					} else {
						// must be CharCode.Space
						partContentCnt++;
					}
				}
			}

			if (!fontIsMonospace) {
				const partIsOnlyWhitespace = (partType === 'vs-whitespace');
				if (partIsOnlyWhitespace || !containsForeignElements) {
					sb.appendASCIIString(' style="width:');
					sb.appendASCIIString(String(spaceWidth * partContentCnt));
					sb.appendASCIIString('px"');
				}
			}
			sb.appendASCII(CharCode.GreaterThan);

			for (; charIndex < partEndIndex; charIndex++) {
				characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
				const charCode = lineContent.charCodeAt(charIndex);

				if (charCode === CharCode.Tab) {
					let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
					tabsCharDelta += insertSpacesCount - 1;
					charOffsetInPart += insertSpacesCount - 1;
					if (insertSpacesCount > 0) {
						sb.write1(0x2192); // &rarr;
						insertSpacesCount--;
					}
					while (insertSpacesCount > 0) {
						sb.write1(0xA0); // &nbsp;
						insertSpacesCount--;
					}
				} else {
					// must be CharCode.Space
					sb.write1(0xb7); // &middot;
				}

				charOffsetInPart++;
			}

			prevPartContentCnt = partContentCnt;

		} else {

			let partContentCnt = 0;

			if (containsRTL) {
				sb.appendASCIIString(' dir="ltr"');
			}
			sb.appendASCII(CharCode.GreaterThan);

			for (; charIndex < partEndIndex; charIndex++) {
				characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
				const charCode = lineContent.charCodeAt(charIndex);

				switch (charCode) {
					case CharCode.Tab:
						let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
						tabsCharDelta += insertSpacesCount - 1;
						charOffsetInPart += insertSpacesCount - 1;
						while (insertSpacesCount > 0) {
							sb.write1(0xA0); // &nbsp;
							partContentCnt++;
							insertSpacesCount--;
						}
						break;

					case CharCode.Space:
						sb.write1(0xA0); // &nbsp;
						partContentCnt++;
						break;

					case CharCode.LessThan:
						sb.appendASCIIString('&lt;');
						partContentCnt++;
						break;

					case CharCode.GreaterThan:
						sb.appendASCIIString('&gt;');
						partContentCnt++;
						break;

					case CharCode.Ampersand:
						sb.appendASCIIString('&amp;');
						partContentCnt++;
						break;

					case CharCode.Null:
						sb.appendASCIIString('&#00;');
						partContentCnt++;
						break;

					case CharCode.UTF8_BOM:
					case CharCode.LINE_SEPARATOR_2028:
						sb.write1(0xfffd);
						partContentCnt++;
						break;

					default:
						if (strings.isFullWidthCharacter(charCode)) {
							tabsCharDelta++;
						}
						if (renderControlCharacters && charCode < 32) {
							sb.write1(9216 + charCode);
							partContentCnt++;
						} else {
							sb.write1(charCode);
							partContentCnt++;
						}
				}

				charOffsetInPart++;
			}

			prevPartContentCnt = partContentCnt;
		}

		sb.appendASCIIString('</span>');

	}

	// When getting client rects for the last character, we will position the
	// text range at the end of the span, insteaf of at the beginning of next span
	characterMapping.setPartData(len, parts.length - 1, charOffsetInPart, partAbsoluteOffset);

	if (isOverflowing) {
		sb.appendASCIIString('<span>&hellip;</span>');
	}

	sb.appendASCIIString('</span>');

	return new RenderLineOutput(characterMapping, containsRTL, containsForeignElements);
}
/**
 * Whitespace is rendered by "replacing" tokens with a special-purpose `vs-whitespace` type that is later recognized in the rendering phase.
 * Moreover, a token is created for every visual indent because on some fonts the glyphs used for rendering whitespace (&rarr; or &middot;) do not have the same width as &nbsp;.
 * The rendering phase will generate `style="width:..."` for these tokens.
 */
function _applyRenderWhitespace(lineContent: string, len: number, tokens: LinePart[], fauxIndentLength: number, tabSize: number, useMonospaceOptimizations: boolean, onlyBoundary: boolean): LinePart[] {

	let result: LinePart[] = [], resultLen = 0;
	let tokenIndex = 0;
	let tokenType = tokens[tokenIndex].type;
	let tokenEndIndex = tokens[tokenIndex].endIndex;

	let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent);
	let lastNonWhitespaceIndex: number;
	if (firstNonWhitespaceIndex === -1) {
		// The entire line is whitespace
		firstNonWhitespaceIndex = len;
		lastNonWhitespaceIndex = len;
	} else {
		lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent);
	}

	let tmpIndent = 0;
	for (let charIndex = 0; charIndex < fauxIndentLength; charIndex++) {
		const chCode = lineContent.charCodeAt(charIndex);
		if (chCode === CharCode.Tab) {
			tmpIndent = tabSize;
		} else if (strings.isFullWidthCharacter(chCode)) {
			tmpIndent += 2;
		} else {
			tmpIndent++;
		}
	}
	tmpIndent = tmpIndent % tabSize;

	let wasInWhitespace = false;
	for (let charIndex = fauxIndentLength; charIndex < len; charIndex++) {
		const chCode = lineContent.charCodeAt(charIndex);

		let isInWhitespace: boolean;
		if (charIndex < firstNonWhitespaceIndex || charIndex > lastNonWhitespaceIndex) {
			// in leading or trailing whitespace
			isInWhitespace = true;
		} else if (chCode === CharCode.Tab) {
			// a tab character is rendered both in all and boundary cases
			isInWhitespace = true;
		} else if (chCode === CharCode.Space) {
			// hit a space character
			if (onlyBoundary) {
				// rendering only boundary whitespace
				if (wasInWhitespace) {
					isInWhitespace = true;
				} else {
					const nextChCode = (charIndex + 1 < len ? lineContent.charCodeAt(charIndex + 1) : CharCode.Null);
					isInWhitespace = (nextChCode === CharCode.Space || nextChCode === CharCode.Tab);
				}
			} else {
				isInWhitespace = true;
			}
		} else {
			isInWhitespace = false;
		}

		if (wasInWhitespace) {
			// was in whitespace token
			if (!isInWhitespace || (!useMonospaceOptimizations && tmpIndent >= tabSize)) {
				// leaving whitespace token or entering a new indent
				result[resultLen++] = new LinePart(charIndex, 'vs-whitespace');
				tmpIndent = tmpIndent % tabSize;
			}
		} else {
			// was in regular token
			if (charIndex === tokenEndIndex || (isInWhitespace && charIndex > fauxIndentLength)) {
				result[resultLen++] = new LinePart(charIndex, tokenType);
				tmpIndent = tmpIndent % tabSize;
			}
		}

		if (chCode === CharCode.Tab) {
			tmpIndent = tabSize;
		} else if (strings.isFullWidthCharacter(chCode)) {
			tmpIndent += 2;
		} else {
			tmpIndent++;
		}

		wasInWhitespace = isInWhitespace;

		if (charIndex === tokenEndIndex) {
			tokenIndex++;
			tokenType = tokens[tokenIndex].type;
			tokenEndIndex = tokens[tokenIndex].endIndex;
		}
	}

	if (wasInWhitespace) {
		// was in whitespace token
		result[resultLen++] = new LinePart(len, 'vs-whitespace');
	} else {
		// was in regular token
		result[resultLen++] = new LinePart(len, tokenType);
	}

	return result;
}
	public createLineMapping(lineText: string, tabSize: number, breakingColumn: number, columnsForFullWidthChar:number, hardWrappingIndent:WrappingIndent): ILineMapping {
		if (breakingColumn === -1) {
			return null;
		}

		tabSize = +tabSize; //@perf
		breakingColumn = +breakingColumn; //@perf
		columnsForFullWidthChar = +columnsForFullWidthChar; //@perf
		hardWrappingIndent = +hardWrappingIndent; //@perf

		var wrappedTextIndentVisibleColumn = 0,
			wrappedTextIndent = '',
			TAB_CHAR_CODE = '\t'.charCodeAt(0);

		if (hardWrappingIndent !== WrappingIndent.None) {
			var firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText);
			if (firstNonWhitespaceIndex !== -1) {
				wrappedTextIndent = lineText.substring(0, firstNonWhitespaceIndex);
				for (var i = 0; i < firstNonWhitespaceIndex; i++) {
					wrappedTextIndentVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(wrappedTextIndentVisibleColumn, tabSize, lineText.charCodeAt(i) === TAB_CHAR_CODE, 1);
				}
				if (hardWrappingIndent === WrappingIndent.Indent) {
					wrappedTextIndent += '\t';
					wrappedTextIndentVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(wrappedTextIndentVisibleColumn, tabSize, true, 1);
				}
				// Force sticking to beginning of line if indentColumn > 66% breakingColumn
				if (wrappedTextIndentVisibleColumn > 1/2 * breakingColumn) {
					wrappedTextIndent = '';
					wrappedTextIndentVisibleColumn = 0;
				}
			}
		}

		var characterClasses = this.characterClasses,
			lastBreakingOffset = 0, // Last 0-based offset in the lineText at which a break happened
			breakingLengths:number[] = [], // The length of each broken-up line text
			breakingLengthsIndex:number = 0, // The count of breaks already done
			i:number,
			len:number,
			visibleColumn:number, // Visible column since the beginning of the current line
			charCode:number,
			charCodeIsTab:boolean,
			charCodeClass:number,
			breakBeforeOffset:number, // 0-based offset in the lineText before which breaking
			restoreVisibleColumnFrom:number; // visible column used to re-establish a correct `visibleColumn`

		var niceBreakOffset = -1, // Last index of a character that indicates a break should happen before it (more desirable)
			niceBreakVisibleColumn = 0, // visible column if a break were to be later introduced before `niceBreakOffset`
			obtrusiveBreakOffset = -1, // Last index of a character that indicates a break should happen before it (less desirable)
			obtrusiveBreakVisibleColumn = 0; // visible column if a break were to be later introduced before `obtrusiveBreakOffset`

		visibleColumn = 0;
		for (i = 0, len = lineText.length; i < len; i++) {
			// At this point, there is a certainty that the character before `i` fits on the current line,
			// but the character at `i` might not fit

			charCode = lineText.charCodeAt(i);
			charCodeIsTab = (charCode === TAB_CHAR_CODE);
			charCodeClass = charCode < characterClasses.length ? characterClasses[charCode] : 0;

			if (charCodeClass === BREAK_BEFORE_CLASS) {
				// This is a character that indicates that a break should happen before it
				// Since we are certain the character before `i` fits, there's no extra checking needed,
				// just mark it as a nice breaking opportunity
				niceBreakOffset = i;
				niceBreakVisibleColumn = 0;
			}

			var charColumnSize = 1;
			if (strings.isFullWidthCharacter(charCode)) {
				charColumnSize = columnsForFullWidthChar;
			}

			// Advance visibleColumn with character at `i`
			visibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(visibleColumn, tabSize, charCodeIsTab, charColumnSize);

			if (visibleColumn > breakingColumn && i !== 0) {
				// We need to break at least before character at `i`:
				//  - break before niceBreakLastOffset if it exists (and re-establish a correct visibleColumn by using niceBreakVisibleColumn + charAt(i))
				//  - otherwise, break before obtrusiveBreakLastOffset if it exists (and re-establish a correct visibleColumn by using obtrusiveBreakVisibleColumn + charAt(i))
				//  - otherwise, break before i (and re-establish a correct visibleColumn by charAt(i))

				if (niceBreakOffset !== -1) {

					// We will break before `niceBreakLastOffset`
					breakBeforeOffset = niceBreakOffset;
					restoreVisibleColumnFrom = niceBreakVisibleColumn + wrappedTextIndentVisibleColumn;

				} else if (obtrusiveBreakOffset !== -1) {

					// We will break before `obtrusiveBreakLastOffset`
					breakBeforeOffset = obtrusiveBreakOffset;
					restoreVisibleColumnFrom = obtrusiveBreakVisibleColumn + wrappedTextIndentVisibleColumn;

				} else {

					// We will break before `i`
					breakBeforeOffset = i;
					restoreVisibleColumnFrom = 0 + wrappedTextIndentVisibleColumn;

				}

				// Break before character at `breakBeforeOffset`
				breakingLengths[breakingLengthsIndex++] = breakBeforeOffset - lastBreakingOffset;
				lastBreakingOffset = breakBeforeOffset;

				// Re-establish visibleColumn by taking character at `i` into account
				visibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(restoreVisibleColumnFrom, tabSize, charCodeIsTab, charColumnSize);

				// Reset markers
				niceBreakOffset = -1;
				niceBreakVisibleColumn = 0;
				obtrusiveBreakOffset = -1;
				obtrusiveBreakVisibleColumn = 0;
			}

			// At this point, there is a certainty that the character at `i` fits on the current line

			if (niceBreakOffset !== -1) {
				// Advance niceBreakVisibleColumn
				niceBreakVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(niceBreakVisibleColumn, tabSize, charCodeIsTab, charColumnSize);
			}
			if (obtrusiveBreakOffset !== -1) {
				// Advance obtrusiveBreakVisibleColumn
				obtrusiveBreakVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(obtrusiveBreakVisibleColumn, tabSize, charCodeIsTab, charColumnSize);
			}

			if (charCodeClass === BREAK_AFTER_CLASS) {
				// This is a character that indicates that a break should happen after it
				niceBreakOffset = i + 1;
				niceBreakVisibleColumn = 0;
			}

			if (charCodeClass === BREAK_OBTRUSIVE_CLASS) {
				// This is an obtrusive character that indicates that a break should happen after it
				obtrusiveBreakOffset = i + 1;
				obtrusiveBreakVisibleColumn = 0;
			}
		}

		if (breakingLengthsIndex === 0) {
			return null;
		}

		// Add last segment
		breakingLengths[breakingLengthsIndex++] = len - lastBreakingOffset;

		return new CharacterHardWrappingLineMapping(new PrefixSumComputer(breakingLengths), wrappedTextIndent);
	}