blob: 4971b864e4d57e4dee7fbae791aaa5ddb83eaa9a [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
lib.rtdep('lib.colors');
/**
* Constructor for TextAttribute objects.
*
* These objects manage a set of text attributes such as foreground/
* background color, bold, faint, italic, blink, underline, and strikethrough.
*
* TextAttribute instances can be used to construct a DOM container implementing
* the current attributes, or to test an existing DOM container for
* compatibility with the current attributes.
*
* @constructor
* @param {HTMLDocument} document The parent document to use when creating
* new DOM containers.
*/
hterm.TextAttributes = function(document) {
this.document_ = document;
// These variables contain the source of the color as either:
// SRC_DEFAULT (use context default)
// SRC_RGB (specified in 'rgb( r, g, b)' form)
// number (representing the index from color palette to use)
this.foregroundSource = this.SRC_DEFAULT;
this.backgroundSource = this.SRC_DEFAULT;
// These properties cache the value in the color table, but foregroundSource
// and backgroundSource contain the canonical values.
this.foreground = this.DEFAULT_COLOR;
this.background = this.DEFAULT_COLOR;
this.defaultForeground = 'rgb(255, 255, 255)';
this.defaultBackground = 'rgb(0, 0, 0)';
this.bold = false;
this.faint = false;
this.italic = false;
this.blink = false;
this.underline = false;
this.strikethrough = false;
this.inverse = false;
this.invisible = false;
this.wcNode = false;
this.tileData = null;
this.colorPalette = null;
this.resetColorPalette();
};
/**
* If false, we ignore the bold attribute.
*
* This is used for fonts that have a bold version that is a different size
* than the normal weight version.
*/
hterm.TextAttributes.prototype.enableBold = true;
/**
* If true, use bright colors (if available) for bold text.
*
* This setting is independent of the enableBold setting.
*/
hterm.TextAttributes.prototype.enableBoldAsBright = true;
/**
* A sentinel constant meaning "whatever the default color is in this context".
*/
hterm.TextAttributes.prototype.DEFAULT_COLOR = lib.f.createEnum('');
/**
* A constant string used to specify that source color is context default.
*/
hterm.TextAttributes.prototype.SRC_DEFAULT = 'default';
/**
* A constant string used to specify that the source of a color is a valid
* rgb( r, g, b) specifier.
*/
hterm.TextAttributes.prototype.SRC_RGB = 'rgb';
/**
* The document object which should own the DOM nodes created by this instance.
*
* @param {HTMLDocument} document The parent document.
*/
hterm.TextAttributes.prototype.setDocument = function(document) {
this.document_ = document;
};
/**
* Create a deep copy of this object.
*
* @return {hterm.TextAttributes} A deep copy of this object.
*/
hterm.TextAttributes.prototype.clone = function() {
var rv = new hterm.TextAttributes(null);
for (var key in this) {
rv[key] = this[key];
}
rv.colorPalette = this.colorPalette.concat();
return rv;
};
/**
* Reset the current set of attributes.
*
* This does not affect the palette. Use resetColorPalette() for that.
* It also doesn't affect the tile data, it's not meant to.
*/
hterm.TextAttributes.prototype.reset = function() {
this.foregroundSource = this.SRC_DEFAULT;
this.backgroundSource = this.SRC_DEFAULT;
this.foreground = this.DEFAULT_COLOR;
this.background = this.DEFAULT_COLOR;
this.bold = false;
this.faint = false;
this.italic = false;
this.blink = false;
this.underline = false;
this.strikethrough = false;
this.inverse = false;
this.invisible = false;
this.wcNode = false;
};
/**
* Reset the color palette to the default state.
*/
hterm.TextAttributes.prototype.resetColorPalette = function() {
this.colorPalette = lib.colors.colorPalette.concat();
this.syncColors();
};
/**
* Test if the current attributes describe unstyled text.
*
* @return {boolean} True if the current attributes describe unstyled text.
*/
hterm.TextAttributes.prototype.isDefault = function() {
return (this.foregroundSource == this.SRC_DEFAULT &&
this.backgroundSource == this.SRC_DEFAULT &&
!this.bold &&
!this.faint &&
!this.italic &&
!this.blink &&
!this.underline &&
!this.strikethrough &&
!this.inverse &&
!this.invisible &&
!this.wcNode &&
this.tileData == null);
};
/**
* Create a DOM container (a span or a text node) with a style to match the
* current set of attributes.
*
* This method will create a plain text node if the text is unstyled, or
* an HTML span if the text is styled. Due to lack of monospace wide character
* fonts on certain systems (e.g. Chrome OS), we need to put each wide character
* in a span of CSS class '.wc-node' which has double column width.
* Each vt_tiledata tile is also represented by a span with a single
* character, with CSS classes '.tile' and '.tile_<glyph number>'.
*
* @param {string} opt_textContent Optional text content for the new container.
* @return {HTMLNode} An HTML span or text nodes styled to match the current
* attributes.
*/
hterm.TextAttributes.prototype.createContainer = function(opt_textContent) {
if (this.isDefault())
return this.document_.createTextNode(opt_textContent);
var span = this.document_.createElement('span');
var style = span.style;
var classes = [];
if (this.foreground != this.DEFAULT_COLOR)
style.color = this.foreground;
if (this.background != this.DEFAULT_COLOR)
style.backgroundColor = this.background;
if (this.enableBold && this.bold)
style.fontWeight = 'bold';
if (this.faint)
span.faint = true;
if (this.italic)
style.fontStyle = 'italic';
if (this.blink) {
classes.push('blink-node');
span.blinkNode = true;
}
var textDecoration = '';
if (this.underline) {
textDecoration += ' underline';
span.underline = true;
}
if (this.strikethrough) {
textDecoration += ' line-through';
span.strikethrough = true;
}
if (textDecoration) {
style.textDecoration = textDecoration;
}
if (this.wcNode) {
classes.push('wc-node');
span.wcNode = true;
}
if (this.tileData != null) {
classes.push('tile');
classes.push('tile_' + this.tileData);
span.tileNode = true;
}
if (opt_textContent)
span.textContent = opt_textContent;
if (classes.length)
span.className = classes.join(' ');
return span;
};
/**
* Tests if the provided object (string, span or text node) has the same
* style as this TextAttributes instance.
*
* This indicates that text with these attributes could be inserted directly
* into the target DOM node.
*
* For the purposes of this method, a string is considered a text node.
*
* @param {string|HTMLNode} obj The object to test.
* @return {boolean} True if the provided container has the same style as
* this attributes instance.
*/
hterm.TextAttributes.prototype.matchesContainer = function(obj) {
if (typeof obj == 'string' || obj.nodeType == 3)
return this.isDefault();
var style = obj.style;
// We don't want to put multiple characters in a wcNode or a tile.
// See the comments in createContainer.
return (!(this.wcNode || obj.wcNode) &&
!(this.tileData != null || obj.tileNode) &&
this.foreground == style.color &&
this.background == style.backgroundColor &&
(this.enableBold && this.bold) == !!style.fontWeight &&
this.blink == obj.blinkNode &&
this.italic == !!style.fontStyle &&
!!this.underline == !!obj.underline &&
!!this.strikethrough == !!obj.strikethrough);
};
hterm.TextAttributes.prototype.setDefaults = function(foreground, background) {
this.defaultForeground = foreground;
this.defaultBackground = background;
this.syncColors();
};
/**
* Updates foreground and background properties based on current indices and
* other state.
*
* @param {string} terminalForeground The terminal foreground color for use as
* inverse text background.
* @param {string} terminalBackground The terminal background color for use as
* inverse text foreground.
*
*/
hterm.TextAttributes.prototype.syncColors = function() {
function getBrightIndex(i) {
if (i < 8) {
// If the color is from the lower half of the ANSI 16, add 8.
return i + 8;
}
// If it's not from the 16 color palette, ignore bold requests. This
// matches the behavior of gnome-terminal.
return i;
}
var foregroundSource = this.foregroundSource;
var backgroundSource = this.backgroundSource;
var defaultForeground = this.DEFAULT_COLOR;
var defaultBackground = this.DEFAULT_COLOR;
if (this.inverse) {
foregroundSource = this.backgroundSource;
backgroundSource = this.foregroundSource;
// We can't inherit the container's color anymore.
defaultForeground = this.defaultBackground;
defaultBackground = this.defaultForeground;
}
if (this.enableBoldAsBright && this.bold) {
if (foregroundSource != this.SRC_DEFAULT &&
foregroundSource != this.SRC_RGB) {
foregroundSource = getBrightIndex(foregroundSource);
}
}
if (this.invisible) {
foregroundSource = backgroundSource;
defaultForeground = this.defaultBackground;
}
// Set fore/background colors unless already specified in rgb(r, g, b) form.
if (foregroundSource != this.SRC_RGB) {
this.foreground = ((foregroundSource == this.SRC_DEFAULT) ?
defaultForeground : this.colorPalette[foregroundSource]);
}
if (this.faint && !this.invisible) {
var colorToMakeFaint = ((this.foreground == this.DEFAULT_COLOR) ?
this.defaultForeground : this.foreground);
this.foreground = lib.colors.mix(colorToMakeFaint, 'rgb(0, 0, 0)', 0.3333);
}
if (backgroundSource != this.SRC_RGB) {
this.background = ((backgroundSource == this.SRC_DEFAULT) ?
defaultBackground : this.colorPalette[backgroundSource]);
}
};
/**
* Static method used to test if the provided objects (strings, spans or
* text nodes) have the same style.
*
* For the purposes of this method, a string is considered a text node.
*
* @param {string|HTMLNode} obj1 An object to test.
* @param {string|HTMLNode} obj2 Another object to test.
* @return {boolean} True if the containers have the same style.
*/
hterm.TextAttributes.containersMatch = function(obj1, obj2) {
if (typeof obj1 == 'string')
return hterm.TextAttributes.containerIsDefault(obj2);
if (obj1.nodeType != obj2.nodeType)
return false;
if (obj1.nodeType == 3)
return true;
var style1 = obj1.style;
var style2 = obj2.style;
return (style1.color == style2.color &&
style1.backgroundColor == style2.backgroundColor &&
style1.fontWeight == style2.fontWeight &&
style1.fontStyle == style2.fontStyle &&
style1.textDecoration == style2.textDecoration);
};
/**
* Static method to test if a given DOM container represents unstyled text.
*
* For the purposes of this method, a string is considered a text node.
*
* @param {string|HTMLNode} obj1 An object to test.
* @return {boolean} True if the object is unstyled.
*/
hterm.TextAttributes.containerIsDefault = function(obj) {
return typeof obj == 'string' || obj.nodeType == 3;
};
/**
* Static method to get the column width of a node's textContent.
*
* @param {HTMLElement} node The HTML element to get the width of textContent
* from.
* @return {integer} The column width of the node's textContent.
*/
hterm.TextAttributes.nodeWidth = function(node) {
if (node.wcNode) {
return lib.wc.strWidth(node.textContent);
} else {
return node.textContent.length;
}
}
/**
* Static method to get the substr of a node's textContent. The start index
* and substr width are computed in column width.
*
* @param {HTMLElement} node The HTML element to get the substr of textContent
* from.
* @param {integer} start The starting offset in column width.
* @param {integer} width The width to capture in column width.
* @return {integer} The extracted substr of the node's textContent.
*/
hterm.TextAttributes.nodeSubstr = function(node, start, width) {
if (node.wcNode) {
return lib.wc.substr(node.textContent, start, width);
} else {
return node.textContent.substr(start, width);
}
}
/**
* Static method to get the substring based of a node's textContent. The
* start index of end index are computed in column width.
*
* @param {HTMLElement} node The HTML element to get the substr of textContent
* from.
* @param {integer} start The starting offset in column width.
* @param {integer} end The ending offset in column width.
* @return {integer} The extracted substring of the node's textContent.
*/
hterm.TextAttributes.nodeSubstring = function(node, start, end) {
if (node.wcNode) {
return lib.wc.substring(node.textContent, start, end);
} else {
return node.textContent.substring(start, end);
}
};
/**
* Static method to split a string into contiguous runs of single-width
* characters and runs of double-width characters.
*
* @param {string} str The string to split.
* @return {Array} An array of objects that contain substrings of str, where
* each substring is either a contiguous runs of single-width characters
* or a double-width character. For object that contains a double-width
* character, its wcNode property is set to true.
*/
hterm.TextAttributes.splitWidecharString = function(str) {
var rv = [];
var base = 0, length = 0;
for (var i = 0; i < str.length;) {
var c = str.codePointAt(i);
var increment = (c <= 0xffff) ? 1 : 2;
if (c < 128 || lib.wc.charWidth(c) == 1) {
length += increment;
} else {
if (length) {
rv.push({str: str.substr(base, length)});
}
rv.push({str: str.substr(i, increment), wcNode: true});
base = i + increment;
length = 0;
}
i += increment;
}
if (length)
rv.push({str: str.substr(base, length)});
return rv;
};