parseText.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. import * as imageHelper from '../helper/image.js';
  2. import { extend, retrieve2, retrieve3, reduce } from '../../core/util.js';
  3. import { getLineHeight, getWidth, parsePercent } from '../../contain/text.js';
  4. var STYLE_REG = /\{([a-zA-Z0-9_]+)\|([^}]*)\}/g;
  5. export function truncateText(text, containerWidth, font, ellipsis, options) {
  6. if (!containerWidth) {
  7. return '';
  8. }
  9. var textLines = (text + '').split('\n');
  10. options = prepareTruncateOptions(containerWidth, font, ellipsis, options);
  11. for (var i = 0, len = textLines.length; i < len; i++) {
  12. textLines[i] = truncateSingleLine(textLines[i], options);
  13. }
  14. return textLines.join('\n');
  15. }
  16. function prepareTruncateOptions(containerWidth, font, ellipsis, options) {
  17. options = options || {};
  18. var preparedOpts = extend({}, options);
  19. preparedOpts.font = font;
  20. ellipsis = retrieve2(ellipsis, '...');
  21. preparedOpts.maxIterations = retrieve2(options.maxIterations, 2);
  22. var minChar = preparedOpts.minChar = retrieve2(options.minChar, 0);
  23. preparedOpts.cnCharWidth = getWidth('国', font);
  24. var ascCharWidth = preparedOpts.ascCharWidth = getWidth('a', font);
  25. preparedOpts.placeholder = retrieve2(options.placeholder, '');
  26. var contentWidth = containerWidth = Math.max(0, containerWidth - 1);
  27. for (var i = 0; i < minChar && contentWidth >= ascCharWidth; i++) {
  28. contentWidth -= ascCharWidth;
  29. }
  30. var ellipsisWidth = getWidth(ellipsis, font);
  31. if (ellipsisWidth > contentWidth) {
  32. ellipsis = '';
  33. ellipsisWidth = 0;
  34. }
  35. contentWidth = containerWidth - ellipsisWidth;
  36. preparedOpts.ellipsis = ellipsis;
  37. preparedOpts.ellipsisWidth = ellipsisWidth;
  38. preparedOpts.contentWidth = contentWidth;
  39. preparedOpts.containerWidth = containerWidth;
  40. return preparedOpts;
  41. }
  42. function truncateSingleLine(textLine, options) {
  43. var containerWidth = options.containerWidth;
  44. var font = options.font;
  45. var contentWidth = options.contentWidth;
  46. if (!containerWidth) {
  47. return '';
  48. }
  49. var lineWidth = getWidth(textLine, font);
  50. if (lineWidth <= containerWidth) {
  51. return textLine;
  52. }
  53. for (var j = 0;; j++) {
  54. if (lineWidth <= contentWidth || j >= options.maxIterations) {
  55. textLine += options.ellipsis;
  56. break;
  57. }
  58. var subLength = j === 0
  59. ? estimateLength(textLine, contentWidth, options.ascCharWidth, options.cnCharWidth)
  60. : lineWidth > 0
  61. ? Math.floor(textLine.length * contentWidth / lineWidth)
  62. : 0;
  63. textLine = textLine.substr(0, subLength);
  64. lineWidth = getWidth(textLine, font);
  65. }
  66. if (textLine === '') {
  67. textLine = options.placeholder;
  68. }
  69. return textLine;
  70. }
  71. function estimateLength(text, contentWidth, ascCharWidth, cnCharWidth) {
  72. var width = 0;
  73. var i = 0;
  74. for (var len = text.length; i < len && width < contentWidth; i++) {
  75. var charCode = text.charCodeAt(i);
  76. width += (0 <= charCode && charCode <= 127) ? ascCharWidth : cnCharWidth;
  77. }
  78. return i;
  79. }
  80. export function parsePlainText(text, style) {
  81. text != null && (text += '');
  82. var overflow = style.overflow;
  83. var padding = style.padding;
  84. var font = style.font;
  85. var truncate = overflow === 'truncate';
  86. var calculatedLineHeight = getLineHeight(font);
  87. var lineHeight = retrieve2(style.lineHeight, calculatedLineHeight);
  88. var bgColorDrawn = !!(style.backgroundColor);
  89. var truncateLineOverflow = style.lineOverflow === 'truncate';
  90. var width = style.width;
  91. var lines;
  92. if (width != null && (overflow === 'break' || overflow === 'breakAll')) {
  93. lines = text ? wrapText(text, style.font, width, overflow === 'breakAll', 0).lines : [];
  94. }
  95. else {
  96. lines = text ? text.split('\n') : [];
  97. }
  98. var contentHeight = lines.length * lineHeight;
  99. var height = retrieve2(style.height, contentHeight);
  100. if (contentHeight > height && truncateLineOverflow) {
  101. var lineCount = Math.floor(height / lineHeight);
  102. lines = lines.slice(0, lineCount);
  103. }
  104. if (text && truncate && width != null) {
  105. var options = prepareTruncateOptions(width, font, style.ellipsis, {
  106. minChar: style.truncateMinChar,
  107. placeholder: style.placeholder
  108. });
  109. for (var i = 0; i < lines.length; i++) {
  110. lines[i] = truncateSingleLine(lines[i], options);
  111. }
  112. }
  113. var outerHeight = height;
  114. var contentWidth = 0;
  115. for (var i = 0; i < lines.length; i++) {
  116. contentWidth = Math.max(getWidth(lines[i], font), contentWidth);
  117. }
  118. if (width == null) {
  119. width = contentWidth;
  120. }
  121. var outerWidth = contentWidth;
  122. if (padding) {
  123. outerHeight += padding[0] + padding[2];
  124. outerWidth += padding[1] + padding[3];
  125. width += padding[1] + padding[3];
  126. }
  127. if (bgColorDrawn) {
  128. outerWidth = width;
  129. }
  130. return {
  131. lines: lines,
  132. height: height,
  133. outerWidth: outerWidth,
  134. outerHeight: outerHeight,
  135. lineHeight: lineHeight,
  136. calculatedLineHeight: calculatedLineHeight,
  137. contentWidth: contentWidth,
  138. contentHeight: contentHeight,
  139. width: width
  140. };
  141. }
  142. var RichTextToken = (function () {
  143. function RichTextToken() {
  144. }
  145. return RichTextToken;
  146. }());
  147. var RichTextLine = (function () {
  148. function RichTextLine(tokens) {
  149. this.tokens = [];
  150. if (tokens) {
  151. this.tokens = tokens;
  152. }
  153. }
  154. return RichTextLine;
  155. }());
  156. var RichTextContentBlock = (function () {
  157. function RichTextContentBlock() {
  158. this.width = 0;
  159. this.height = 0;
  160. this.contentWidth = 0;
  161. this.contentHeight = 0;
  162. this.outerWidth = 0;
  163. this.outerHeight = 0;
  164. this.lines = [];
  165. }
  166. return RichTextContentBlock;
  167. }());
  168. export { RichTextContentBlock };
  169. export function parseRichText(text, style) {
  170. var contentBlock = new RichTextContentBlock();
  171. text != null && (text += '');
  172. if (!text) {
  173. return contentBlock;
  174. }
  175. var topWidth = style.width;
  176. var topHeight = style.height;
  177. var overflow = style.overflow;
  178. var wrapInfo = (overflow === 'break' || overflow === 'breakAll') && topWidth != null
  179. ? { width: topWidth, accumWidth: 0, breakAll: overflow === 'breakAll' }
  180. : null;
  181. var lastIndex = STYLE_REG.lastIndex = 0;
  182. var result;
  183. while ((result = STYLE_REG.exec(text)) != null) {
  184. var matchedIndex = result.index;
  185. if (matchedIndex > lastIndex) {
  186. pushTokens(contentBlock, text.substring(lastIndex, matchedIndex), style, wrapInfo);
  187. }
  188. pushTokens(contentBlock, result[2], style, wrapInfo, result[1]);
  189. lastIndex = STYLE_REG.lastIndex;
  190. }
  191. if (lastIndex < text.length) {
  192. pushTokens(contentBlock, text.substring(lastIndex, text.length), style, wrapInfo);
  193. }
  194. var pendingList = [];
  195. var calculatedHeight = 0;
  196. var calculatedWidth = 0;
  197. var stlPadding = style.padding;
  198. var truncate = overflow === 'truncate';
  199. var truncateLine = style.lineOverflow === 'truncate';
  200. function finishLine(line, lineWidth, lineHeight) {
  201. line.width = lineWidth;
  202. line.lineHeight = lineHeight;
  203. calculatedHeight += lineHeight;
  204. calculatedWidth = Math.max(calculatedWidth, lineWidth);
  205. }
  206. outer: for (var i = 0; i < contentBlock.lines.length; i++) {
  207. var line = contentBlock.lines[i];
  208. var lineHeight = 0;
  209. var lineWidth = 0;
  210. for (var j = 0; j < line.tokens.length; j++) {
  211. var token = line.tokens[j];
  212. var tokenStyle = token.styleName && style.rich[token.styleName] || {};
  213. var textPadding = token.textPadding = tokenStyle.padding;
  214. var paddingH = textPadding ? textPadding[1] + textPadding[3] : 0;
  215. var font = token.font = tokenStyle.font || style.font;
  216. token.contentHeight = getLineHeight(font);
  217. var tokenHeight = retrieve2(tokenStyle.height, token.contentHeight);
  218. token.innerHeight = tokenHeight;
  219. textPadding && (tokenHeight += textPadding[0] + textPadding[2]);
  220. token.height = tokenHeight;
  221. token.lineHeight = retrieve3(tokenStyle.lineHeight, style.lineHeight, tokenHeight);
  222. token.align = tokenStyle && tokenStyle.align || style.align;
  223. token.verticalAlign = tokenStyle && tokenStyle.verticalAlign || 'middle';
  224. if (truncateLine && topHeight != null && calculatedHeight + token.lineHeight > topHeight) {
  225. if (j > 0) {
  226. line.tokens = line.tokens.slice(0, j);
  227. finishLine(line, lineWidth, lineHeight);
  228. contentBlock.lines = contentBlock.lines.slice(0, i + 1);
  229. }
  230. else {
  231. contentBlock.lines = contentBlock.lines.slice(0, i);
  232. }
  233. break outer;
  234. }
  235. var styleTokenWidth = tokenStyle.width;
  236. var tokenWidthNotSpecified = styleTokenWidth == null || styleTokenWidth === 'auto';
  237. if (typeof styleTokenWidth === 'string' && styleTokenWidth.charAt(styleTokenWidth.length - 1) === '%') {
  238. token.percentWidth = styleTokenWidth;
  239. pendingList.push(token);
  240. token.contentWidth = getWidth(token.text, font);
  241. }
  242. else {
  243. if (tokenWidthNotSpecified) {
  244. var textBackgroundColor = tokenStyle.backgroundColor;
  245. var bgImg = textBackgroundColor && textBackgroundColor.image;
  246. if (bgImg) {
  247. bgImg = imageHelper.findExistImage(bgImg);
  248. if (imageHelper.isImageReady(bgImg)) {
  249. token.width = Math.max(token.width, bgImg.width * tokenHeight / bgImg.height);
  250. }
  251. }
  252. }
  253. var remainTruncWidth = truncate && topWidth != null
  254. ? topWidth - lineWidth : null;
  255. if (remainTruncWidth != null && remainTruncWidth < token.width) {
  256. if (!tokenWidthNotSpecified || remainTruncWidth < paddingH) {
  257. token.text = '';
  258. token.width = token.contentWidth = 0;
  259. }
  260. else {
  261. token.text = truncateText(token.text, remainTruncWidth - paddingH, font, style.ellipsis, { minChar: style.truncateMinChar });
  262. token.width = token.contentWidth = getWidth(token.text, font);
  263. }
  264. }
  265. else {
  266. token.contentWidth = getWidth(token.text, font);
  267. }
  268. }
  269. token.width += paddingH;
  270. lineWidth += token.width;
  271. tokenStyle && (lineHeight = Math.max(lineHeight, token.lineHeight));
  272. }
  273. finishLine(line, lineWidth, lineHeight);
  274. }
  275. contentBlock.outerWidth = contentBlock.width = retrieve2(topWidth, calculatedWidth);
  276. contentBlock.outerHeight = contentBlock.height = retrieve2(topHeight, calculatedHeight);
  277. contentBlock.contentHeight = calculatedHeight;
  278. contentBlock.contentWidth = calculatedWidth;
  279. if (stlPadding) {
  280. contentBlock.outerWidth += stlPadding[1] + stlPadding[3];
  281. contentBlock.outerHeight += stlPadding[0] + stlPadding[2];
  282. }
  283. for (var i = 0; i < pendingList.length; i++) {
  284. var token = pendingList[i];
  285. var percentWidth = token.percentWidth;
  286. token.width = parseInt(percentWidth, 10) / 100 * contentBlock.width;
  287. }
  288. return contentBlock;
  289. }
  290. function pushTokens(block, str, style, wrapInfo, styleName) {
  291. var isEmptyStr = str === '';
  292. var tokenStyle = styleName && style.rich[styleName] || {};
  293. var lines = block.lines;
  294. var font = tokenStyle.font || style.font;
  295. var newLine = false;
  296. var strLines;
  297. var linesWidths;
  298. if (wrapInfo) {
  299. var tokenPadding = tokenStyle.padding;
  300. var tokenPaddingH = tokenPadding ? tokenPadding[1] + tokenPadding[3] : 0;
  301. if (tokenStyle.width != null && tokenStyle.width !== 'auto') {
  302. var outerWidth_1 = parsePercent(tokenStyle.width, wrapInfo.width) + tokenPaddingH;
  303. if (lines.length > 0) {
  304. if (outerWidth_1 + wrapInfo.accumWidth > wrapInfo.width) {
  305. strLines = str.split('\n');
  306. newLine = true;
  307. }
  308. }
  309. wrapInfo.accumWidth = outerWidth_1;
  310. }
  311. else {
  312. var res = wrapText(str, font, wrapInfo.width, wrapInfo.breakAll, wrapInfo.accumWidth);
  313. wrapInfo.accumWidth = res.accumWidth + tokenPaddingH;
  314. linesWidths = res.linesWidths;
  315. strLines = res.lines;
  316. }
  317. }
  318. else {
  319. strLines = str.split('\n');
  320. }
  321. for (var i = 0; i < strLines.length; i++) {
  322. var text = strLines[i];
  323. var token = new RichTextToken();
  324. token.styleName = styleName;
  325. token.text = text;
  326. token.isLineHolder = !text && !isEmptyStr;
  327. if (typeof tokenStyle.width === 'number') {
  328. token.width = tokenStyle.width;
  329. }
  330. else {
  331. token.width = linesWidths
  332. ? linesWidths[i]
  333. : getWidth(text, font);
  334. }
  335. if (!i && !newLine) {
  336. var tokens = (lines[lines.length - 1] || (lines[0] = new RichTextLine())).tokens;
  337. var tokensLen = tokens.length;
  338. (tokensLen === 1 && tokens[0].isLineHolder)
  339. ? (tokens[0] = token)
  340. : ((text || !tokensLen || isEmptyStr) && tokens.push(token));
  341. }
  342. else {
  343. lines.push(new RichTextLine([token]));
  344. }
  345. }
  346. }
  347. function isLatin(ch) {
  348. var code = ch.charCodeAt(0);
  349. return code >= 0x21 && code <= 0x17F;
  350. }
  351. var breakCharMap = reduce(',&?/;] '.split(''), function (obj, ch) {
  352. obj[ch] = true;
  353. return obj;
  354. }, {});
  355. function isWordBreakChar(ch) {
  356. if (isLatin(ch)) {
  357. if (breakCharMap[ch]) {
  358. return true;
  359. }
  360. return false;
  361. }
  362. return true;
  363. }
  364. function wrapText(text, font, lineWidth, isBreakAll, lastAccumWidth) {
  365. var lines = [];
  366. var linesWidths = [];
  367. var line = '';
  368. var currentWord = '';
  369. var currentWordWidth = 0;
  370. var accumWidth = 0;
  371. for (var i = 0; i < text.length; i++) {
  372. var ch = text.charAt(i);
  373. if (ch === '\n') {
  374. if (currentWord) {
  375. line += currentWord;
  376. accumWidth += currentWordWidth;
  377. }
  378. lines.push(line);
  379. linesWidths.push(accumWidth);
  380. line = '';
  381. currentWord = '';
  382. currentWordWidth = 0;
  383. accumWidth = 0;
  384. continue;
  385. }
  386. var chWidth = getWidth(ch, font);
  387. var inWord = isBreakAll ? false : !isWordBreakChar(ch);
  388. if (!lines.length
  389. ? lastAccumWidth + accumWidth + chWidth > lineWidth
  390. : accumWidth + chWidth > lineWidth) {
  391. if (!accumWidth) {
  392. if (inWord) {
  393. lines.push(currentWord);
  394. linesWidths.push(currentWordWidth);
  395. currentWord = ch;
  396. currentWordWidth = chWidth;
  397. }
  398. else {
  399. lines.push(ch);
  400. linesWidths.push(chWidth);
  401. }
  402. }
  403. else if (line || currentWord) {
  404. if (inWord) {
  405. if (!line) {
  406. line = currentWord;
  407. currentWord = '';
  408. currentWordWidth = 0;
  409. accumWidth = currentWordWidth;
  410. }
  411. lines.push(line);
  412. linesWidths.push(accumWidth - currentWordWidth);
  413. currentWord += ch;
  414. currentWordWidth += chWidth;
  415. line = '';
  416. accumWidth = currentWordWidth;
  417. }
  418. else {
  419. if (currentWord) {
  420. line += currentWord;
  421. currentWord = '';
  422. currentWordWidth = 0;
  423. }
  424. lines.push(line);
  425. linesWidths.push(accumWidth);
  426. line = ch;
  427. accumWidth = chWidth;
  428. }
  429. }
  430. continue;
  431. }
  432. accumWidth += chWidth;
  433. if (inWord) {
  434. currentWord += ch;
  435. currentWordWidth += chWidth;
  436. }
  437. else {
  438. if (currentWord) {
  439. line += currentWord;
  440. currentWord = '';
  441. currentWordWidth = 0;
  442. }
  443. line += ch;
  444. }
  445. }
  446. if (!lines.length && !line) {
  447. line = text;
  448. currentWord = '';
  449. currentWordWidth = 0;
  450. }
  451. if (currentWord) {
  452. line += currentWord;
  453. }
  454. if (line) {
  455. lines.push(line);
  456. linesWidths.push(accumWidth);
  457. }
  458. if (lines.length === 1) {
  459. accumWidth += lastAccumWidth;
  460. }
  461. return {
  462. accumWidth: accumWidth,
  463. lines: lines,
  464. linesWidths: linesWidths
  465. };
  466. }