/** * Provides precise pixel measurements for blocks of text so that you can determine * the exact pixel height and width of a block of text. * * **Note:** The TextMetrics tool should only be utilized to measure plain text. Attempting to * measure text that includes HTML may return inaccurate results. * * This measurement works by copying the relevant font-related CSS styles from the element * param to the TextMetrics' cached measurement element. This returns the dimensions of the cached * element wrapping the text. By default, the wrapping element is auto-sized. * You must provide a **fixed width** if the passed text is multi-lined. * * When multiple measurements are being done with the same element styling, you should * create a single, reusable TextMetrics instance. This is more efficient than using the * static {@link #measure} method. The element styles are copied to the cached * TextMetrics element once during instantiation versus repeated copying using * _measure()_. * * The following example demonstrates the recommended use of TextMetrics where the custom * textfield class sets up a reusable TextMetrics instance used to measure the label * width. This example assumes that all instances of _mytextfield_ have the same * {@link Ext.form.Labelable#labelClsExtra labelClsExtra} and * {@link Ext.form.Labelable#labelStyle labelStyle} configs. * * Ext.define('MyApp.view.MyTextField', { * extend: 'Ext.form.field.Text', * xtype: 'mytextfield', * * initComponent: function () { * var me = this, * tm = me.getTextMetrics(); * * me.labelWidth = tm.getWidth(me.fieldLabel + me.labelSeparator); * me.callParent(); * }, * * getTextMetrics: function () { * var me = this, * // Using me.self allows labelCls etc. to vary by derived * // class, but not by instance. * cls = me.self, * tm = cls.measurer, * el; * * if (!tm) { * el = Ext.getBody().createChild(); * el.addCls(me.labelCls + ' ' + me.labelClsExtra). * applyStyles(me.labelStyle); * * cls.measurer = tm = new Ext.util.TextMetrics(el); * } * * return tm; * } * }); * * Ext.create('Ext.form.Panel', { * title: 'Contact Info', * width: 600, * bodyPadding: 10, * renderTo: Ext.getBody(), * items: [{ * xtype: 'mytextfield', * fieldLabel: 'Name', * labelStyle: 'font-size: 10px;' * }, { * xtype: 'mytextfield', * fieldLabel: 'Email Address', * labelStyle: 'font-size: 10px;' * }] * }); * * While less efficient than the preceding example, this example allows each instance of * _mytextfield2_ to have unique labelClsExtra and labelStyle configs. Each custom * textfield instance uses the static TextMetrics measure method which will copy the * label styles repeatedly, thus being less efficient but more versatile. * * Ext.define('MyApp.view.MyTextField2', { * extend: 'Ext.form.field.Text', * xtype: 'mytextfield2', * * initComponent: function () { * var me = this, * el = me.getMeasurementEl(), * tm = Ext.util.TextMetrics; * * me.labelWidth = tm.measure(el, me.fieldLabel + me.labelSeparator).width; * me.callParent(); * }, * * getMeasurementEl: function () { * var me = this, * cls = MyApp.view.MyTextField2, * el = cls.measureEl; * * if (!el) { * cls.measureEl = el = Ext.getBody().createChild(); * } * * el.dom.removeAttribute('style'); * el.removeCls(el.dom.className). * addCls(me.labelCls + ' ' + me.labelClsExtra). * applyStyles(me.labelStyle); * * return el; * } * }); * * Ext.create('Ext.form.Panel', { * title: 'Contact Info', * width: 600, * bodyPadding: 10, * renderTo: Ext.getBody(), * items: [{ * xtype: 'mytextfield2', * fieldLabel: 'Name', * labelStyle: 'font-size: 14px;font-weight: bold;', * labelClsExtra: 'nameLabel' * }, { * xtype: 'mytextfield2', * fieldLabel: 'Email Address', * labelStyle: 'font-size: 10px;', * labelClsExtra: 'emailLabel' * }] * }); */ Ext.define('Ext.util.TextMetrics', { requires: [ 'Ext.dom.Element' ], statics: { shared: null, /** * Measures the size of the specified text * @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles * that can affect the size of the rendered text * @param {String} text The text to measure * @param {Number} [fixedWidth] If the text will be multiline, you have to set a fixed width * in order to accurately measure the text height * @return {Object} An object containing the text's size `{width: (width), height: (height)}` * @static */ measure: function(el, text, fixedWidth) { var me = this, shared = me.shared || (me.shared = new me(el, fixedWidth)); shared.bind(el); shared.setFixedWidth(fixedWidth || 'auto'); return shared.getSize(text); }, /** * Destroy the TextMetrics instance created by {@link #measure}. * @static */ destroy: function() { this.shared = Ext.destroy(this.shared); } }, /** * Creates new TextMetrics. * @param {String/HTMLElement/Ext.dom.Element} bindTo The element or its ID to bind to. * @param {Number} [fixedWidth] A fixed width to apply to the measuring element. */ constructor: function(bindTo, fixedWidth) { var me = this, measure = me.measure = Ext.getBody().createChild({ //<debug> // tell the spec runner to ignore this element when checking if the dom is clean 'data-sticky': true, //</debug> role: 'presentation', cls: Ext.baseCSSPrefix + 'textmetrics', style: { position: 'absolute', left: '-1000px', top: '-1000px', visibility: 'hidden' } }); if (bindTo) { me.bind(bindTo); } if (fixedWidth) { measure.setWidth(fixedWidth); } }, /** * Returns the size of the specified text based on the internal element's style and width properties * @param {String} text The text to measure * @return {Object} An object containing the text's size `{width: (width), height: (height)}` */ getSize: function(text) { var measure = this.measure, size; measure.setHtml(text); size = measure.getSize(); measure.setHtml(''); return size; }, /** * Binds this TextMetrics instance to a new element * @param {String/HTMLElement/Ext.dom.Element} el The element or its ID. */ bind: function(el) { this.measure.setStyle( // Create an Ext.dom.Fly instance on our prototype unless we've already been through here. // Attach it to the passed HTMLElement/Ext.Element (this.el || (this.self.prototype.el = new Ext.dom.Fly())).attach(el).getStyle([ 'font-size', 'font-size-adjust', 'font-style', 'font-weight', 'font-family', 'font-kerning', 'font-stretch', 'line-height', 'text-transform', 'text-decoration', 'letter-spacing', 'word-break' ]) ); }, /** * Sets a fixed width on the internal measurement element. If the text will be multiline, you have * to set a fixed width in order to accurately measure the text height. * @param {Number} width The width to set on the element */ setFixedWidth : function(width){ this.measure.setWidth(width); }, /** * Returns the measured width of the specified text * @param {String} text The text to measure * @return {Number} width The width in pixels */ getWidth : function(text){ this.measure.dom.style.width = 'auto'; return this.getSize(text).width; }, /** * Returns the measured height of the specified text * @param {String} text The text to measure * @return {Number} height The height in pixels */ getHeight : function(text){ return this.getSize(text).height; }, /** * Destroy this instance */ destroy: function(){ var me = this; me.el = me.measure = Ext.destroy(me.measure); me.callParent(); }}, function(){ Ext.Element.override({ /** * Returns the width in pixels of the passed text, or the width of the text in this Element. * @param {String} text The text to measure. Defaults to the innerHTML of the element. * @param {Number} [min] The minumum value to return. * @param {Number} [max] The maximum value to return. * @return {Number} The text width in pixels. * @member Ext.dom.Element */ getTextWidth : function(text, min, max){ return Ext.Number.constrain(Ext.util.TextMetrics.measure(this.dom, Ext.valueFrom(text, this.dom.innerHTML, true)).width, min || 0, max || 1000000); } });});