%PDF- %PDF-
Direktori : /www/varak.net/wiki.varak.net/extensions/UniversalLanguageSelector/lib/ |
Current File : /www/varak.net/wiki.varak.net/extensions/UniversalLanguageSelector/lib/jquery.webfonts.js |
/** * jQuery Webfonts. * * Copyright (C) 2012 Santhosh Thottingal * * UniversalLanguageSelector is dual licensed GPLv2 or later and MIT. You don't * have to do anything special to choose one license or the other and you don't * have to notify anyone which license you are using. You are free to use * UniversalLanguageSelector in commercial projects as long as the copyright * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details. * * @file * @ingroup Extensions * @licence GNU General Public Licence 2.0 or later * @licence MIT License */ ( function( $, window, document, undefined ) { 'use strict'; var WebFonts = function( element, options ) { // Load defaults this.options = $.extend( {}, $.fn.webfonts.defaults, options ); this.$element = $( element ); this.repository = $.extend( WebFonts.repository, this.options.repository ); // List of loaded fonts this.fonts = []; this.originalFontFamily = this.$element.css( 'font-family' ); this.language = this.$element.attr( 'lang' ) || $( 'html' ).attr( 'lang' ); this.init(); }; WebFonts.repository = { base: 'fonts', // Relative or absolute path to the font repository. languages: {}, // languages to font mappings fonts: {}, // Font name to font configuration mapping // Utility methods to work on the repository. defaultFont: function( language ) { var defaultFont = null; if ( this.languages[language] ) { defaultFont = this.languages[language][0]; } return defaultFont; }, get: function( fontFamily ) { return this.fonts[fontFamily]; } }; WebFonts.prototype = { constructor: WebFonts, /** * Get the default font family for given language. * @param {String} language Language code. * @param {array} classes * @return {String} Font family name */ getFont: function( language, classes ) { language = ( language || this.language ).toLowerCase(); if ( this.options.fontSelector ) { return this.options.fontSelector( this.repository, language, classes ); } else { return this.repository.defaultFont( language ); } }, /** * Initialize. */ init: function() { if ( this.language ) { this.apply( this.getFont( this.language ) ); } this.parse(); }, /** * TODO: document */ refresh: function() { this.reset(); this.init(); }, /** * Apply a font for given elements. * * @param {String} fontFamily Font family name * @param {jQuery} $element One or more jQuery elements */ apply: function( fontFamily, $element ) { var fontStack = this.options.fontStack.slice( 0 ); $element = $element || this.$element; // Loading an empty string is pointless. // Putting an empty string into a font-family list doesn't work with // jQuery.css(). if ( fontFamily ) { this.load( fontFamily ); // Avoid duplicates if ( $.inArray( fontFamily, fontStack ) < 0 ) { fontStack.unshift( fontFamily ); } } if ( !fontFamily ) { // We are resetting the font to original font. fontStack = []; // This will cause removing inline fontFamily style. } // Set the font of this element if it's not excluded. // Add class webfonts-changed when webfonts are applied. $element.not( this.options.exclude ) .css( 'font-family', fontStack.join() ) .addClass( 'webfonts-changed' ); // Set the font of this element's children if they are not excluded. // font-family of <input>, <textarea> and <button> must be changed explicitly. // Add class webfonts-changed when webfonts are applied. $element.find( 'textarea, input, button' ).not( this.options.exclude ) .css( 'font-family', fontStack.join() ) .addClass( 'webfonts-changed' ); }, /** * Load given font families if not loaded already. Creates the CSS rules * and appends them to document. * * @param {Array|String} fontFamilies List of font families */ load: function( fontFamilies ) { var css, fontFamily, i, fontFaceRule = ''; // Convert to array if string given (old signature) if ( typeof fontFamilies === 'string' ) { fontFamilies = [fontFamilies]; } for ( i = 0; i < fontFamilies.length; i++ ) { fontFamily = fontFamilies[i]; if ( $.inArray( fontFamily, this.fonts ) >= 0 ) { continue; } css = this.getCSS( fontFamily, 'normal' ); if ( css !== false ) { fontFaceRule += css; this.fonts.push( fontFamily ); } } // In case the list contained only fonts that are already loaded // or non-existing fonts. if ( fontFaceRule !== '' ) { injectCSS( fontFaceRule ); } return true; }, /** * Parse the element for custom font-family styles and for nodes with * different language than what the element itself has. */ parse: function() { var webfonts = this, // Fonts can be added indirectly via classes, but also with // style attributes. For lang attributes we will use our font // if they don't have explicit font already. $elements = webfonts.$element.find( '*[lang], [style], [class]' ), // List of fonts to load in a batch fontQueue = [], // List of elements to apply a certain font family in a batch. // Object keys are the font family, values are list of plain elements. elementQueue = {}; // Add to the font queue(no dupes) function addToFontQueue( value ) { if ( $.inArray( value, fontQueue ) < 0 ) { fontQueue.push( value ); } } // Add to the font queue function addToElementQueue( element, fontFamily ) { elementQueue[fontFamily] = elementQueue[fontFamily] || []; elementQueue[fontFamily].push( element ); } $elements.each( function( i, element ) { var fontFamilyStyle, fontFamily, $element = $( element ); if ( $element.is( webfonts.options.exclude ) ) { return; } // Note: it depends on the browser whether this returns font names // which don't exist. In Chrome it does, while in Opera it doesn't. fontFamilyStyle = $element.css( 'fontFamily' ); // Note: It is unclear whether this can ever be falsy. Maybe also // browser specific. if ( fontFamilyStyle ) { // if it is overridable, override. always. if ( webfonts.isOverridable( fontFamilyStyle ) ) { fontFamily = webfonts.getFont( element.lang || webfonts.language ); // We do not have fonts for all languages if ( fontFamily ) { addToFontQueue( fontFamily ); addToElementQueue( element, fontFamily ); } return; } else { fontFamily = fontFamilyStyle.split( ',' )[0]; // Remove the ' and " characters if any. fontFamily = $.trim( fontFamily.replace( /["']/g, '' ) ); addToFontQueue( fontFamily ); } } // Load and apply fonts for other language tagged elements (batched) if ( element.lang && element.lang !== webfonts.language ) { // language differs. We may want to apply a different font. if ( webfonts.hasExplicitFontStyle ( $element ) && !webfonts.isOverridable( fontFamilyStyle ) ) { // respect the explicit font family style. Do not override. // This style may be from css, inheritance, or even from // browser settings. return; } else { fontFamily = webfonts.getFont( element.lang, element.className.split(/\s+/) ); } if ( !fontFamily ) { // No font preference for the language. // Check if we need to reset for this language. // If the font of the parent element, to which webfonts were applied, // remained the same, there is no need to reset. if ( webfonts.$element.css( 'fontFamily' ) !== webfonts.originalFontFamily ) { // The parent font changed. // Is there an inheritance? // Is the font for this element the same as parent's font? if ( fontFamilyStyle === webfonts.$element.css( 'fontFamily' ) ) { // Break inheritance of the font from the parent element // by applying the original font to this element fontFamily = webfonts.originalFontFamily; } } } // We do not have fonts for all languages if ( fontFamily ) { addToFontQueue( fontFamily ); addToElementQueue( element, fontFamily ); } } } ); // Process in batch the accumulated fonts and elements this.load( fontQueue ); $.each( elementQueue, function( fontFamily, elements ) { webfonts.apply( fontFamily, $( elements ) ); } ); }, /** * Find out whether an element has explicit non generic font family style * For the practical purpose we check whether font is same as top element * or having any of generic font family * http://www.w3.org/TR/CSS2/fonts.html#generic-font-families * @param {jQuery} $element * @return {boolean} */ hasExplicitFontStyle: function ( $element ) { var elementFontFamily = $element.css( 'fontFamily' ); // whether the font is inherited from top element to which plugin applied return this.$element.css( 'fontFamily' ) !== elementFontFamily // whether the element has generic font family && ( $.inArray( elementFontFamily, [ 'monospace', 'serif', 'cursive', 'fantasy', 'sans-serif' ] ) < 0 ); }, /** * Check whether the give font family is overridable or not. jquey.webfonts * by default does not override any font-family styles other than generic * font family styles(See hasExplicitFontStyle method) * @param {string} fontFamily * @return {boolean} Whether the given fontFamily is overridable or not. */ isOverridable: function( fontFamily ) { var overridableFontFamilies = [ 'monospace', 'serif', 'cursive', 'fantasy', 'sans-serif' ]; $.merge( overridableFontFamilies, this.options.overridableFontFamilies ); // Browsers like FF put space after comma in font stack. Chrome does not. // Normalise it by removing the spaces and quotes overridableFontFamilies = $.map( overridableFontFamilies, function( item ) { return item.replace( /[\s'"]/g, '' ); } ); fontFamily = fontFamily.replace( /[\s'"]/g, '' ); return $.inArray( fontFamily, overridableFontFamilies ) >= 0; }, /** * List all fonts for the given language * * @param {String} [language] Language code. If undefined all fonts will be listed. * @return {Array} List of font family names. */ list: function( language ) { var fontName, fontNames = []; if ( language ) { fontNames = this.repository.languages[language] || []; } else { for ( fontName in this.repository.fonts ) { if ( this.repository.fonts.hasOwnProperty( fontName ) ) { fontNames.push( fontName ); } } } return fontNames; }, /** * List all languages supported by the repository * * @return {Array} List of language codes */ languages: function() { var language, languages = []; for ( language in this.repository.languages ) { if ( this.repository.languages.hasOwnProperty( language ) ) { languages.push( language ); } } return languages; }, /** * Set the font repository * * @param {Object} repository The font repository. */ setRepository: function( repository ) { this.repository = $.extend( WebFonts.repository, repository ); }, /** * Reset the font-family style. */ reset: function() { this.$element.find( '.webfonts-changed' ) .removeClass( '.webfonts-changed' ) .css( 'font-family', '' ); this.apply( this.originalFontFamily ); }, /** * Unbind the plugin */ unbind: function() { this.$element.data( 'webfonts', null ); }, /** * Construct the CSS required for the font-family. * * @param {String} fontFamily The font-family name * @param {String} [variant] The font variant, eg: bold, italic etc. Default is normal. * @return {String} CSS */ getCSS: function( fontFamily, variant ) { var webfonts, base, version, versionSuffix, fontFaceRule, userAgent, fontStyle, fontFormats, fontconfig = this.repository.get( fontFamily ); variant = variant || 'normal'; if ( variant !== 'normal' ) { if ( fontconfig.variants !== undefined && fontconfig.variants[variant] ) { fontconfig = this.repository.get( fontconfig.variants[variant] ); } } if ( !fontconfig ) { return false; } base = this.repository.base; version = fontconfig.version; versionSuffix = '?version=' + version; fontFaceRule = '@font-face { font-family: \'' + fontFamily + '\';\n'; userAgent = window.navigator.userAgent; fontStyle = fontconfig.fontstyle || 'normal'; fontFormats = []; if ( fontconfig.eot ) { fontFaceRule += '\tsrc: url(\'' + base + fontconfig.eot + versionSuffix + '\');\n'; } fontFaceRule += '\tsrc: '; // If the font is present locally, use it. if ( userAgent.match( /Android 2\.3/ ) === null ) { // Android 2.3.x does not respect local() syntax. // http://code.google.com/p/android/issues/detail?id=10609 fontFaceRule += 'local(\'' + fontFamily + '\'),'; } if ( fontconfig.woff2 ) { fontFormats.push( '\t\turl(\'' + base + fontconfig.woff2 + versionSuffix + '\') format(\'woff2\')' ); } if ( fontconfig.woff ) { fontFormats.push( '\t\turl(\'' + base + fontconfig.woff + versionSuffix + '\') format(\'woff\')' ); } if ( fontconfig.svg ) { fontFormats.push( '\t\turl(\'' + base + fontconfig.svg + versionSuffix + '#' + fontFamily + '\') format(\'svg\')' ); } if ( fontconfig.ttf ) { fontFormats.push( '\t\turl(\'' + base + fontconfig.ttf + versionSuffix + '\') format(\'truetype\')' ); } fontFaceRule += fontFormats.join() + ';\n'; if ( fontconfig.fontweight ) { fontFaceRule += '\tfont-weight:' + fontconfig.fontweight + ';'; } if ( fontconfig.fontstyle !== undefined ) { fontFaceRule += '\tfont-style:' + fontconfig.fontstyle + ';'; } else { fontFaceRule += '\tfont-style: normal;'; } fontFaceRule += '}\n'; webfonts = this; if ( fontconfig.variants !== undefined ) { $.each( fontconfig.variants, function ( variant ) { fontFaceRule += webfonts.getCSS( fontFamily, variant ); } ); } return fontFaceRule; } }; $.fn.webfonts = function( option ) { return this.each( function() { var $this = $( this ), data = $this.data( 'webfonts' ), options = typeof option === 'object' && option; if ( !data ) { $this.data( 'webfonts', ( data = new WebFonts( this, options ) ) ); } if ( typeof option === 'string' ) { data[option](); } } ); }; $.fn.webfonts.defaults = { repository: WebFonts.repository, // Default font repository fontStack: [ 'Helvetica', 'Arial', 'sans-serif' ], // Default font fallback exclude: '', // jQuery selectors to exclude overridableFontFamilies: [] }; $.fn.webfonts.Constructor = WebFonts; // Private methods for the WebFonts prototype /** * Create a new style tag and add it to the DOM. * * @param {String} css */ function injectCSS( css ) { var s = document.createElement( 'style' ); // Insert into document before setting cssText document.getElementsByTagName( 'head' )[0].appendChild( s ); if ( s.styleSheet ) { s.styleSheet.cssText = css; // IE } else { // Safari sometimes borks on null s.appendChild( document.createTextNode( String( css ) ) ); } } } )( jQuery, window, document );