font_metrics.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. /* ***** BEGIN LICENSE BLOCK *****
  2. * Distributed under the BSD license:
  3. *
  4. * Copyright (c) 2010, Ajax.org B.V.
  5. * All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. * * Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * * Redistributions in binary form must reproduce the above copyright
  12. * notice, this list of conditions and the following disclaimer in the
  13. * documentation and/or other materials provided with the distribution.
  14. * * Neither the name of Ajax.org B.V. nor the
  15. * names of its contributors may be used to endorse or promote products
  16. * derived from this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  19. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *
  29. * ***** END LICENSE BLOCK ***** */
  30. define(function(require, exports, module) {
  31. var oop = require("../lib/oop");
  32. var dom = require("../lib/dom");
  33. var lang = require("../lib/lang");
  34. var useragent = require("../lib/useragent");
  35. var EventEmitter = require("../lib/event_emitter").EventEmitter;
  36. var CHAR_COUNT = 0;
  37. var FontMetrics = exports.FontMetrics = function(parentEl) {
  38. this.el = dom.createElement("div");
  39. this.$setMeasureNodeStyles(this.el.style, true);
  40. this.$main = dom.createElement("div");
  41. this.$setMeasureNodeStyles(this.$main.style);
  42. this.$measureNode = dom.createElement("div");
  43. this.$setMeasureNodeStyles(this.$measureNode.style);
  44. this.el.appendChild(this.$main);
  45. this.el.appendChild(this.$measureNode);
  46. parentEl.appendChild(this.el);
  47. if (!CHAR_COUNT)
  48. this.$testFractionalRect();
  49. this.$measureNode.innerHTML = lang.stringRepeat("X", CHAR_COUNT);
  50. this.$characterSize = {width: 0, height: 0};
  51. this.checkForSizeChanges();
  52. };
  53. (function() {
  54. oop.implement(this, EventEmitter);
  55. this.$characterSize = {width: 0, height: 0};
  56. this.$testFractionalRect = function() {
  57. var el = dom.createElement("div");
  58. this.$setMeasureNodeStyles(el.style);
  59. el.style.width = "0.2px";
  60. document.documentElement.appendChild(el);
  61. var w = el.getBoundingClientRect().width;
  62. if (w > 0 && w < 1)
  63. CHAR_COUNT = 50;
  64. else
  65. CHAR_COUNT = 100;
  66. el.parentNode.removeChild(el);
  67. };
  68. this.$setMeasureNodeStyles = function(style, isRoot) {
  69. style.width = style.height = "auto";
  70. style.left = style.top = "0px";
  71. style.visibility = "hidden";
  72. style.position = "absolute";
  73. style.whiteSpace = "pre";
  74. if (useragent.isIE < 8) {
  75. style["font-family"] = "inherit";
  76. } else {
  77. style.font = "inherit";
  78. }
  79. style.overflow = isRoot ? "hidden" : "visible";
  80. };
  81. this.checkForSizeChanges = function() {
  82. var size = this.$measureSizes();
  83. if (size && (this.$characterSize.width !== size.width || this.$characterSize.height !== size.height)) {
  84. this.$measureNode.style.fontWeight = "bold";
  85. var boldSize = this.$measureSizes();
  86. this.$measureNode.style.fontWeight = "";
  87. this.$characterSize = size;
  88. this.charSizes = Object.create(null);
  89. this.allowBoldFonts = boldSize && boldSize.width === size.width && boldSize.height === size.height;
  90. this._emit("changeCharacterSize", {data: size});
  91. }
  92. };
  93. this.$pollSizeChanges = function() {
  94. if (this.$pollSizeChangesTimer)
  95. return this.$pollSizeChangesTimer;
  96. var self = this;
  97. return this.$pollSizeChangesTimer = setInterval(function() {
  98. self.checkForSizeChanges();
  99. }, 500);
  100. };
  101. this.setPolling = function(val) {
  102. if (val) {
  103. this.$pollSizeChanges();
  104. } else if (this.$pollSizeChangesTimer) {
  105. clearInterval(this.$pollSizeChangesTimer);
  106. this.$pollSizeChangesTimer = 0;
  107. }
  108. };
  109. this.$measureSizes = function() {
  110. if (CHAR_COUNT === 50) {
  111. var rect = null;
  112. try {
  113. rect = this.$measureNode.getBoundingClientRect();
  114. } catch(e) {
  115. rect = {width: 0, height:0 };
  116. };
  117. var size = {
  118. height: rect.height,
  119. width: rect.width / CHAR_COUNT
  120. };
  121. } else {
  122. var size = {
  123. height: this.$measureNode.clientHeight,
  124. width: this.$measureNode.clientWidth / CHAR_COUNT
  125. };
  126. }
  127. // Size and width can be null if the editor is not visible or
  128. // detached from the document
  129. if (size.width === 0 || size.height === 0)
  130. return null;
  131. return size;
  132. };
  133. this.$measureCharWidth = function(ch) {
  134. this.$main.innerHTML = lang.stringRepeat(ch, CHAR_COUNT);
  135. var rect = this.$main.getBoundingClientRect();
  136. return rect.width / CHAR_COUNT;
  137. };
  138. this.getCharacterWidth = function(ch) {
  139. var w = this.charSizes[ch];
  140. if (w === undefined) {
  141. w = this.charSizes[ch] = this.$measureCharWidth(ch) / this.$characterSize.width;
  142. }
  143. return w;
  144. };
  145. this.destroy = function() {
  146. clearInterval(this.$pollSizeChangesTimer);
  147. if (this.el && this.el.parentNode)
  148. this.el.parentNode.removeChild(this.el);
  149. };
  150. }).call(FontMetrics.prototype);
  151. });