lychees's picture
Upload 569 files
87b3b3a
/**
* @namespace
* Contains text tokenization and breaking routines
*/
ROT.Text = {
RE_COLORS: /%([bc]){([^}]*)}/g,
/* token types */
TYPE_TEXT: 0,
TYPE_NEWLINE: 1,
TYPE_FG: 2,
TYPE_BG: 3,
/**
* Measure size of a resulting text block
*/
measure: function(str, maxWidth) {
var result = {width:0, height:1};
var tokens = this.tokenize(str, maxWidth);
var lineWidth = 0;
for (var i=0;i<tokens.length;i++) {
var token = tokens[i];
switch (token.type) {
case this.TYPE_TEXT:
lineWidth += token.value.length;
break;
case this.TYPE_NEWLINE:
result.height++;
result.width = Math.max(result.width, lineWidth);
lineWidth = 0;
break;
}
}
result.width = Math.max(result.width, lineWidth);
return result;
},
/**
* Convert string to a series of a formatting commands
*/
tokenize: function(str, maxWidth) {
var result = [];
/* first tokenization pass - split texts and color formatting commands */
var offset = 0;
str.replace(this.RE_COLORS, function(match, type, name, index) {
/* string before */
var part = str.substring(offset, index);
if (part.length) {
result.push({
type: ROT.Text.TYPE_TEXT,
value: part
});
}
/* color command */
result.push({
type: (type == "c" ? ROT.Text.TYPE_FG : ROT.Text.TYPE_BG),
value: name.trim()
});
offset = index + match.length;
return "";
});
/* last remaining part */
var part = str.substring(offset);
if (part.length) {
result.push({
type: ROT.Text.TYPE_TEXT,
value: part
});
}
return this._breakLines(result, maxWidth);
},
/* insert line breaks into first-pass tokenized data */
_breakLines: function(tokens, maxWidth) {
if (!maxWidth) { maxWidth = Infinity; };
var i = 0;
var lineLength = 0;
var lastTokenWithSpace = -1;
while (i < tokens.length) { /* take all text tokens, remove space, apply linebreaks */
var token = tokens[i];
if (token.type == ROT.Text.TYPE_NEWLINE) { /* reset */
lineLength = 0;
lastTokenWithSpace = -1;
}
if (token.type != ROT.Text.TYPE_TEXT) { /* skip non-text tokens */
i++;
continue;
}
/* remove spaces at the beginning of line */
while (lineLength == 0 && token.value.charAt(0) == " ") { token.value = token.value.substring(1); }
/* forced newline? insert two new tokens after this one */
var index = token.value.indexOf("\n");
if (index != -1) {
token.value = this._breakInsideToken(tokens, i, index, true);
/* if there are spaces at the end, we must remove them (we do not want the line too long) */
var arr = token.value.split("");
while (arr[arr.length-1] == " ") { arr.pop(); }
token.value = arr.join("");
}
/* token degenerated? */
if (!token.value.length) {
tokens.splice(i, 1);
continue;
}
if (lineLength + token.value.length > maxWidth) { /* line too long, find a suitable breaking spot */
/* is it possible to break within this token? */
var index = -1;
while (1) {
var nextIndex = token.value.indexOf(" ", index+1);
if (nextIndex == -1) { break; }
if (lineLength + nextIndex > maxWidth) { break; }
index = nextIndex;
}
if (index != -1) { /* break at space within this one */
token.value = this._breakInsideToken(tokens, i, index, true);
} else if (lastTokenWithSpace != -1) { /* is there a previous token where a break can occur? */
var token = tokens[lastTokenWithSpace];
var breakIndex = token.value.lastIndexOf(" ");
token.value = this._breakInsideToken(tokens, lastTokenWithSpace, breakIndex, true);
i = lastTokenWithSpace;
} else { /* force break in this token */
token.value = this._breakInsideToken(tokens, i, maxWidth-lineLength, false);
}
} else { /* line not long, continue */
lineLength += token.value.length;
if (token.value.indexOf(" ") != -1) { lastTokenWithSpace = i; }
}
i++; /* advance to next token */
}
tokens.push({type: ROT.Text.TYPE_NEWLINE}); /* insert fake newline to fix the last text line */
/* remove trailing space from text tokens before newlines */
var lastTextToken = null;
for (var i=0;i<tokens.length;i++) {
var token = tokens[i];
switch (token.type) {
case ROT.Text.TYPE_TEXT: lastTextToken = token; break;
case ROT.Text.TYPE_NEWLINE:
if (lastTextToken) { /* remove trailing space */
var arr = lastTextToken.value.split("");
while (arr[arr.length-1] == " ") { arr.pop(); }
lastTextToken.value = arr.join("");
}
lastTextToken = null;
break;
}
}
tokens.pop(); /* remove fake token */
return tokens;
},
/**
* Create new tokens and insert them into the stream
* @param {object[]} tokens
* @param {int} tokenIndex Token being processed
* @param {int} breakIndex Index within current token's value
* @param {bool} removeBreakChar Do we want to remove the breaking character?
* @returns {string} remaining unbroken token value
*/
_breakInsideToken: function(tokens, tokenIndex, breakIndex, removeBreakChar) {
var newBreakToken = {
type: ROT.Text.TYPE_NEWLINE
}
var newTextToken = {
type: ROT.Text.TYPE_TEXT,
value: tokens[tokenIndex].value.substring(breakIndex + (removeBreakChar ? 1 : 0))
}
tokens.splice(tokenIndex+1, 0, newBreakToken, newTextToken);
return tokens[tokenIndex].value.substring(0, breakIndex);
}
}