239 lines
8.3 KiB
JavaScript
Executable File
239 lines
8.3 KiB
JavaScript
Executable File
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Distributed under the BSD license:
|
|
*
|
|
* Copyright (c) 2010, Ajax.org B.V.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of Ajax.org B.V. nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
define(function(require, exports, module) {
|
|
|
|
var oop = require("../lib/oop");
|
|
var dom = require("../lib/dom");
|
|
var lang = require("../lib/lang");
|
|
var event = require("../lib/event");
|
|
var useragent = require("../lib/useragent");
|
|
var EventEmitter = require("../lib/event_emitter").EventEmitter;
|
|
|
|
var CHAR_COUNT = 256;
|
|
var USE_OBSERVER = typeof ResizeObserver == "function";
|
|
var L = 200;
|
|
|
|
var FontMetrics = exports.FontMetrics = function(parentEl) {
|
|
this.el = dom.createElement("div");
|
|
this.$setMeasureNodeStyles(this.el.style, true);
|
|
|
|
this.$main = dom.createElement("div");
|
|
this.$setMeasureNodeStyles(this.$main.style);
|
|
|
|
this.$measureNode = dom.createElement("div");
|
|
this.$setMeasureNodeStyles(this.$measureNode.style);
|
|
|
|
|
|
this.el.appendChild(this.$main);
|
|
this.el.appendChild(this.$measureNode);
|
|
parentEl.appendChild(this.el);
|
|
|
|
this.$measureNode.innerHTML = lang.stringRepeat("X", CHAR_COUNT);
|
|
|
|
this.$characterSize = {width: 0, height: 0};
|
|
|
|
|
|
if (USE_OBSERVER)
|
|
this.$addObserver();
|
|
else
|
|
this.checkForSizeChanges();
|
|
};
|
|
|
|
(function() {
|
|
|
|
oop.implement(this, EventEmitter);
|
|
|
|
this.$characterSize = {width: 0, height: 0};
|
|
|
|
this.$setMeasureNodeStyles = function(style, isRoot) {
|
|
style.width = style.height = "auto";
|
|
style.left = style.top = "0px";
|
|
style.visibility = "hidden";
|
|
style.position = "absolute";
|
|
style.whiteSpace = "pre";
|
|
|
|
if (useragent.isIE < 8) {
|
|
style["font-family"] = "inherit";
|
|
} else {
|
|
style.font = "inherit";
|
|
}
|
|
style.overflow = isRoot ? "hidden" : "visible";
|
|
};
|
|
|
|
this.checkForSizeChanges = function(size) {
|
|
if (size === undefined)
|
|
size = this.$measureSizes();
|
|
if (size && (this.$characterSize.width !== size.width || this.$characterSize.height !== size.height)) {
|
|
this.$measureNode.style.fontWeight = "bold";
|
|
var boldSize = this.$measureSizes();
|
|
this.$measureNode.style.fontWeight = "";
|
|
this.$characterSize = size;
|
|
this.charSizes = Object.create(null);
|
|
this.allowBoldFonts = boldSize && boldSize.width === size.width && boldSize.height === size.height;
|
|
this._emit("changeCharacterSize", {data: size});
|
|
}
|
|
};
|
|
|
|
this.$addObserver = function() {
|
|
var self = this;
|
|
this.$observer = new window.ResizeObserver(function(e) {
|
|
var rect = e[0].contentRect;
|
|
self.checkForSizeChanges({
|
|
height: rect.height,
|
|
width: rect.width / CHAR_COUNT
|
|
});
|
|
});
|
|
this.$observer.observe(this.$measureNode);
|
|
};
|
|
|
|
this.$pollSizeChanges = function() {
|
|
if (this.$pollSizeChangesTimer || this.$observer)
|
|
return this.$pollSizeChangesTimer;
|
|
var self = this;
|
|
|
|
return this.$pollSizeChangesTimer = event.onIdle(function cb() {
|
|
self.checkForSizeChanges();
|
|
event.onIdle(cb, 500);
|
|
}, 500);
|
|
};
|
|
|
|
this.setPolling = function(val) {
|
|
if (val) {
|
|
this.$pollSizeChanges();
|
|
} else if (this.$pollSizeChangesTimer) {
|
|
clearInterval(this.$pollSizeChangesTimer);
|
|
this.$pollSizeChangesTimer = 0;
|
|
}
|
|
};
|
|
|
|
this.$measureSizes = function(node) {
|
|
var size = {
|
|
height: (node || this.$measureNode).clientHeight,
|
|
width: (node || this.$measureNode).clientWidth / CHAR_COUNT
|
|
};
|
|
|
|
// Size and width can be null if the editor is not visible or
|
|
// detached from the document
|
|
if (size.width === 0 || size.height === 0)
|
|
return null;
|
|
return size;
|
|
};
|
|
|
|
this.$measureCharWidth = function(ch) {
|
|
this.$main.innerHTML = lang.stringRepeat(ch, CHAR_COUNT);
|
|
var rect = this.$main.getBoundingClientRect();
|
|
return rect.width / CHAR_COUNT;
|
|
};
|
|
|
|
this.getCharacterWidth = function(ch) {
|
|
var w = this.charSizes[ch];
|
|
if (w === undefined) {
|
|
w = this.charSizes[ch] = this.$measureCharWidth(ch) / this.$characterSize.width;
|
|
}
|
|
return w;
|
|
};
|
|
|
|
this.destroy = function() {
|
|
clearInterval(this.$pollSizeChangesTimer);
|
|
if (this.$observer)
|
|
this.$observer.disconnect();
|
|
if (this.el && this.el.parentNode)
|
|
this.el.parentNode.removeChild(this.el);
|
|
};
|
|
|
|
|
|
this.$getZoom = function getZoom(element) {
|
|
if (!element) return 1;
|
|
return (window.getComputedStyle(element).zoom || 1) * getZoom(element.parentElement);
|
|
};
|
|
this.$initTransformMeasureNodes = function() {
|
|
var t = function(t, l) {
|
|
return ["div", {
|
|
style: "position: absolute;top:" + t + "px;left:" + l + "px;"
|
|
}];
|
|
};
|
|
this.els = dom.buildDom([t(0, 0), t(L, 0), t(0, L), t(L, L)], this.el);
|
|
};
|
|
// general transforms from element coordinates x to screen coordinates u have the form
|
|
// | m1[0] m2[0] t[0] | | x | | u |
|
|
// | m1[1] m2[1] t[1] | . | y | == k | v |
|
|
// | h[0] h[1] 1 | | 1 | | 1 |
|
|
// this function finds the coeeficients of the matrix using positions of four points
|
|
//
|
|
this.transformCoordinates = function(clientPos, elPos) {
|
|
if (clientPos) {
|
|
var zoom = this.$getZoom(this.el);
|
|
clientPos = mul(1 / zoom, clientPos);
|
|
}
|
|
function solve(l1, l2, r) {
|
|
var det = l1[1] * l2[0] - l1[0] * l2[1];
|
|
return [
|
|
(-l2[1] * r[0] + l2[0] * r[1]) / det,
|
|
(+l1[1] * r[0] - l1[0] * r[1]) / det
|
|
];
|
|
}
|
|
function sub(a, b) { return [a[0] - b[0], a[1] - b[1]]; }
|
|
function add(a, b) { return [a[0] + b[0], a[1] + b[1]]; }
|
|
function mul(a, b) { return [a * b[0], a * b[1]]; }
|
|
|
|
if (!this.els)
|
|
this.$initTransformMeasureNodes();
|
|
|
|
function p(el) {
|
|
var r = el.getBoundingClientRect();
|
|
return [r.left, r.top];
|
|
}
|
|
|
|
var a = p(this.els[0]);
|
|
var b = p(this.els[1]);
|
|
var c = p(this.els[2]);
|
|
var d = p(this.els[3]);
|
|
|
|
var h = solve(sub(d, b), sub(d, c), sub(add(b, c), add(d, a)));
|
|
|
|
var m1 = mul(1 + h[0], sub(b, a));
|
|
var m2 = mul(1 + h[1], sub(c, a));
|
|
|
|
if (elPos) {
|
|
var x = elPos;
|
|
var k = h[0] * x[0] / L + h[1] * x[1] / L + 1;
|
|
var ut = add(mul(x[0], m1), mul(x[1], m2));
|
|
return add(mul(1 / k / L, ut), a);
|
|
}
|
|
var u = sub(clientPos, a);
|
|
var f = solve(sub(m1, mul(h[0], u)), sub(m2, mul(h[1], u)), u);
|
|
return mul(L, f);
|
|
};
|
|
|
|
}).call(FontMetrics.prototype);
|
|
|
|
});
|