text.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import BoundingRect from '../core/BoundingRect.js';
  2. import LRU from '../core/LRU.js';
  3. import { DEFAULT_FONT, platformApi } from '../core/platform.js';
  4. export function getWidth(text, font) {
  5. return measureWidth(ensureFontMeasureInfo(font), text);
  6. }
  7. export function ensureFontMeasureInfo(font) {
  8. if (!_fontMeasureInfoCache) {
  9. _fontMeasureInfoCache = new LRU(100);
  10. }
  11. font = font || DEFAULT_FONT;
  12. var measureInfo = _fontMeasureInfoCache.get(font);
  13. if (!measureInfo) {
  14. measureInfo = {
  15. font: font,
  16. strWidthCache: new LRU(500),
  17. asciiWidthMap: null,
  18. asciiWidthMapTried: false,
  19. stWideCharWidth: platformApi.measureText('国', font).width,
  20. asciiCharWidth: platformApi.measureText('a', font).width
  21. };
  22. _fontMeasureInfoCache.put(font, measureInfo);
  23. }
  24. return measureInfo;
  25. }
  26. var _fontMeasureInfoCache;
  27. function tryCreateASCIIWidthMap(font) {
  28. if (_getASCIIWidthMapLongCount >= GET_ASCII_WIDTH_LONG_COUNT_MAX) {
  29. return;
  30. }
  31. font = font || DEFAULT_FONT;
  32. var asciiWidthMap = [];
  33. var start = +(new Date());
  34. for (var code = 0; code <= 127; code++) {
  35. asciiWidthMap[code] = platformApi.measureText(String.fromCharCode(code), font).width;
  36. }
  37. var cost = +(new Date()) - start;
  38. if (cost > 16) {
  39. _getASCIIWidthMapLongCount = GET_ASCII_WIDTH_LONG_COUNT_MAX;
  40. }
  41. else if (cost > 2) {
  42. _getASCIIWidthMapLongCount++;
  43. }
  44. return asciiWidthMap;
  45. }
  46. var _getASCIIWidthMapLongCount = 0;
  47. var GET_ASCII_WIDTH_LONG_COUNT_MAX = 5;
  48. export function measureCharWidth(fontMeasureInfo, charCode) {
  49. if (!fontMeasureInfo.asciiWidthMapTried) {
  50. fontMeasureInfo.asciiWidthMap = tryCreateASCIIWidthMap(fontMeasureInfo.font);
  51. fontMeasureInfo.asciiWidthMapTried = true;
  52. }
  53. return (0 <= charCode && charCode <= 127)
  54. ? (fontMeasureInfo.asciiWidthMap != null
  55. ? fontMeasureInfo.asciiWidthMap[charCode]
  56. : fontMeasureInfo.asciiCharWidth)
  57. : fontMeasureInfo.stWideCharWidth;
  58. }
  59. export function measureWidth(fontMeasureInfo, text) {
  60. var strWidthCache = fontMeasureInfo.strWidthCache;
  61. var width = strWidthCache.get(text);
  62. if (width == null) {
  63. width = platformApi.measureText(text, fontMeasureInfo.font).width;
  64. strWidthCache.put(text, width);
  65. }
  66. return width;
  67. }
  68. export function innerGetBoundingRect(text, font, textAlign, textBaseline) {
  69. var width = measureWidth(ensureFontMeasureInfo(font), text);
  70. var height = getLineHeight(font);
  71. var x = adjustTextX(0, width, textAlign);
  72. var y = adjustTextY(0, height, textBaseline);
  73. var rect = new BoundingRect(x, y, width, height);
  74. return rect;
  75. }
  76. export function getBoundingRect(text, font, textAlign, textBaseline) {
  77. var textLines = ((text || '') + '').split('\n');
  78. var len = textLines.length;
  79. if (len === 1) {
  80. return innerGetBoundingRect(textLines[0], font, textAlign, textBaseline);
  81. }
  82. else {
  83. var uniondRect = new BoundingRect(0, 0, 0, 0);
  84. for (var i = 0; i < textLines.length; i++) {
  85. var rect = innerGetBoundingRect(textLines[i], font, textAlign, textBaseline);
  86. i === 0 ? uniondRect.copy(rect) : uniondRect.union(rect);
  87. }
  88. return uniondRect;
  89. }
  90. }
  91. export function adjustTextX(x, width, textAlign, inverse) {
  92. if (textAlign === 'right') {
  93. !inverse ? (x -= width) : (x += width);
  94. }
  95. else if (textAlign === 'center') {
  96. !inverse ? (x -= width / 2) : (x += width / 2);
  97. }
  98. return x;
  99. }
  100. export function adjustTextY(y, height, verticalAlign, inverse) {
  101. if (verticalAlign === 'middle') {
  102. !inverse ? (y -= height / 2) : (y += height / 2);
  103. }
  104. else if (verticalAlign === 'bottom') {
  105. !inverse ? (y -= height) : (y += height);
  106. }
  107. return y;
  108. }
  109. export function getLineHeight(font) {
  110. return ensureFontMeasureInfo(font).stWideCharWidth;
  111. }
  112. export function measureText(text, font) {
  113. return platformApi.measureText(text, font);
  114. }
  115. export function parsePercent(value, maxValue) {
  116. if (typeof value === 'string') {
  117. if (value.lastIndexOf('%') >= 0) {
  118. return parseFloat(value) / 100 * maxValue;
  119. }
  120. return parseFloat(value);
  121. }
  122. return value;
  123. }
  124. export function calculateTextPosition(out, opts, rect) {
  125. var textPosition = opts.position || 'inside';
  126. var distance = opts.distance != null ? opts.distance : 5;
  127. var height = rect.height;
  128. var width = rect.width;
  129. var halfHeight = height / 2;
  130. var x = rect.x;
  131. var y = rect.y;
  132. var textAlign = 'left';
  133. var textVerticalAlign = 'top';
  134. if (textPosition instanceof Array) {
  135. x += parsePercent(textPosition[0], rect.width);
  136. y += parsePercent(textPosition[1], rect.height);
  137. textAlign = null;
  138. textVerticalAlign = null;
  139. }
  140. else {
  141. switch (textPosition) {
  142. case 'left':
  143. x -= distance;
  144. y += halfHeight;
  145. textAlign = 'right';
  146. textVerticalAlign = 'middle';
  147. break;
  148. case 'right':
  149. x += distance + width;
  150. y += halfHeight;
  151. textVerticalAlign = 'middle';
  152. break;
  153. case 'top':
  154. x += width / 2;
  155. y -= distance;
  156. textAlign = 'center';
  157. textVerticalAlign = 'bottom';
  158. break;
  159. case 'bottom':
  160. x += width / 2;
  161. y += height + distance;
  162. textAlign = 'center';
  163. break;
  164. case 'inside':
  165. x += width / 2;
  166. y += halfHeight;
  167. textAlign = 'center';
  168. textVerticalAlign = 'middle';
  169. break;
  170. case 'insideLeft':
  171. x += distance;
  172. y += halfHeight;
  173. textVerticalAlign = 'middle';
  174. break;
  175. case 'insideRight':
  176. x += width - distance;
  177. y += halfHeight;
  178. textAlign = 'right';
  179. textVerticalAlign = 'middle';
  180. break;
  181. case 'insideTop':
  182. x += width / 2;
  183. y += distance;
  184. textAlign = 'center';
  185. break;
  186. case 'insideBottom':
  187. x += width / 2;
  188. y += height - distance;
  189. textAlign = 'center';
  190. textVerticalAlign = 'bottom';
  191. break;
  192. case 'insideTopLeft':
  193. x += distance;
  194. y += distance;
  195. break;
  196. case 'insideTopRight':
  197. x += width - distance;
  198. y += distance;
  199. textAlign = 'right';
  200. break;
  201. case 'insideBottomLeft':
  202. x += distance;
  203. y += height - distance;
  204. textVerticalAlign = 'bottom';
  205. break;
  206. case 'insideBottomRight':
  207. x += width - distance;
  208. y += height - distance;
  209. textAlign = 'right';
  210. textVerticalAlign = 'bottom';
  211. break;
  212. }
  213. }
  214. out = out || {};
  215. out.x = x;
  216. out.y = y;
  217. out.align = textAlign;
  218. out.verticalAlign = textVerticalAlign;
  219. return out;
  220. }