text.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719
  1. var BoundingRect = require("../core/BoundingRect");
  2. var imageHelper = require("../graphic/helper/image");
  3. var _util = require("../core/util");
  4. var getContext = _util.getContext;
  5. var extend = _util.extend;
  6. var retrieve2 = _util.retrieve2;
  7. var retrieve3 = _util.retrieve3;
  8. var trim = _util.trim;
  9. var textWidthCache = {};
  10. var textWidthCacheCounter = 0;
  11. var TEXT_CACHE_MAX = 5000;
  12. var STYLE_REG = /\{([a-zA-Z0-9_]+)\|([^}]*)\}/g;
  13. var DEFAULT_FONT = '12px sans-serif'; // Avoid assign to an exported variable, for transforming to cjs.
  14. var methods = {};
  15. function $override(name, fn) {
  16. methods[name] = fn;
  17. }
  18. /**
  19. * @public
  20. * @param {string} text
  21. * @param {string} font
  22. * @return {number} width
  23. */
  24. function getWidth(text, font) {
  25. font = font || DEFAULT_FONT;
  26. var key = text + ':' + font;
  27. if (textWidthCache[key]) {
  28. return textWidthCache[key];
  29. }
  30. var textLines = (text + '').split('\n');
  31. var width = 0;
  32. for (var i = 0, l = textLines.length; i < l; i++) {
  33. // textContain.measureText may be overrided in SVG or VML
  34. width = Math.max(measureText(textLines[i], font).width, width);
  35. }
  36. if (textWidthCacheCounter > TEXT_CACHE_MAX) {
  37. textWidthCacheCounter = 0;
  38. textWidthCache = {};
  39. }
  40. textWidthCacheCounter++;
  41. textWidthCache[key] = width;
  42. return width;
  43. }
  44. /**
  45. * @public
  46. * @param {string} text
  47. * @param {string} font
  48. * @param {string} [textAlign='left']
  49. * @param {string} [textVerticalAlign='top']
  50. * @param {Array.<number>} [textPadding]
  51. * @param {Object} [rich]
  52. * @param {Object} [truncate]
  53. * @return {Object} {x, y, width, height, lineHeight}
  54. */
  55. function getBoundingRect(text, font, textAlign, textVerticalAlign, textPadding, textLineHeight, rich, truncate) {
  56. return rich ? getRichTextRect(text, font, textAlign, textVerticalAlign, textPadding, textLineHeight, rich, truncate) : getPlainTextRect(text, font, textAlign, textVerticalAlign, textPadding, textLineHeight, truncate);
  57. }
  58. function getPlainTextRect(text, font, textAlign, textVerticalAlign, textPadding, textLineHeight, truncate) {
  59. var contentBlock = parsePlainText(text, font, textPadding, textLineHeight, truncate);
  60. var outerWidth = getWidth(text, font);
  61. if (textPadding) {
  62. outerWidth += textPadding[1] + textPadding[3];
  63. }
  64. var outerHeight = contentBlock.outerHeight;
  65. var x = adjustTextX(0, outerWidth, textAlign);
  66. var y = adjustTextY(0, outerHeight, textVerticalAlign);
  67. var rect = new BoundingRect(x, y, outerWidth, outerHeight);
  68. rect.lineHeight = contentBlock.lineHeight;
  69. return rect;
  70. }
  71. function getRichTextRect(text, font, textAlign, textVerticalAlign, textPadding, textLineHeight, rich, truncate) {
  72. var contentBlock = parseRichText(text, {
  73. rich: rich,
  74. truncate: truncate,
  75. font: font,
  76. textAlign: textAlign,
  77. textPadding: textPadding,
  78. textLineHeight: textLineHeight
  79. });
  80. var outerWidth = contentBlock.outerWidth;
  81. var outerHeight = contentBlock.outerHeight;
  82. var x = adjustTextX(0, outerWidth, textAlign);
  83. var y = adjustTextY(0, outerHeight, textVerticalAlign);
  84. return new BoundingRect(x, y, outerWidth, outerHeight);
  85. }
  86. /**
  87. * @public
  88. * @param {number} x
  89. * @param {number} width
  90. * @param {string} [textAlign='left']
  91. * @return {number} Adjusted x.
  92. */
  93. function adjustTextX(x, width, textAlign) {
  94. // FIXME Right to left language
  95. if (textAlign === 'right') {
  96. x -= width;
  97. } else if (textAlign === 'center') {
  98. x -= width / 2;
  99. }
  100. return x;
  101. }
  102. /**
  103. * @public
  104. * @param {number} y
  105. * @param {number} height
  106. * @param {string} [textVerticalAlign='top']
  107. * @return {number} Adjusted y.
  108. */
  109. function adjustTextY(y, height, textVerticalAlign) {
  110. if (textVerticalAlign === 'middle') {
  111. y -= height / 2;
  112. } else if (textVerticalAlign === 'bottom') {
  113. y -= height;
  114. }
  115. return y;
  116. }
  117. /**
  118. * Follow same interface to `Displayable.prototype.calculateTextPosition`.
  119. * @public
  120. * @param {Obejct} [out] Prepared out object. If not input, auto created in the method.
  121. * @param {module:zrender/graphic/Style} style where `textPosition` and `textDistance` are visited.
  122. * @param {Object} rect {x, y, width, height} Rect of the host elment, according to which the text positioned.
  123. * @return {Object} The input `out`. Set: {x, y, textAlign, textVerticalAlign}
  124. */
  125. function calculateTextPosition(out, style, rect) {
  126. var textPosition = style.textPosition;
  127. var distance = style.textDistance;
  128. var x = rect.x;
  129. var y = rect.y;
  130. distance = distance || 0;
  131. var height = rect.height;
  132. var width = rect.width;
  133. var halfHeight = height / 2;
  134. var textAlign = 'left';
  135. var textVerticalAlign = 'top';
  136. switch (textPosition) {
  137. case 'left':
  138. x -= distance;
  139. y += halfHeight;
  140. textAlign = 'right';
  141. textVerticalAlign = 'middle';
  142. break;
  143. case 'right':
  144. x += distance + width;
  145. y += halfHeight;
  146. textVerticalAlign = 'middle';
  147. break;
  148. case 'top':
  149. x += width / 2;
  150. y -= distance;
  151. textAlign = 'center';
  152. textVerticalAlign = 'bottom';
  153. break;
  154. case 'bottom':
  155. x += width / 2;
  156. y += height + distance;
  157. textAlign = 'center';
  158. break;
  159. case 'inside':
  160. x += width / 2;
  161. y += halfHeight;
  162. textAlign = 'center';
  163. textVerticalAlign = 'middle';
  164. break;
  165. case 'insideLeft':
  166. x += distance;
  167. y += halfHeight;
  168. textVerticalAlign = 'middle';
  169. break;
  170. case 'insideRight':
  171. x += width - distance;
  172. y += halfHeight;
  173. textAlign = 'right';
  174. textVerticalAlign = 'middle';
  175. break;
  176. case 'insideTop':
  177. x += width / 2;
  178. y += distance;
  179. textAlign = 'center';
  180. break;
  181. case 'insideBottom':
  182. x += width / 2;
  183. y += height - distance;
  184. textAlign = 'center';
  185. textVerticalAlign = 'bottom';
  186. break;
  187. case 'insideTopLeft':
  188. x += distance;
  189. y += distance;
  190. break;
  191. case 'insideTopRight':
  192. x += width - distance;
  193. y += distance;
  194. textAlign = 'right';
  195. break;
  196. case 'insideBottomLeft':
  197. x += distance;
  198. y += height - distance;
  199. textVerticalAlign = 'bottom';
  200. break;
  201. case 'insideBottomRight':
  202. x += width - distance;
  203. y += height - distance;
  204. textAlign = 'right';
  205. textVerticalAlign = 'bottom';
  206. break;
  207. }
  208. out = out || {};
  209. out.x = x;
  210. out.y = y;
  211. out.textAlign = textAlign;
  212. out.textVerticalAlign = textVerticalAlign;
  213. return out;
  214. }
  215. /**
  216. * To be removed. But still do not remove in case that some one has imported it.
  217. * @deprecated
  218. * @public
  219. * @param {stirng} textPosition
  220. * @param {Object} rect {x, y, width, height}
  221. * @param {number} distance
  222. * @return {Object} {x, y, textAlign, textVerticalAlign}
  223. */
  224. function adjustTextPositionOnRect(textPosition, rect, distance) {
  225. var dummyStyle = {
  226. textPosition: textPosition,
  227. textDistance: distance
  228. };
  229. return calculateTextPosition({}, dummyStyle, rect);
  230. }
  231. /**
  232. * Show ellipsis if overflow.
  233. *
  234. * @public
  235. * @param {string} text
  236. * @param {string} containerWidth
  237. * @param {string} font
  238. * @param {number} [ellipsis='...']
  239. * @param {Object} [options]
  240. * @param {number} [options.maxIterations=3]
  241. * @param {number} [options.minChar=0] If truncate result are less
  242. * then minChar, ellipsis will not show, which is
  243. * better for user hint in some cases.
  244. * @param {number} [options.placeholder=''] When all truncated, use the placeholder.
  245. * @return {string}
  246. */
  247. function truncateText(text, containerWidth, font, ellipsis, options) {
  248. if (!containerWidth) {
  249. return '';
  250. }
  251. var textLines = (text + '').split('\n');
  252. options = prepareTruncateOptions(containerWidth, font, ellipsis, options); // FIXME
  253. // It is not appropriate that every line has '...' when truncate multiple lines.
  254. for (var i = 0, len = textLines.length; i < len; i++) {
  255. textLines[i] = truncateSingleLine(textLines[i], options);
  256. }
  257. return textLines.join('\n');
  258. }
  259. function prepareTruncateOptions(containerWidth, font, ellipsis, options) {
  260. options = extend({}, options);
  261. options.font = font;
  262. var ellipsis = retrieve2(ellipsis, '...');
  263. options.maxIterations = retrieve2(options.maxIterations, 2);
  264. var minChar = options.minChar = retrieve2(options.minChar, 0); // FIXME
  265. // Other languages?
  266. options.cnCharWidth = getWidth('国', font); // FIXME
  267. // Consider proportional font?
  268. var ascCharWidth = options.ascCharWidth = getWidth('a', font);
  269. options.placeholder = retrieve2(options.placeholder, ''); // Example 1: minChar: 3, text: 'asdfzxcv', truncate result: 'asdf', but not: 'a...'.
  270. // Example 2: minChar: 3, text: '维度', truncate result: '维', but not: '...'.
  271. var contentWidth = containerWidth = Math.max(0, containerWidth - 1); // Reserve some gap.
  272. for (var i = 0; i < minChar && contentWidth >= ascCharWidth; i++) {
  273. contentWidth -= ascCharWidth;
  274. }
  275. var ellipsisWidth = getWidth(ellipsis, font);
  276. if (ellipsisWidth > contentWidth) {
  277. ellipsis = '';
  278. ellipsisWidth = 0;
  279. }
  280. contentWidth = containerWidth - ellipsisWidth;
  281. options.ellipsis = ellipsis;
  282. options.ellipsisWidth = ellipsisWidth;
  283. options.contentWidth = contentWidth;
  284. options.containerWidth = containerWidth;
  285. return options;
  286. }
  287. function truncateSingleLine(textLine, options) {
  288. var containerWidth = options.containerWidth;
  289. var font = options.font;
  290. var contentWidth = options.contentWidth;
  291. if (!containerWidth) {
  292. return '';
  293. }
  294. var lineWidth = getWidth(textLine, font);
  295. if (lineWidth <= containerWidth) {
  296. return textLine;
  297. }
  298. for (var j = 0;; j++) {
  299. if (lineWidth <= contentWidth || j >= options.maxIterations) {
  300. textLine += options.ellipsis;
  301. break;
  302. }
  303. var subLength = j === 0 ? estimateLength(textLine, contentWidth, options.ascCharWidth, options.cnCharWidth) : lineWidth > 0 ? Math.floor(textLine.length * contentWidth / lineWidth) : 0;
  304. textLine = textLine.substr(0, subLength);
  305. lineWidth = getWidth(textLine, font);
  306. }
  307. if (textLine === '') {
  308. textLine = options.placeholder;
  309. }
  310. return textLine;
  311. }
  312. function estimateLength(text, contentWidth, ascCharWidth, cnCharWidth) {
  313. var width = 0;
  314. var i = 0;
  315. for (var len = text.length; i < len && width < contentWidth; i++) {
  316. var charCode = text.charCodeAt(i);
  317. width += 0 <= charCode && charCode <= 127 ? ascCharWidth : cnCharWidth;
  318. }
  319. return i;
  320. }
  321. /**
  322. * @public
  323. * @param {string} font
  324. * @return {number} line height
  325. */
  326. function getLineHeight(font) {
  327. // FIXME A rough approach.
  328. return getWidth('国', font);
  329. }
  330. /**
  331. * @public
  332. * @param {string} text
  333. * @param {string} font
  334. * @return {Object} width
  335. */
  336. function measureText(text, font) {
  337. return methods.measureText(text, font);
  338. } // Avoid assign to an exported variable, for transforming to cjs.
  339. methods.measureText = function (text, font) {
  340. var ctx = getContext();
  341. ctx.font = font || DEFAULT_FONT;
  342. return ctx.measureText(text);
  343. };
  344. /**
  345. * @public
  346. * @param {string} text
  347. * @param {string} font
  348. * @param {Object} [truncate]
  349. * @return {Object} block: {lineHeight, lines, height, outerHeight, canCacheByTextString}
  350. * Notice: for performance, do not calculate outerWidth util needed.
  351. * `canCacheByTextString` means the result `lines` is only determined by the input `text`.
  352. * Thus we can simply comparing the `input` text to determin whether the result changed,
  353. * without travel the result `lines`.
  354. */
  355. function parsePlainText(text, font, padding, textLineHeight, truncate) {
  356. text != null && (text += '');
  357. var lineHeight = retrieve2(textLineHeight, getLineHeight(font));
  358. var lines = text ? text.split('\n') : [];
  359. var height = lines.length * lineHeight;
  360. var outerHeight = height;
  361. var canCacheByTextString = true;
  362. if (padding) {
  363. outerHeight += padding[0] + padding[2];
  364. }
  365. if (text && truncate) {
  366. canCacheByTextString = false;
  367. var truncOuterHeight = truncate.outerHeight;
  368. var truncOuterWidth = truncate.outerWidth;
  369. if (truncOuterHeight != null && outerHeight > truncOuterHeight) {
  370. text = '';
  371. lines = [];
  372. } else if (truncOuterWidth != null) {
  373. var options = prepareTruncateOptions(truncOuterWidth - (padding ? padding[1] + padding[3] : 0), font, truncate.ellipsis, {
  374. minChar: truncate.minChar,
  375. placeholder: truncate.placeholder
  376. }); // FIXME
  377. // It is not appropriate that every line has '...' when truncate multiple lines.
  378. for (var i = 0, len = lines.length; i < len; i++) {
  379. lines[i] = truncateSingleLine(lines[i], options);
  380. }
  381. }
  382. }
  383. return {
  384. lines: lines,
  385. height: height,
  386. outerHeight: outerHeight,
  387. lineHeight: lineHeight,
  388. canCacheByTextString: canCacheByTextString
  389. };
  390. }
  391. /**
  392. * For example: 'some text {a|some text}other text{b|some text}xxx{c|}xxx'
  393. * Also consider 'bbbb{a|xxx\nzzz}xxxx\naaaa'.
  394. *
  395. * @public
  396. * @param {string} text
  397. * @param {Object} style
  398. * @return {Object} block
  399. * {
  400. * width,
  401. * height,
  402. * lines: [{
  403. * lineHeight,
  404. * width,
  405. * tokens: [[{
  406. * styleName,
  407. * text,
  408. * width, // include textPadding
  409. * height, // include textPadding
  410. * textWidth, // pure text width
  411. * textHeight, // pure text height
  412. * lineHeihgt,
  413. * font,
  414. * textAlign,
  415. * textVerticalAlign
  416. * }], [...], ...]
  417. * }, ...]
  418. * }
  419. * If styleName is undefined, it is plain text.
  420. */
  421. function parseRichText(text, style) {
  422. var contentBlock = {
  423. lines: [],
  424. width: 0,
  425. height: 0
  426. };
  427. text != null && (text += '');
  428. if (!text) {
  429. return contentBlock;
  430. }
  431. var lastIndex = STYLE_REG.lastIndex = 0;
  432. var result;
  433. while ((result = STYLE_REG.exec(text)) != null) {
  434. var matchedIndex = result.index;
  435. if (matchedIndex > lastIndex) {
  436. pushTokens(contentBlock, text.substring(lastIndex, matchedIndex));
  437. }
  438. pushTokens(contentBlock, result[2], result[1]);
  439. lastIndex = STYLE_REG.lastIndex;
  440. }
  441. if (lastIndex < text.length) {
  442. pushTokens(contentBlock, text.substring(lastIndex, text.length));
  443. }
  444. var lines = contentBlock.lines;
  445. var contentHeight = 0;
  446. var contentWidth = 0; // For `textWidth: 100%`
  447. var pendingList = [];
  448. var stlPadding = style.textPadding;
  449. var truncate = style.truncate;
  450. var truncateWidth = truncate && truncate.outerWidth;
  451. var truncateHeight = truncate && truncate.outerHeight;
  452. if (stlPadding) {
  453. truncateWidth != null && (truncateWidth -= stlPadding[1] + stlPadding[3]);
  454. truncateHeight != null && (truncateHeight -= stlPadding[0] + stlPadding[2]);
  455. } // Calculate layout info of tokens.
  456. for (var i = 0; i < lines.length; i++) {
  457. var line = lines[i];
  458. var lineHeight = 0;
  459. var lineWidth = 0;
  460. for (var j = 0; j < line.tokens.length; j++) {
  461. var token = line.tokens[j];
  462. var tokenStyle = token.styleName && style.rich[token.styleName] || {}; // textPadding should not inherit from style.
  463. var textPadding = token.textPadding = tokenStyle.textPadding; // textFont has been asigned to font by `normalizeStyle`.
  464. var font = token.font = tokenStyle.font || style.font; // textHeight can be used when textVerticalAlign is specified in token.
  465. var tokenHeight = token.textHeight = retrieve2( // textHeight should not be inherited, consider it can be specified
  466. // as box height of the block.
  467. tokenStyle.textHeight, getLineHeight(font));
  468. textPadding && (tokenHeight += textPadding[0] + textPadding[2]);
  469. token.height = tokenHeight;
  470. token.lineHeight = retrieve3(tokenStyle.textLineHeight, style.textLineHeight, tokenHeight);
  471. token.textAlign = tokenStyle && tokenStyle.textAlign || style.textAlign;
  472. token.textVerticalAlign = tokenStyle && tokenStyle.textVerticalAlign || 'middle';
  473. if (truncateHeight != null && contentHeight + token.lineHeight > truncateHeight) {
  474. return {
  475. lines: [],
  476. width: 0,
  477. height: 0
  478. };
  479. }
  480. token.textWidth = getWidth(token.text, font);
  481. var tokenWidth = tokenStyle.textWidth;
  482. var tokenWidthNotSpecified = tokenWidth == null || tokenWidth === 'auto'; // Percent width, can be `100%`, can be used in drawing separate
  483. // line when box width is needed to be auto.
  484. if (typeof tokenWidth === 'string' && tokenWidth.charAt(tokenWidth.length - 1) === '%') {
  485. token.percentWidth = tokenWidth;
  486. pendingList.push(token);
  487. tokenWidth = 0; // Do not truncate in this case, because there is no user case
  488. // and it is too complicated.
  489. } else {
  490. if (tokenWidthNotSpecified) {
  491. tokenWidth = token.textWidth; // FIXME: If image is not loaded and textWidth is not specified, calling
  492. // `getBoundingRect()` will not get correct result.
  493. var textBackgroundColor = tokenStyle.textBackgroundColor;
  494. var bgImg = textBackgroundColor && textBackgroundColor.image; // Use cases:
  495. // (1) If image is not loaded, it will be loaded at render phase and call
  496. // `dirty()` and `textBackgroundColor.image` will be replaced with the loaded
  497. // image, and then the right size will be calculated here at the next tick.
  498. // See `graphic/helper/text.js`.
  499. // (2) If image loaded, and `textBackgroundColor.image` is image src string,
  500. // use `imageHelper.findExistImage` to find cached image.
  501. // `imageHelper.findExistImage` will always be called here before
  502. // `imageHelper.createOrUpdateImage` in `graphic/helper/text.js#renderRichText`
  503. // which ensures that image will not be rendered before correct size calcualted.
  504. if (bgImg) {
  505. bgImg = imageHelper.findExistImage(bgImg);
  506. if (imageHelper.isImageReady(bgImg)) {
  507. tokenWidth = Math.max(tokenWidth, bgImg.width * tokenHeight / bgImg.height);
  508. }
  509. }
  510. }
  511. var paddingW = textPadding ? textPadding[1] + textPadding[3] : 0;
  512. tokenWidth += paddingW;
  513. var remianTruncWidth = truncateWidth != null ? truncateWidth - lineWidth : null;
  514. if (remianTruncWidth != null && remianTruncWidth < tokenWidth) {
  515. if (!tokenWidthNotSpecified || remianTruncWidth < paddingW) {
  516. token.text = '';
  517. token.textWidth = tokenWidth = 0;
  518. } else {
  519. token.text = truncateText(token.text, remianTruncWidth - paddingW, font, truncate.ellipsis, {
  520. minChar: truncate.minChar
  521. });
  522. token.textWidth = getWidth(token.text, font);
  523. tokenWidth = token.textWidth + paddingW;
  524. }
  525. }
  526. }
  527. lineWidth += token.width = tokenWidth;
  528. tokenStyle && (lineHeight = Math.max(lineHeight, token.lineHeight));
  529. }
  530. line.width = lineWidth;
  531. line.lineHeight = lineHeight;
  532. contentHeight += lineHeight;
  533. contentWidth = Math.max(contentWidth, lineWidth);
  534. }
  535. contentBlock.outerWidth = contentBlock.width = retrieve2(style.textWidth, contentWidth);
  536. contentBlock.outerHeight = contentBlock.height = retrieve2(style.textHeight, contentHeight);
  537. if (stlPadding) {
  538. contentBlock.outerWidth += stlPadding[1] + stlPadding[3];
  539. contentBlock.outerHeight += stlPadding[0] + stlPadding[2];
  540. }
  541. for (var i = 0; i < pendingList.length; i++) {
  542. var token = pendingList[i];
  543. var percentWidth = token.percentWidth; // Should not base on outerWidth, because token can not be placed out of padding.
  544. token.width = parseInt(percentWidth, 10) / 100 * contentWidth;
  545. }
  546. return contentBlock;
  547. }
  548. function pushTokens(block, str, styleName) {
  549. var isEmptyStr = str === '';
  550. var strs = str.split('\n');
  551. var lines = block.lines;
  552. for (var i = 0; i < strs.length; i++) {
  553. var text = strs[i];
  554. var token = {
  555. styleName: styleName,
  556. text: text,
  557. isLineHolder: !text && !isEmptyStr
  558. }; // The first token should be appended to the last line.
  559. if (!i) {
  560. var tokens = (lines[lines.length - 1] || (lines[0] = {
  561. tokens: []
  562. })).tokens; // Consider cases:
  563. // (1) ''.split('\n') => ['', '\n', ''], the '' at the first item
  564. // (which is a placeholder) should be replaced by new token.
  565. // (2) A image backage, where token likes {a|}.
  566. // (3) A redundant '' will affect textAlign in line.
  567. // (4) tokens with the same tplName should not be merged, because
  568. // they should be displayed in different box (with border and padding).
  569. var tokensLen = tokens.length;
  570. tokensLen === 1 && tokens[0].isLineHolder ? tokens[0] = token : // Consider text is '', only insert when it is the "lineHolder" or
  571. // "emptyStr". Otherwise a redundant '' will affect textAlign in line.
  572. (text || !tokensLen || isEmptyStr) && tokens.push(token);
  573. } // Other tokens always start a new line.
  574. else {
  575. // If there is '', insert it as a placeholder.
  576. lines.push({
  577. tokens: [token]
  578. });
  579. }
  580. }
  581. }
  582. function makeFont(style) {
  583. // FIXME in node-canvas fontWeight is before fontStyle
  584. // Use `fontSize` `fontFamily` to check whether font properties are defined.
  585. var font = (style.fontSize || style.fontFamily) && [style.fontStyle, style.fontWeight, (style.fontSize || 12) + 'px', // If font properties are defined, `fontFamily` should not be ignored.
  586. style.fontFamily || 'sans-serif'].join(' ');
  587. return font && trim(font) || style.textFont || style.font;
  588. }
  589. exports.DEFAULT_FONT = DEFAULT_FONT;
  590. exports.$override = $override;
  591. exports.getWidth = getWidth;
  592. exports.getBoundingRect = getBoundingRect;
  593. exports.adjustTextX = adjustTextX;
  594. exports.adjustTextY = adjustTextY;
  595. exports.calculateTextPosition = calculateTextPosition;
  596. exports.adjustTextPositionOnRect = adjustTextPositionOnRect;
  597. exports.truncateText = truncateText;
  598. exports.getLineHeight = getLineHeight;
  599. exports.measureText = measureText;
  600. exports.parsePlainText = parsePlainText;
  601. exports.parseRichText = parseRichText;
  602. exports.makeFont = makeFont;