/* jshint -W083 */ (function(exports) { 'use strict'; /** * Utility functions for measuring and manipulating font sizes */ var FontSizeUtils = { /** * Keep a cache of canvas contexts with a given font. * We do this because it is faster to create new canvases * than to re-set the font on existing contexts repeatedly. */ _cachedContexts: {}, /** * Grab or create a cached canvas context for a given fontSize/family pair. * @todo Add font-weight as a new dimension for caching. * * @param {Integer} fontSize The font size of the canvas we want. * @param {String} fontFamily The font family of the canvas we want. * @param {String} fontStyle The style of the font (default to italic). * @return {CanvasRenderingContext2D} A context with the specified font. */ _getCachedContext: function(fontSize, fontFamily, fontStyle) { // Default to italic style since this code is only ever used // by headers right now and header text is always italic. fontStyle = fontStyle || 'italic'; var cache = this._cachedContexts; var ctx = cache[fontSize] && cache[fontSize][fontFamily] ? cache[fontSize][fontFamily][fontStyle] : null; if (!ctx) { var canvas = document.createElement('canvas'); canvas.setAttribute('moz-opaque', 'true'); canvas.setAttribute('width', '1'); canvas.setAttribute('height', '1'); ctx = canvas.getContext('2d', { willReadFrequently: true }); ctx.font = fontStyle + ' ' + fontSize + 'px ' + fontFamily; // Populate the contexts cache. if (!cache[fontSize]) { cache[fontSize] = {}; } if (!cache[fontSize][fontFamily]) { cache[fontSize][fontFamily] = {}; } cache[fontSize][fontFamily][fontStyle] = ctx; } return ctx; }, /** * Clear any current canvas contexts from the cache. */ resetCache: function() { this._cachedContexts = {}; }, /** * Get the width of a string in pixels, given its fontSize and fontFamily. * * @param {String} string The string we are measuring. * @param {Integer} fontSize The size of the font to measure against. * @param {String} fontFamily The font family to measure against. * @param {String} fontStyle The style of the font (default to italic). * @return {Integer} The pixel width of the string with the given font. */ getFontWidth: function(string, fontSize, fontFamily, fontStyle) { var ctx = this._getCachedContext(fontSize, fontFamily, fontStyle); return ctx.measureText(string).width; }, /** * Get the maximum allowable fontSize for a string such that it will * not overflow past a maximum width. * * @param {String} string The string for which to check max font size. * @param {Array} allowedSizes A list of fontSizes allowed. * @param {String} fontFamily The font family of the string we're measuring. * @param {Integer} maxWidth The maximum number of pixels before overflow. * @return {Object} Dict containing fontSize, overflow and textWidth. */ getMaxFontSizeInfo: function(string, allowedSizes, fontFamily, maxWidth) { var fontSize; var resultWidth; var i = allowedSizes.length - 1; do { fontSize = allowedSizes[i]; resultWidth = this.getFontWidth(string, fontSize, fontFamily); i--; } while (resultWidth > maxWidth && i >= 0); return { fontSize: fontSize, overflow: resultWidth > maxWidth, textWidth: resultWidth }; }, /** * Last result of the overflow count computation. Every time we recompute * the overflow count we start from this value; if the string is larger * than the previous one this will cut down the number of computations, if * not we restart from 0. */ _overflowCountCache: -1, /** * Get the amount of characters truncated from overflow ellipses. * * @param {String} string The string for which to check max font size. * @param {Integer} fontSize The font size of the string we are measuring. * @param {String} fontFamily The font family of the string we're measuring. * @param {Integer} maxWidth The maximum number of pixels before overflow. */ getOverflowCount: function(string, fontSize, fontFamily, maxWidth) { var substring; var resultWidth; var overflowCount = -1; // If the new string is longer than the cached result of the previous // call, and if the calculation yields a coherent result then start from // the cached value to speed up the calculation. */ if (string.length > this._overflowCountCache) { substring = string.substr(0, string.length - this._overflowCountCache); resultWidth = this.getFontWidth(substring, fontSize, fontFamily); if (resultWidth > maxWidth) { overflowCount = this._overflowCountCache; } } do { overflowCount++; substring = string.substr(0, string.length - overflowCount); resultWidth = this.getFontWidth(substring, fontSize, fontFamily); } while (substring.length > 0 && resultWidth > maxWidth); this._overflowCountCache = overflowCount; return overflowCount; }, /** * Get an element's content width disregarding its box model sizing. * * @param {HTMLElement|Object} HTML element, or style object. * @returns {Number} width in pixels of elements content. */ getContentWidth: function(style) { var width = parseInt(style.width, 10); if (style.boxSizing === 'border-box') { width -= (parseInt(style.paddingRight, 10) + parseInt(style.paddingLeft, 10)); } return width; }, /** * Get an element's style properies. * * @param {HTMLElement} element The element from which to fetch style. * @return {Object} A dictionary containing element's style properties. */ getStyleProperties: function(element) { var style = window.getComputedStyle(element); var contentWidth = this.getContentWidth(style); if (isNaN(contentWidth)) { contentWidth = 0; } return { fontFamily: style.fontFamily, contentWidth: contentWidth, paddingRight: parseInt(style.paddingRight, 10), paddingLeft: parseInt(style.paddingLeft, 10), offsetLeft: element.offsetLeft }; }, /** * Cache and return the width of the inner window. * * @return {Integer} The width of the inner window in pixels. */ getWindowWidth: function() { return window.innerWidth; } }; exports.FontSizeUtils = FontSizeUtils; }(this));