You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
470 lines
17 KiB
470 lines
17 KiB
1 month ago
|
import * as imageHelper from '../helper/image.js';
|
||
|
import { extend, retrieve2, retrieve3, reduce } from '../../core/util.js';
|
||
|
import { getLineHeight, getWidth, parsePercent } from '../../contain/text.js';
|
||
|
var STYLE_REG = /\{([a-zA-Z0-9_]+)\|([^}]*)\}/g;
|
||
|
export function truncateText(text, containerWidth, font, ellipsis, options) {
|
||
|
if (!containerWidth) {
|
||
|
return '';
|
||
|
}
|
||
|
var textLines = (text + '').split('\n');
|
||
|
options = prepareTruncateOptions(containerWidth, font, ellipsis, options);
|
||
|
for (var i = 0, len = textLines.length; i < len; i++) {
|
||
|
textLines[i] = truncateSingleLine(textLines[i], options);
|
||
|
}
|
||
|
return textLines.join('\n');
|
||
|
}
|
||
|
function prepareTruncateOptions(containerWidth, font, ellipsis, options) {
|
||
|
options = options || {};
|
||
|
var preparedOpts = extend({}, options);
|
||
|
preparedOpts.font = font;
|
||
|
ellipsis = retrieve2(ellipsis, '...');
|
||
|
preparedOpts.maxIterations = retrieve2(options.maxIterations, 2);
|
||
|
var minChar = preparedOpts.minChar = retrieve2(options.minChar, 0);
|
||
|
preparedOpts.cnCharWidth = getWidth('国', font);
|
||
|
var ascCharWidth = preparedOpts.ascCharWidth = getWidth('a', font);
|
||
|
preparedOpts.placeholder = retrieve2(options.placeholder, '');
|
||
|
var contentWidth = containerWidth = Math.max(0, containerWidth - 1);
|
||
|
for (var i = 0; i < minChar && contentWidth >= ascCharWidth; i++) {
|
||
|
contentWidth -= ascCharWidth;
|
||
|
}
|
||
|
var ellipsisWidth = getWidth(ellipsis, font);
|
||
|
if (ellipsisWidth > contentWidth) {
|
||
|
ellipsis = '';
|
||
|
ellipsisWidth = 0;
|
||
|
}
|
||
|
contentWidth = containerWidth - ellipsisWidth;
|
||
|
preparedOpts.ellipsis = ellipsis;
|
||
|
preparedOpts.ellipsisWidth = ellipsisWidth;
|
||
|
preparedOpts.contentWidth = contentWidth;
|
||
|
preparedOpts.containerWidth = containerWidth;
|
||
|
return preparedOpts;
|
||
|
}
|
||
|
function truncateSingleLine(textLine, options) {
|
||
|
var containerWidth = options.containerWidth;
|
||
|
var font = options.font;
|
||
|
var contentWidth = options.contentWidth;
|
||
|
if (!containerWidth) {
|
||
|
return '';
|
||
|
}
|
||
|
var lineWidth = getWidth(textLine, font);
|
||
|
if (lineWidth <= containerWidth) {
|
||
|
return textLine;
|
||
|
}
|
||
|
for (var j = 0;; j++) {
|
||
|
if (lineWidth <= contentWidth || j >= options.maxIterations) {
|
||
|
textLine += options.ellipsis;
|
||
|
break;
|
||
|
}
|
||
|
var subLength = j === 0
|
||
|
? estimateLength(textLine, contentWidth, options.ascCharWidth, options.cnCharWidth)
|
||
|
: lineWidth > 0
|
||
|
? Math.floor(textLine.length * contentWidth / lineWidth)
|
||
|
: 0;
|
||
|
textLine = textLine.substr(0, subLength);
|
||
|
lineWidth = getWidth(textLine, font);
|
||
|
}
|
||
|
if (textLine === '') {
|
||
|
textLine = options.placeholder;
|
||
|
}
|
||
|
return textLine;
|
||
|
}
|
||
|
function estimateLength(text, contentWidth, ascCharWidth, cnCharWidth) {
|
||
|
var width = 0;
|
||
|
var i = 0;
|
||
|
for (var len = text.length; i < len && width < contentWidth; i++) {
|
||
|
var charCode = text.charCodeAt(i);
|
||
|
width += (0 <= charCode && charCode <= 127) ? ascCharWidth : cnCharWidth;
|
||
|
}
|
||
|
return i;
|
||
|
}
|
||
|
export function parsePlainText(text, style) {
|
||
|
text != null && (text += '');
|
||
|
var overflow = style.overflow;
|
||
|
var padding = style.padding;
|
||
|
var font = style.font;
|
||
|
var truncate = overflow === 'truncate';
|
||
|
var calculatedLineHeight = getLineHeight(font);
|
||
|
var lineHeight = retrieve2(style.lineHeight, calculatedLineHeight);
|
||
|
var bgColorDrawn = !!(style.backgroundColor);
|
||
|
var truncateLineOverflow = style.lineOverflow === 'truncate';
|
||
|
var width = style.width;
|
||
|
var lines;
|
||
|
if (width != null && (overflow === 'break' || overflow === 'breakAll')) {
|
||
|
lines = text ? wrapText(text, style.font, width, overflow === 'breakAll', 0).lines : [];
|
||
|
}
|
||
|
else {
|
||
|
lines = text ? text.split('\n') : [];
|
||
|
}
|
||
|
var contentHeight = lines.length * lineHeight;
|
||
|
var height = retrieve2(style.height, contentHeight);
|
||
|
if (contentHeight > height && truncateLineOverflow) {
|
||
|
var lineCount = Math.floor(height / lineHeight);
|
||
|
lines = lines.slice(0, lineCount);
|
||
|
}
|
||
|
if (text && truncate && width != null) {
|
||
|
var options = prepareTruncateOptions(width, font, style.ellipsis, {
|
||
|
minChar: style.truncateMinChar,
|
||
|
placeholder: style.placeholder
|
||
|
});
|
||
|
for (var i = 0; i < lines.length; i++) {
|
||
|
lines[i] = truncateSingleLine(lines[i], options);
|
||
|
}
|
||
|
}
|
||
|
var outerHeight = height;
|
||
|
var contentWidth = 0;
|
||
|
for (var i = 0; i < lines.length; i++) {
|
||
|
contentWidth = Math.max(getWidth(lines[i], font), contentWidth);
|
||
|
}
|
||
|
if (width == null) {
|
||
|
width = contentWidth;
|
||
|
}
|
||
|
var outerWidth = contentWidth;
|
||
|
if (padding) {
|
||
|
outerHeight += padding[0] + padding[2];
|
||
|
outerWidth += padding[1] + padding[3];
|
||
|
width += padding[1] + padding[3];
|
||
|
}
|
||
|
if (bgColorDrawn) {
|
||
|
outerWidth = width;
|
||
|
}
|
||
|
return {
|
||
|
lines: lines,
|
||
|
height: height,
|
||
|
outerWidth: outerWidth,
|
||
|
outerHeight: outerHeight,
|
||
|
lineHeight: lineHeight,
|
||
|
calculatedLineHeight: calculatedLineHeight,
|
||
|
contentWidth: contentWidth,
|
||
|
contentHeight: contentHeight,
|
||
|
width: width
|
||
|
};
|
||
|
}
|
||
|
var RichTextToken = (function () {
|
||
|
function RichTextToken() {
|
||
|
}
|
||
|
return RichTextToken;
|
||
|
}());
|
||
|
var RichTextLine = (function () {
|
||
|
function RichTextLine(tokens) {
|
||
|
this.tokens = [];
|
||
|
if (tokens) {
|
||
|
this.tokens = tokens;
|
||
|
}
|
||
|
}
|
||
|
return RichTextLine;
|
||
|
}());
|
||
|
var RichTextContentBlock = (function () {
|
||
|
function RichTextContentBlock() {
|
||
|
this.width = 0;
|
||
|
this.height = 0;
|
||
|
this.contentWidth = 0;
|
||
|
this.contentHeight = 0;
|
||
|
this.outerWidth = 0;
|
||
|
this.outerHeight = 0;
|
||
|
this.lines = [];
|
||
|
}
|
||
|
return RichTextContentBlock;
|
||
|
}());
|
||
|
export { RichTextContentBlock };
|
||
|
export function parseRichText(text, style) {
|
||
|
var contentBlock = new RichTextContentBlock();
|
||
|
text != null && (text += '');
|
||
|
if (!text) {
|
||
|
return contentBlock;
|
||
|
}
|
||
|
var topWidth = style.width;
|
||
|
var topHeight = style.height;
|
||
|
var overflow = style.overflow;
|
||
|
var wrapInfo = (overflow === 'break' || overflow === 'breakAll') && topWidth != null
|
||
|
? { width: topWidth, accumWidth: 0, breakAll: overflow === 'breakAll' }
|
||
|
: null;
|
||
|
var lastIndex = STYLE_REG.lastIndex = 0;
|
||
|
var result;
|
||
|
while ((result = STYLE_REG.exec(text)) != null) {
|
||
|
var matchedIndex = result.index;
|
||
|
if (matchedIndex > lastIndex) {
|
||
|
pushTokens(contentBlock, text.substring(lastIndex, matchedIndex), style, wrapInfo);
|
||
|
}
|
||
|
pushTokens(contentBlock, result[2], style, wrapInfo, result[1]);
|
||
|
lastIndex = STYLE_REG.lastIndex;
|
||
|
}
|
||
|
if (lastIndex < text.length) {
|
||
|
pushTokens(contentBlock, text.substring(lastIndex, text.length), style, wrapInfo);
|
||
|
}
|
||
|
var pendingList = [];
|
||
|
var calculatedHeight = 0;
|
||
|
var calculatedWidth = 0;
|
||
|
var stlPadding = style.padding;
|
||
|
var truncate = overflow === 'truncate';
|
||
|
var truncateLine = style.lineOverflow === 'truncate';
|
||
|
function finishLine(line, lineWidth, lineHeight) {
|
||
|
line.width = lineWidth;
|
||
|
line.lineHeight = lineHeight;
|
||
|
calculatedHeight += lineHeight;
|
||
|
calculatedWidth = Math.max(calculatedWidth, lineWidth);
|
||
|
}
|
||
|
outer: for (var i = 0; i < contentBlock.lines.length; i++) {
|
||
|
var line = contentBlock.lines[i];
|
||
|
var lineHeight = 0;
|
||
|
var lineWidth = 0;
|
||
|
for (var j = 0; j < line.tokens.length; j++) {
|
||
|
var token = line.tokens[j];
|
||
|
var tokenStyle = token.styleName && style.rich[token.styleName] || {};
|
||
|
var textPadding = token.textPadding = tokenStyle.padding;
|
||
|
var paddingH = textPadding ? textPadding[1] + textPadding[3] : 0;
|
||
|
var font = token.font = tokenStyle.font || style.font;
|
||
|
token.contentHeight = getLineHeight(font);
|
||
|
var tokenHeight = retrieve2(tokenStyle.height, token.contentHeight);
|
||
|
token.innerHeight = tokenHeight;
|
||
|
textPadding && (tokenHeight += textPadding[0] + textPadding[2]);
|
||
|
token.height = tokenHeight;
|
||
|
token.lineHeight = retrieve3(tokenStyle.lineHeight, style.lineHeight, tokenHeight);
|
||
|
token.align = tokenStyle && tokenStyle.align || style.align;
|
||
|
token.verticalAlign = tokenStyle && tokenStyle.verticalAlign || 'middle';
|
||
|
if (truncateLine && topHeight != null && calculatedHeight + token.lineHeight > topHeight) {
|
||
|
if (j > 0) {
|
||
|
line.tokens = line.tokens.slice(0, j);
|
||
|
finishLine(line, lineWidth, lineHeight);
|
||
|
contentBlock.lines = contentBlock.lines.slice(0, i + 1);
|
||
|
}
|
||
|
else {
|
||
|
contentBlock.lines = contentBlock.lines.slice(0, i);
|
||
|
}
|
||
|
break outer;
|
||
|
}
|
||
|
var styleTokenWidth = tokenStyle.width;
|
||
|
var tokenWidthNotSpecified = styleTokenWidth == null || styleTokenWidth === 'auto';
|
||
|
if (typeof styleTokenWidth === 'string' && styleTokenWidth.charAt(styleTokenWidth.length - 1) === '%') {
|
||
|
token.percentWidth = styleTokenWidth;
|
||
|
pendingList.push(token);
|
||
|
token.contentWidth = getWidth(token.text, font);
|
||
|
}
|
||
|
else {
|
||
|
if (tokenWidthNotSpecified) {
|
||
|
var textBackgroundColor = tokenStyle.backgroundColor;
|
||
|
var bgImg = textBackgroundColor && textBackgroundColor.image;
|
||
|
if (bgImg) {
|
||
|
bgImg = imageHelper.findExistImage(bgImg);
|
||
|
if (imageHelper.isImageReady(bgImg)) {
|
||
|
token.width = Math.max(token.width, bgImg.width * tokenHeight / bgImg.height);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
var remainTruncWidth = truncate && topWidth != null
|
||
|
? topWidth - lineWidth : null;
|
||
|
if (remainTruncWidth != null && remainTruncWidth < token.width) {
|
||
|
if (!tokenWidthNotSpecified || remainTruncWidth < paddingH) {
|
||
|
token.text = '';
|
||
|
token.width = token.contentWidth = 0;
|
||
|
}
|
||
|
else {
|
||
|
token.text = truncateText(token.text, remainTruncWidth - paddingH, font, style.ellipsis, { minChar: style.truncateMinChar });
|
||
|
token.width = token.contentWidth = getWidth(token.text, font);
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
token.contentWidth = getWidth(token.text, font);
|
||
|
}
|
||
|
}
|
||
|
token.width += paddingH;
|
||
|
lineWidth += token.width;
|
||
|
tokenStyle && (lineHeight = Math.max(lineHeight, token.lineHeight));
|
||
|
}
|
||
|
finishLine(line, lineWidth, lineHeight);
|
||
|
}
|
||
|
contentBlock.outerWidth = contentBlock.width = retrieve2(topWidth, calculatedWidth);
|
||
|
contentBlock.outerHeight = contentBlock.height = retrieve2(topHeight, calculatedHeight);
|
||
|
contentBlock.contentHeight = calculatedHeight;
|
||
|
contentBlock.contentWidth = calculatedWidth;
|
||
|
if (stlPadding) {
|
||
|
contentBlock.outerWidth += stlPadding[1] + stlPadding[3];
|
||
|
contentBlock.outerHeight += stlPadding[0] + stlPadding[2];
|
||
|
}
|
||
|
for (var i = 0; i < pendingList.length; i++) {
|
||
|
var token = pendingList[i];
|
||
|
var percentWidth = token.percentWidth;
|
||
|
token.width = parseInt(percentWidth, 10) / 100 * contentBlock.width;
|
||
|
}
|
||
|
return contentBlock;
|
||
|
}
|
||
|
function pushTokens(block, str, style, wrapInfo, styleName) {
|
||
|
var isEmptyStr = str === '';
|
||
|
var tokenStyle = styleName && style.rich[styleName] || {};
|
||
|
var lines = block.lines;
|
||
|
var font = tokenStyle.font || style.font;
|
||
|
var newLine = false;
|
||
|
var strLines;
|
||
|
var linesWidths;
|
||
|
if (wrapInfo) {
|
||
|
var tokenPadding = tokenStyle.padding;
|
||
|
var tokenPaddingH = tokenPadding ? tokenPadding[1] + tokenPadding[3] : 0;
|
||
|
if (tokenStyle.width != null && tokenStyle.width !== 'auto') {
|
||
|
var outerWidth_1 = parsePercent(tokenStyle.width, wrapInfo.width) + tokenPaddingH;
|
||
|
if (lines.length > 0) {
|
||
|
if (outerWidth_1 + wrapInfo.accumWidth > wrapInfo.width) {
|
||
|
strLines = str.split('\n');
|
||
|
newLine = true;
|
||
|
}
|
||
|
}
|
||
|
wrapInfo.accumWidth = outerWidth_1;
|
||
|
}
|
||
|
else {
|
||
|
var res = wrapText(str, font, wrapInfo.width, wrapInfo.breakAll, wrapInfo.accumWidth);
|
||
|
wrapInfo.accumWidth = res.accumWidth + tokenPaddingH;
|
||
|
linesWidths = res.linesWidths;
|
||
|
strLines = res.lines;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
strLines = str.split('\n');
|
||
|
}
|
||
|
for (var i = 0; i < strLines.length; i++) {
|
||
|
var text = strLines[i];
|
||
|
var token = new RichTextToken();
|
||
|
token.styleName = styleName;
|
||
|
token.text = text;
|
||
|
token.isLineHolder = !text && !isEmptyStr;
|
||
|
if (typeof tokenStyle.width === 'number') {
|
||
|
token.width = tokenStyle.width;
|
||
|
}
|
||
|
else {
|
||
|
token.width = linesWidths
|
||
|
? linesWidths[i]
|
||
|
: getWidth(text, font);
|
||
|
}
|
||
|
if (!i && !newLine) {
|
||
|
var tokens = (lines[lines.length - 1] || (lines[0] = new RichTextLine())).tokens;
|
||
|
var tokensLen = tokens.length;
|
||
|
(tokensLen === 1 && tokens[0].isLineHolder)
|
||
|
? (tokens[0] = token)
|
||
|
: ((text || !tokensLen || isEmptyStr) && tokens.push(token));
|
||
|
}
|
||
|
else {
|
||
|
lines.push(new RichTextLine([token]));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
function isAlphabeticLetter(ch) {
|
||
|
var code = ch.charCodeAt(0);
|
||
|
return code >= 0x20 && code <= 0x24F
|
||
|
|| code >= 0x370 && code <= 0x10FF
|
||
|
|| code >= 0x1200 && code <= 0x13FF
|
||
|
|| code >= 0x1E00 && code <= 0x206F;
|
||
|
}
|
||
|
var breakCharMap = reduce(',&?/;] '.split(''), function (obj, ch) {
|
||
|
obj[ch] = true;
|
||
|
return obj;
|
||
|
}, {});
|
||
|
function isWordBreakChar(ch) {
|
||
|
if (isAlphabeticLetter(ch)) {
|
||
|
if (breakCharMap[ch]) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
function wrapText(text, font, lineWidth, isBreakAll, lastAccumWidth) {
|
||
|
var lines = [];
|
||
|
var linesWidths = [];
|
||
|
var line = '';
|
||
|
var currentWord = '';
|
||
|
var currentWordWidth = 0;
|
||
|
var accumWidth = 0;
|
||
|
for (var i = 0; i < text.length; i++) {
|
||
|
var ch = text.charAt(i);
|
||
|
if (ch === '\n') {
|
||
|
if (currentWord) {
|
||
|
line += currentWord;
|
||
|
accumWidth += currentWordWidth;
|
||
|
}
|
||
|
lines.push(line);
|
||
|
linesWidths.push(accumWidth);
|
||
|
line = '';
|
||
|
currentWord = '';
|
||
|
currentWordWidth = 0;
|
||
|
accumWidth = 0;
|
||
|
continue;
|
||
|
}
|
||
|
var chWidth = getWidth(ch, font);
|
||
|
var inWord = isBreakAll ? false : !isWordBreakChar(ch);
|
||
|
if (!lines.length
|
||
|
? lastAccumWidth + accumWidth + chWidth > lineWidth
|
||
|
: accumWidth + chWidth > lineWidth) {
|
||
|
if (!accumWidth) {
|
||
|
if (inWord) {
|
||
|
lines.push(currentWord);
|
||
|
linesWidths.push(currentWordWidth);
|
||
|
currentWord = ch;
|
||
|
currentWordWidth = chWidth;
|
||
|
}
|
||
|
else {
|
||
|
lines.push(ch);
|
||
|
linesWidths.push(chWidth);
|
||
|
}
|
||
|
}
|
||
|
else if (line || currentWord) {
|
||
|
if (inWord) {
|
||
|
if (!line) {
|
||
|
line = currentWord;
|
||
|
currentWord = '';
|
||
|
currentWordWidth = 0;
|
||
|
accumWidth = currentWordWidth;
|
||
|
}
|
||
|
lines.push(line);
|
||
|
linesWidths.push(accumWidth - currentWordWidth);
|
||
|
currentWord += ch;
|
||
|
currentWordWidth += chWidth;
|
||
|
line = '';
|
||
|
accumWidth = currentWordWidth;
|
||
|
}
|
||
|
else {
|
||
|
if (currentWord) {
|
||
|
line += currentWord;
|
||
|
currentWord = '';
|
||
|
currentWordWidth = 0;
|
||
|
}
|
||
|
lines.push(line);
|
||
|
linesWidths.push(accumWidth);
|
||
|
line = ch;
|
||
|
accumWidth = chWidth;
|
||
|
}
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
accumWidth += chWidth;
|
||
|
if (inWord) {
|
||
|
currentWord += ch;
|
||
|
currentWordWidth += chWidth;
|
||
|
}
|
||
|
else {
|
||
|
if (currentWord) {
|
||
|
line += currentWord;
|
||
|
currentWord = '';
|
||
|
currentWordWidth = 0;
|
||
|
}
|
||
|
line += ch;
|
||
|
}
|
||
|
}
|
||
|
if (!lines.length && !line) {
|
||
|
line = text;
|
||
|
currentWord = '';
|
||
|
currentWordWidth = 0;
|
||
|
}
|
||
|
if (currentWord) {
|
||
|
line += currentWord;
|
||
|
}
|
||
|
if (line) {
|
||
|
lines.push(line);
|
||
|
linesWidths.push(accumWidth);
|
||
|
}
|
||
|
if (lines.length === 1) {
|
||
|
accumWidth += lastAccumWidth;
|
||
|
}
|
||
|
return {
|
||
|
accumWidth: accumWidth,
|
||
|
lines: lines,
|
||
|
linesWidths: linesWidths
|
||
|
};
|
||
|
}
|