Repository: typekit/webfontloader
Branch: master
Commit: 117e48d521d6
Files: 110
Total size: 457.7 KB
Directory structure:
gitextract_e9i9r2bx/
├── .gitignore
├── .travis.yml
├── CHANGELOG
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── bin/
│ └── webfontloader-demos
├── bower.json
├── browsers.json
├── externs.js
├── lib/
│ ├── webfontloader/
│ │ ├── demo/
│ │ │ ├── public/
│ │ │ │ ├── basic.css
│ │ │ │ ├── blank.html
│ │ │ │ ├── custom-iframe.html
│ │ │ │ ├── custom.html
│ │ │ │ ├── event-css-active-multiple.html
│ │ │ │ ├── event-css-active.html
│ │ │ │ ├── event-css-inactive.html
│ │ │ │ ├── event-css-loading.html
│ │ │ │ ├── event-js-active.html
│ │ │ │ ├── event-js-font-active.html
│ │ │ │ ├── event-js-loading.html
│ │ │ │ ├── events-variations.html
│ │ │ │ ├── events.html
│ │ │ │ ├── fontdeck.html
│ │ │ │ ├── fontwatchrunner-default-fonts.html
│ │ │ │ ├── google-css.html
│ │ │ │ ├── google-iframe.html
│ │ │ │ ├── google.html
│ │ │ │ ├── ie-fast-js.html
│ │ │ │ ├── ie-slow-js.html
│ │ │ │ ├── ie-slow-link.html
│ │ │ │ ├── index.html
│ │ │ │ ├── monotype-iframe.html
│ │ │ │ ├── monotype.html
│ │ │ │ ├── typekit-iframe.html
│ │ │ │ ├── typekit-variations.html
│ │ │ │ └── typekit.html
│ │ │ └── server.rb
│ │ └── modules.rb
│ └── webfontloader.rb
├── package.json
├── spec/
│ ├── core/
│ │ ├── cssclassname_spec.js
│ │ ├── domhelper_spec.js
│ │ ├── eventdispatcher_spec.js
│ │ ├── font_spec.js
│ │ ├── fontmoduleloader_spec.js
│ │ ├── fontruler_spec.js
│ │ ├── fontwatcher_spec.js
│ │ ├── fontwatchrunner_spec.js
│ │ ├── nativefontwatchrunner_spec.js
│ │ ├── size_spec.js
│ │ └── webfont_spec.js
│ ├── deps.js
│ ├── fixtures/
│ │ ├── external_script.js
│ │ ├── external_stylesheet.css
│ │ └── fonts/
│ │ ├── LICENSE.txt
│ │ ├── nullfont.css
│ │ ├── nullfont1.css
│ │ ├── nullfont2.css
│ │ ├── nullfont3.css
│ │ ├── sourcesans.otf
│ │ ├── sourcesansa.css
│ │ ├── sourcesansb.css
│ │ ├── sourcesansc.css
│ │ ├── sourcesansd.css
│ │ ├── sourcesansdup1.css
│ │ └── sourcesansdup2.css
│ ├── index.html
│ └── modules/
│ ├── custom_spec.js
│ ├── fontdeck_spec.js
│ ├── google/
│ │ ├── fontapiparser_spec.js
│ │ ├── fontapiurlbuilder_spec.js
│ │ └── googlefontapi_spec.js
│ ├── monotype_spec.js
│ └── typekit_spec.js
├── src/
│ ├── closure.js
│ ├── core/
│ │ ├── cssclassname.js
│ │ ├── domhelper.js
│ │ ├── eventdispatcher.js
│ │ ├── font.js
│ │ ├── fontmodule.js
│ │ ├── fontmoduleloader.js
│ │ ├── fontruler.js
│ │ ├── fontwatcher.js
│ │ ├── fontwatchrunner.js
│ │ ├── initialize.js
│ │ ├── nativefontwatchrunner.js
│ │ ├── stylesheetwaiter.js
│ │ └── webfont.js
│ ├── modules/
│ │ ├── custom.js
│ │ ├── fontdeck.js
│ │ ├── google/
│ │ │ ├── fontapiparser.js
│ │ │ ├── fontapiurlbuilder.js
│ │ │ └── googlefontapi.js
│ │ ├── monotype.js
│ │ └── typekit.js
│ └── modules.yml
├── tools/
│ ├── compiler/
│ │ ├── base.js
│ │ └── compiler.jar
│ ├── jasmine/
│ │ ├── MIT.LICENSE
│ │ ├── jasmine-html.js
│ │ ├── jasmine.css
│ │ └── jasmine.js
│ ├── jasmine-browserstack/
│ │ └── jasmine-browserstack.js
│ └── jasmine-phantomjs/
│ ├── jasmine-phantomjs.js
│ └── terminal-reporter.js
├── webfontloader.gemspec
└── webfontloader.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
.bundle
*~
target
tmp
_site
pkg
================================================
FILE: .travis.yml
================================================
before_install:
- wget https://s3.amazonaws.com/travis-phantomjs/phantomjs-2.0.0-ubuntu-12.04.tar.bz2
- tar -xjf phantomjs-2.0.0-ubuntu-12.04.tar.bz2
- sudo mv /usr/local/phantomjs/bin/phantomjs /usr/local/phantomjs/bin/phantomjs1
- sudo mv phantomjs /usr/local/phantomjs/bin/phantomjs
language: node_js
================================================
FILE: CHANGELOG
================================================
v1.6.28 (March 27, 2017)
* Clear timer timeout when promise resolves.
v1.6.27 (November 29, 2016)
* Disable native font loading for Safari 10
v1.6.26 (July 8, 2016)
* Add support for latin extended in the Google Fonts module.
v1.6.25 (July 7, 2016)
* Add "loadAllFonts" option to Monotype module.
* Add documentation on how to use Web Font Loader with Edge Web Fonts.
v1.6.24 (March 20, 2016)
* Fix type annotation in DomHelper.
v1.6.23 (March 20, 2016)
* Add bower.json
* Small code rewrite in order to workaround a bad type cast (Closure Compiler
is correct, but reality doesn't always match).
v1.6.22 (February 29, 2016)
* Fix bug that caused long loading fonts to get stuck on wf-loading because
the timeout never fired (or took a really long time).
v1.6.21 (February 2, 2016)
* Fix bug in Google module that caused non-latin to fail on browsers that
support the native font loading API.
* Fix bug in IE8 where font events sometimes failed due to a timing issue.
v1.6.20 (January 4, 2016)
* Add all source files to the npm package.
v1.6.19 (January 4, 2016)
* Add src/core files to npm package.
v1.6.18 (January 4, 2016)
* Version bump.
v1.6.17 (January 4, 2016)
* Upgrade closure compiler and base.js to latest version (no code changes).
v1.6.16 (December 8, 2015)
* Add support for detecting font loads even if the font isn't in the CSSOM yet (workaround for a Chrome bug).
v1.6.15 (November 26, 2015)
* Temporarily disable the native font loading API in Firefox 42 and below due to a bug.
v1.6.14 (November 24, 2015)
* Change media type check to be more reliable on old IE.
v1.6.13 (November 24, 2015)
* Fix media type check in IE/Edge.
v1.6.12 (November 23, 2015)
* Fix bug that sometimes caused wf-inactive to show when asynchronously loading CSS.
v1.6.11 (November 17, 2015)
* Only include production build in npm package
* Fix bug that caused duplicate font loads to fail (using the native font loading code)
v1.6.10 (October 9, 2015)
* Fix compile warning
v1.6.9 (October 9, 2015)
* Fix native font load detection when combined with asynchronous CSS loading
* Fix support for font family names starting with a number
v1.6.8 (October 1, 2015)
* Add support for the native font loading API.
v1.6.7 (September 24, 2015)
* Update Monotype module
v1.6.6 (August 7, 2015)
* Fix weird check in insertInto that disallowed adding a link to an empty head element.
* Load fonts asynchronously for the Typekit module.
* Remove font rulers and trigger callback asynchronously to avoid unnecessary reflows.
v1.6.5 (July 25, 2015)
* Optimise layout calculations
* Fix Rubygems source
* Simplify internal Google module code
v1.6.4 (July 9, 2015)
* Optimise reflows when setting up test spans.
v1.6.3 (June 1, 2015)
* Fix invalid references to global this (window).
v1.6.2 (May 29, 2015)
* No changes.
v1.6.1 (May 29, 2015)
* Automatically update npm version.
v1.6.0 (May 28, 2015)
* Remove user agent string sniffing. From now on the Web Font Loader will always try to load fonts.
v1.5.21 (May 26, 2015)
* No changes.
v1.5.20 (May 26, 2015)
* Use setTimeout instead of window.setTimeout
v1.5.19 (May 24, 2015)
* Add UMD wrapper
v1.5.18 (April 15, 2015)
* Add configuration option to enable asynchronous CSS (internal API)
v1.5.17 (April 14, 2015)
* Load CSS files asynchronously
v1.5.16 (March 30, 2015)
* Optimise DOM performance in the font watcher.
v1.5.15 (March 25, 2015)
* Increase offset position for ruler so scrollbars are less likely to be triggered by large test strings
v1.5.14 (January 23, 2015)
* Refactor EventDispatcher
v1.5.13 (January 20, 2015)
* Remove unused exports
* Move definition of FontTestStrings to where it is first used
v1.5.12 (January 19, 2015)
* Revert using visibility hidden (caused strange artifacts on some sites on iOS browsers)
* Fix compiler warnings
v1.5.11 (January 7, 2015)
* Add support for explicitly setting the protocol
v1.5.10 (December 18, 2014)
* Fix parameters passed to Typekit KitJS
v1.5.9 (December 18, 2014)
* Use visibility hidden instead of off-screen positioning
* Decouple Typekit internal API
* Disable native font loading (see comment)
v1.5.8 (November 17, 2014)
* Add prebuilt library to repository
v1.5.7 (November 17, 2014)
* Decrease default timeout to 3 seconds
* Add support for native font loading
v1.5.6 (September 3, 2014)
* Fix double inactive event when all modules fail to load
v1.5.5 (June 5, 2014)
* Add support for the Chromecast browser
v1.5.4 (May 2, 2014)
* Add support for the PlayStation 4 browser
v1.5.3 (April 8, 2014)
* Prevent a potential FOUT when setting the font event classes.
* Add "display:block" to test rulers so "* { display: none; }" doesn't override it
v1.5.2 (January 3, 2014)
* Add Copyright, License and Version to the compiled output file(s)
* Fix small bug in Google Font module that rejected font variations with dashes
v1.5.1 (October 15, 2013)
* Dispatch wf-loading event synchronously instead of waiting for asynchronous modules
* Update README explaining how to use custom fonts without a stylesheet
* Add license information to gemspec
v1.5.0 (September 9, 2013)
* Correctly categorize Internet Explorer 11 as an Internet Explorer version.
* Add support for custom test strings in the custom module.
* Various small bug fixes to the test suite so it runs on all browsers automatically.
v1.4.12 (August 21, 2013)
* Cleared up initialization code and made all modules optional and configurable at compile time
v1.4.11 (August 8, 2013)
* Remove Ascender module (according to Monotype the service has now been shutdown completely.)
* Switch Monotype module to use fast.fonts.net instead of fast.fonts.com
* Update license
v1.4.10 (July 31, 2013)
* Add loadStylesheet method with optional callback that replaces createCssLink
* Remove supportForStyle check because it breaks XHTML/SVG and is no longer necessary
* Fix inactive never called when script loading times out
* Move test assets into fixtures directory
v1.4.9 (July 24, 2013)
* Disable terminal spec report when not using PhantomJS
* Remove obsolete namespace declaration
v1.4.8 (June 24, 2013)
* Add support for the Chromium based Opera browser
* Change the debug task to do a Closure Compiler debug build
* Fix a global variable leak in the compiled output
* Add support for the PhantomJS user agent string
v1.4.7 (June 6, 2013)
* Fix backwards compatibility with version strings for Chrome
* Restore specs that test against version strings for backwards compatibility with the old UserAgent API.
v1.4.6 (May 29, 2013)
* Merge font watching strategies from core and the Google module
* Add support for the Samsung Galaxy S4 user agent string
v1.4.5 (May 23, 2013)
* Move modules into their own namespace
* Add new methods into DomHelper and add specs for all DomHelper methods
* Rename watch method so as not to conflict with Firefox Object.prototype.watch
v1.4.4 (May 22, 2013)
* Change the UserAgent API so that it is backwards compatible with older Typekit kits.
v1.4.3 (May 16, 2013)
* UserAgent now maintains version numbers as instances of the Version class.
v1.4.2 (April 11, 2013)
* Namespace is now configurable at compile time
* BrowserInfo and UserAgent are exported so Typekit KitJS and the standalone webfontloader can share data
* Add "aria-hidden=true" to test spans so screen readers do not read test spans
* Fix passing the test strings from the modules to core.
v1.4.1 (April 8, 2013)
* Internal rewrite of font and variations
* Fix for the configurable timeout on the Google module
v1.4.0 (March 28, 2013)
* Stop exporting the `addModule` API to dynamically add modules (it didn't work anyway.)
* Stop checking the height when monitoring for font load. This turned out to be inconsistent across platforms.
v1.3.2 (March 27, 2013)
* Add support for the Amazon 1 and 2+ browsers.
v1.3.1 (March 14, 2013)
* Change code to use explicit dependencies
* Fix unit tests in older browsers
* Fix google/FontApiParser.js to work in IE <= 8
v1.3.0 (February 28, 2013)
* New optional configuration parameter `timeout` which lets you customize the default timeout.
* Change the Typekit module to use `use.typekit.net` instead of `use.typekit.com`.
* Disable height check on OSX and iOS WebKit based browsers which suffer from a metrics bug when loading web fonts.
v1.2.1 (February 26, 2013)
* Fix the possibility of test strings wrapping to a new line and thereby breaking font watching.
* Change the FontWatchRunner to not create DOM elements until it is started.
* Fix the possibility of extraneous whitespace in class names.
* Add a cache buster parameter to the Monotype/Fonts.com module.
* Fix the case where there are no fonts to load. Webfontloader will now fire the inactive event correctly.
* Test suite now uses the active browser to test font watching in addition to the mocked font watching tests.
* Test suite is now using Jasmine instead of JSTestDriver.
v1.2.0 (January 30, 2013)
* Improved font watching for browsers with the WebKit web font fallback bug
* Improved font watching in general by comparing both width and height
* UserAgent user interface has changed with the introduction of a BrowserInfo object that contains information derived from the user agent string
* The custom module now supports loading font variations
v1.1.2 (January 21, 2013)
* Added parameter to Google module to do character based subsetting.
* Made WebKit useragent check less strict about capitalization for LG L160 phones that apparently use 'AppleWebkit' instead of 'AppleWebKit' in their useragent strings.
v1.1.1 (December 12, 2012)
* Added the ability to recognize BlackBerry devices, which have web font support (WOFF) in platform version 10+
* Added a new browser name "BuiltinBrowser" to recognize built-in browsers on mobile platforms which we previously called "Safari" (applies to Android's "Browser" app and BlackBerry's built-in browser)
* Fixed a bug in the Monotype module which caused a double active event in IE9 and no active event in IE8 (reported in issue #64)
* Fixed some typos in the demo pages
v1.1.0 (December 5, 2012)
* Adds the ability to load fonts into a same-origin child window or iframe using the new optional `context` configuration option (thanks to @ryanwolf of Google).
* Updates the demos to fix broken stuff and demonstrate the new context option in action.
* DomHelper interface changed to take the main window and an optional separate window for loading.
* Methods added to retrieve both windows and get the correct protocol for assets from the location's protocol.
v1.0.31 (September 11, 2012)
* Improvements to Google's module to recognize more variation descriptor formats (such as 100italic and 200n).
v1.0.30 (August 17, 2012)
* Added support for detecting the Windows Phone platform in UserAgentParser, which supports web fonts at version 8 and above.
* Changes to make the global namespace of the library easier to configure when used in 3rd-party projects. (Thanks cbosco!)
v1.0.29 (July 26, 2012)
* Added test to ensure Firefox for Android is properly detected as "Firefox" by UserAgentParser.
* Added test to ensure Opera Mobile for Android is properly detected as "Opera" by UserAgentParser.
* Changed detection so that Chrome for iOS is detected as "Chrome" instead of "Safari".
* Changed detection so that Opera Mini is correctly detected as "OperaMini" (without font support) instead of "Opera" (with font support).
* Fixed a bug in Google Web Fonts module when requesting a font family with character sets and no variations.
* Scaled back the number of fall back fonts used in the Google Web Fonts font watching code.
v1.0.28 (June 4, 2012)
* Added support for detecting the Chrome OS platform ("CrOS") in the UserAgentParser.
v1.0.27 (April 20, 2012)
* Updated DomHelper to not require a UserAgent instance. Feature-detection is not used instead of UA detection for switching DOM methods.
v1.0.26 (February 8, 2012)
* Updated the included version of the Closure JS compiler jar to 1741, to handle newer annotation styles.
* Added a missing param annotation for the new FontWatcher.watch argument.
* Added param annotations for Google's custom FontWatchRunner implementation.
* Updated the Google Web Fonts API parser to accept family names with a plus sign.
v1.0.25 (February 7, 2012)
* Updated the user agent parser to recognize Chrome for Android properly as a Chrome browser.
v1.0.24 (January 9, 2012)
* Updated the standard test string from "BESs" to "BESbswy" for more width variability.
* Improved Google's custom FontWatchRunner implementation for Webkit to work around an issue where the browser reports the 400 weight's width when it is already loaded.
v1.0.23 (November 29, 2011)
* Made the FontWatchRunner implementation configurable on a module-by-module basis.
* Added a new .start() method to FontWatchRunner to actually kick off font loading detection.
* Added a different FontWatchRunner implementation that Google's module uses to work around a Webkit browser bug. This implementation won't trigger active early, but may trigger active much later, so it's not the default for all modules.
* Updated the implementation of Fontdeck's module to defer loading responsibility to their JS.
v1.0.22 (July 1, 2011)
* Fixed a bug in Webkit-based browsers with font detection where active would trigger without the font actually being active yet.
* Increased the frequency of checking the widths of the font watcher spans.
v1.0.21 (June 17, 2011)
* Added a protocol detect for the Typekit module so JS is loaded securely on secure pages. Thanks to bee525 for the pull request.
v1.0.20 (March 30, 2011)
* Changed CSS style for hidden span so it's not affected by inline or floated elements at the end of the body
v1.0.19 (March 3, 2011)
* Add a module for Monotype.
v1.0.18 (January 24, 2011)
* Improve behavior of CSS classes over multiple loads on a single page. (Documented in docs/EVENTS.md)
* Add support for international subsets in the Google module.
* Add a module for Fontdeck.
v1.0.17 (December 1, 2010)
* Changed CSS style for hidden span in order to be less affected by environment
* Removed restriction on iPhone/iPad/iPod in the google modules
v1.0.16 (November 18, 2010)
* Fix a bug where we fail to detect that fonts have loaded if they have the same width as the fallback font.
v1.0.15 (October 14, 2010)
* Fix an error parsing platform version in IE, when it has no items following the platform in the UA string.
v1.0.14 (October 14, 2010)
* Bugfix: fixed IE issue in google module.
v1.0.13 (September 30, 2010)
* Added support for detecting Adobe Air.
v1.0.12 (September 30, 2010)
* Bugfix: google module, change the url builder to handle integrations.
v1.0.10 (September 24, 2010)
* Bugfix: extra alert
v1.0.10 (September 22, 2010)
* Add support for customizable FontWatcher test string, for international
fonts.
v1.0.9 (September 10, 2010)
* Bugfix: of the bug fix
v1.0.8 (September 10, 2010)
* Bugfix: fix type definitions
v1.0.7 (August 31, 2010)
* Fix that wf-loading was not removed in the case of wf-inactive because of
a timeout.
* Add UserAgent#getDocumentMode to aid in determining font support in IE.
v1.0.6 (July 20, 2010)
* Add JsDoc comments and type annotations for the Closure compiler. Fixes
several small bugs caught by the compiler in doing so.
v1.0.5 (July 12, 2010)
* webfont.UserAgent now provides getPlatformVersion. WebFont Loader is now
packaged as a ruby gem.
v1.0.4 (June 14, 2010)
* Add a module for Ascender's Fonts Live.
v1.0.3 (June 6, 2010)
* IE fixes.
v1.0.2 (June 1, 2010)
* Added a way of loading the WebFont Loader script asynchronously.
v1.0.1 (May 20, 2010)
* Fix namespace pollution by wrapping all of the code in a closure.
v1.0.0 (May 19, 2010)
* Initial release!
* Modules: google, typekit, custom
* Events: loading, active, inactive, fontloading, fontactive, fontintactive
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
Please open [an issue](https://github.com/typekit/webfontloader/issues) if you find or suspect any problems. Sample pages and test cases are greatly appreciated.
## Development requirements
You'll need a few rubygems to run the tests, demo server, and other rake tasks, which should be installed with [Bundler](http://gembundler.com/).
$ gem install bundler
$ bundle install
To run the tests in a headless WebKit you will also need to have [PhantomJS](http://www.phantomjs.org) installed. You can install PhantomJS by downloading a binary or using HomeBrew.
$ brew install phantomjs
## Building
To build a JS file from source, just run rake:
$ rake
If you want to build a JS file with only specific modules you can specify them on the command line:
$ rake compile['custom google typekit']
This will compile a JS file with only the `custom`, `google` and `typekit` modules. The available modules are: `custom`, `google`, `typekit`, `ascender`, `monotype`, `fontdeck`. By default all modules are included.
## Demos
A full suite of demo pages is included in this source. Here you can find some
live examples using the JS and CSS events. Run the demo server with:
$ rake demo
You can also run the demos with uncompressed, debuggable code to aid in
development. Just start the server in dev mode:
$ rake demodev
Browse the demos [source code](http://github.com/typekit/webfontloader/blob/master/lib/webfontloader/demo/public).
## Testing
Web Font Loader has an extensive test suite that runs via Jasmine. The test suite
should be passing before submitting a pull request, and new tests should be added for any new functionality.
To run tests, open up `spec/index.html` in a browser and check the results. The
test suite will run automatically. Again, before submitting a pull request
please run the test suite in multiple browsers and list them in the pull request.
To run tests in a headless WebKit using [PhantomJS](http://www.phantomjs.org) run:
$ rake test
================================================
FILE: Gemfile
================================================
source 'https://rubygems.org'
gemspec
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
================================================
FILE: README.md
================================================
# Web Font Loader
Web Font Loader gives you added control when using linked fonts via `@font-face`. It provides a common interface to loading fonts regardless of the source, then adds a standard set of events you may use to control the loading experience. The Web Font Loader is able to load fonts from [Google Fonts](http://www.google.com/fonts/), [Typekit](http://www.typekit.com/), [Fonts.com](http://www.fonts.com/), and [Fontdeck](http://fontdeck.com/), as well as self-hosted web fonts. It is co-developed by [Google](http://www.google.com/) and [Typekit](http://www.typekit.com).
[](https://travis-ci.org/typekit/webfontloader)
## Contents
* [Get started](#get-started)
* [Configuration](#configuration)
* [Events](#events)
* [Timeout](#timeouts)
* [Iframes](#iframes)
* [Modules](#modules)
* [Adobe Edge Web Fonts](#adobe-edge-web-fonts)
* [Custom](#custom)
* [Fontdeck](#fontdeck)
* [Fonts.com](#fontscom)
* [Google](#google)
* [Typekit](#typekit)
* [Browser support](#browser-support)
* [License](#copyright-and-license)
## Get Started
To use the Web Font Loader library, just include it in your page and tell it which fonts to load. For example, you could load fonts from [Google Fonts](http://www.google.com/fonts/) using the Web Font Loader hosted on [Google Hosted Libraries](https://developers.google.com/speed/libraries/) using the following code.
```html
```
Alternatively, you can link to the latest `1.x` version of the Web Font Loader by using `https://ajax.googleapis.com/ajax/libs/webfont/1/webfont.js` as the `script` source. Note that the version in this url is less specific. It will always load the latest `1.x` version, but it also has a shorter cache time to ensure that your page gets updates in a timely manner. For performance reasons, we recommend using an explicit version number (such as `1.6.26`) in urls when using the Web Font Loader in production. You can manually update the Web Font Loader version number in the url when you want to adopt a new version.
Web Font Loader is also available on the [jsDelivr](http://www.jsdelivr.com/projects/webfontloader) & [CDNJS](https://cdnjs.com/libraries/webfont) CDNs.
It is also possible to use the Web Font Loader asynchronously. For example, to load [Typekit](http://www.typekit.com) fonts asynchronously, you could use the following code.
```html
```
Using the Web Font Loader asynchronously avoids blocking your page while loading the JavaScript. Be aware that if the script is used asynchronously, the rest of the page might render before the Web Font Loader is loaded and executed, which can cause a [Flash of Unstyled Text (FOUT)](http://help.typekit.com/customer/portal/articles/6852).
The FOUT can be more easily avoided when loading the Web Font Loader synchronously, as it will automatically set the `wf-loading` class on the HTML element as soon as `Webfont.load` has been called. The browser will wait for the script to load before continuing to load the rest of the content, FOUT is avoided.
Web Font Loader is also available on npm as a CommonJS module. Just `npm install webfontloader` and then require it in your code.
```js
var WebFont = require('webfontloader');
WebFont.load({
google: {
families: ['Droid Sans', 'Droid Serif']
}
});
```
## Configuration
The Web Font Loader configuration is defined by a global variable named `WebFontConfig`, or passed directly to the `WebFont.load` method. It defines which fonts to load from each web font provider and gives you the option to specify callbacks for certain events. When using the asynchronous approach, you must define the global variable `WebFontConfig` before the code that loads the Web Font Loader (as in the example above).
### Events
Web Font Loader provides an event system that developers can hook into. It gives you notifications of the font loading sequence in both CSS and JavaScript.
* `loading` - This event is triggered when all fonts have been requested.
* `active` - This event is triggered when the fonts have rendered.
* `inactive` - This event is triggered when the browser does not support linked fonts *or* if none of the fonts could be loaded.
* `fontloading` - This event is triggered once for each font that's loaded.
* `fontactive` - This event is triggered once for each font that renders.
* `fontinactive` - This event is triggered if the font can't be loaded.
CSS events are implemented as classes on the `html` element. The following classes are set on the `html` element:
```css
.wf-loading
.wf-active
.wf-inactive
.wf---loading
.wf---active
.wf---inactive
```
The `` placeholder will be replaced by a sanitized version of the name of each font family. Spaces and underscores are removed from the name, and all characters are converted to lower case. For example, `Droid Sans` becomes `droidsans`. The `` placeholder is a [Font Variation Description](https://github.com/typekit/fvd). Put simply, it's a shorthand for describing the style and weight of a particular font. Here are a few examples:
```css
/* n4 */
@font-face { font-style: normal; font-weight: normal; }
/* i7 */
@font-face { font-style: italic; font-weight: bold; }
```
Keep in mind that `font-weight: normal` maps to `font-weight: 400` and `font-weight: bold` maps to `font-weight: 700`. If no style/weight is specified, the default `n4` (`font-style: normal; font-weight: normal;`) will be used.
If fonts are loaded multiple times on a single page, the CSS classes continue to update to reflect the current state of the page. The global `wf-loading` class is applied whenever fonts are being requested (even if other fonts are already active or inactive). The `wf-inactive` class is applied only if none of the fonts on the page have rendered. Otherwise, the `wf-active` class is applied (even if some fonts are inactive).
JavaScript events are implemented as callback functions on the `WebFontConfig` configuration object.
```javascript
WebFontConfig = {
loading: function() {},
active: function() {},
inactive: function() {},
fontloading: function(familyName, fvd) {},
fontactive: function(familyName, fvd) {},
fontinactive: function(familyName, fvd) {}
};
```
The `fontloading`, `fontactive` and `fontinactive` callbacks are passed the family name and font variation description of the font that concerns the event.
It is possible to disable setting classes on the HTML element by setting the `classes` configuration parameter to `false` (defaults to `true`).
```javascript
WebFontConfig = {
classes: false
};
```
You can also disable font events (callbacks) by setting the `events` parameter to `false` (defaults to `true`).
```javascript
WebFontConfig = {
events: false
};
```
If both events and classes are disabled, the Web Font Loader does not perform font watching and only acts as a way to insert @font-face rules in the document.
### Timeouts
Since the Internet is not 100% reliable, it's possible that a font will fail to load. The `fontinactive` event will be triggered after 5 seconds if the font fails to render. If *at least* one font successfully renders, the `active` event will be triggered, else the `inactive` event will be triggered.
You can change the default timeout by using the `timeout` option on the `WebFontConfig` object.
```javascript
WebFontConfig = {
google: {
families: ['Droid Sans']
},
timeout: 2000 // Set the timeout to two seconds
};
```
The timeout value should be in milliseconds, and defaults to 3000 milliseconds (3 seconds) if not supplied.
### Iframes
Usually, it's easiest to include a copy of Web Font Loader in every window where fonts are needed, so that each window manages its own fonts. However, if you need to have a single window manage fonts for multiple same-origin child windows or iframes that are built up using JavaScript, Web Font Loader supports that as well. Just use the optional `context` configuration option and give it a reference to the target window for loading:
```javascript
WebFontConfig = {
google: {
families: ['Droid Sans']
},
context: frames['my-child']
};
```
This is an advanced configuration option that isn't needed for most use cases.
## Modules
Web Font Loader provides a module system so that any web font provider can contribute code that allows their fonts to be loaded. This makes it possible to use multiple web font providers at the same time. The specifics of each provider currently supported by the library are documented here.
### Adobe Edge Web Fonts
When using [Adobe Edge Web Fonts](https://edgewebfonts.adobe.com/), you can use the `typekit` module by passing in a catenated list of fonts in the `id` parameter and set the `api` parameter to point to the Edge Web Fonts URL.
```javascript
WebFontConfig = {
typekit: {
id: 'adamina;advent-pro',
api: '//use.edgefonts.net'
}
};
```
### Custom
To load fonts from any external stylesheet, use the `custom` module. Here you'll
need to specify the font family names you're trying to load, and optionally the url of the stylesheet that provides the `@font-face` declarations for those fonts.
You can specify a specific font variation or set of variations to load and watch
by appending the variations separated by commas to the family name separated by
a colon. Variations are specified using [FVD notation](https://github.com/typekit/fvd).
```javascript
WebFontConfig = {
custom: {
families: ['My Font', 'My Other Font:n4,i4,n7'],
urls: ['/fonts.css']
}
};
```
In this example, the `fonts.css` file might look something like this:
```css
@font-face {
font-family: 'My Font';
src: ...;
}
@font-face {
font-family: 'My Other Font';
font-style: normal;
font-weight: normal; /* or 400 */
src: ...;
}
@font-face {
font-family: 'My Other Font';
font-style: italic;
font-weight: normal; /* or 400 */
src: ...;
}
@font-face {
font-family: 'My Other Font';
font-style: normal;
font-weight: bold; /* or 700 */
src: ...;
}
```
If your fonts are already included in another stylesheet you can also leave out the `urls` array and just specify font family names to start font loading. As long as the names match those that are declared in the `families` array, the proper loading classes will be applied to the html element.
```html
```
The custom module also supports customizing the test strings that are used to determine whether or not a font has loaded. This can be used to load fonts with custom subsets or glyphs in the private use unicode area.
```javascript
WebFontConfig = {
custom: {
families: ['My Font'],
testStrings: {
'My Font': '\uE003\uE005'
}
}
};
```
Tests strings should be specified on a per font basis and contain at least one character. If not specified the default test string (`BESbswy`) is used.
### Fontdeck
To use the [Fontdeck](http://fontdeck.com/) module, specify the ID of your website. You can find this ID on the website page within your account settings.
```javascript
WebFontConfig = {
fontdeck: {
id: 'xxxxx'
}
};
```
### Fonts.com
When using [Fonts.com web fonts](http://www.fonts.com/web-fonts/) specify your Project ID.
```javascript
WebFontConfig = {
monotype: {
projectId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
version: 12345, // (optional, flushes the CDN cache)
loadAllFonts: true //(optional, loads all project fonts)
}
};
```
The Fonts.com module has an optional `version` option which acts as a cache-buster, optional `loadAllFonts` loads all project fonts. By default, Fonts.com module loads only fonts used on the page.
### Google
Using [Google's Font API](https://code.google.com/apis/webfonts/docs/getting_started.html), name the font families you'd like to load. You can use the same [syntax](https://developers.google.com/fonts/docs/getting_started#Syntax) as in the Font API to specify styles. Please note that the Google module does not support the FVD syntax that is used in the custom module.
```javascript
WebFontConfig = {
google: {
families: ['Droid Sans', 'Droid Serif:bold']
}
};
```
Sometimes the font you requested doesn't come in the default weight (e.g. 400) and you need to explicitly request the variation you want for font events to work (e.g. `300`, `700`, etc.):
```javascript
WebFontConfig = {
google: {
families: ['Open Sans Condensed:300,700']
}
};
```
If you need to specify character subsets other than the default (e.g.: greek script in addition to latin), you must append the subset string to the requested family string after a colon. The subset string should follow the [Google documentation](https://developers.google.com/fonts/docs/getting_started#Subsets) (subset names separated by commas):
```javascript
WebFontConfig = {
google: {
families: ['Open Sans Condensed:300,700:latin,greek']
}
};
```
You can also supply the `text` parameter to perform character subsetting:
```javascript
WebFontConfig = {
google: {
families: ['Droid Sans', 'Droid Serif'],
text: 'abcdefghijklmnopqrstuvwxyz!'
}
};
```
The `text` subsetting functionality is only available for the Google module.
### Typekit
When using [Typekit](http://www.typekit.com), specify the Kit to retrieve by its ID. You can find the Kit ID within Typekit's Kit Editor interface.
```javascript
WebFontConfig = {
typekit: {
id: 'xxxxxx'
}
};
```
**FYI:** Typekit's own JavaScript is built using the Web Font Loader library and already provides all of the same font event functionality. If you're using Typekit, you should use their embed codes directly unless you also need to load web fonts from other providers on the same page.
## Browser Support
Every web browser has varying levels of support for fonts linked via `@font-face`. Web Font Loader determines support for web fonts is using the browser's user agent string. The user agent string may claim to support a web font format when it in fact does not. This is especially noticeable on mobile browsers with a "Desktop" mode, which usually identify as Chrome on Linux. In this case a web font provider may decide to send WOFF fonts to the device because the real desktop Chrome supports it, while the mobile browser does not. The Web Font Loader is not designed to handle these cases and it defaults to believing what's in the user agent string. Web font providers can build on top of the basic Web Font Loader functionality to handle these special cases individually.
If Web Font Loader determines that the current browser does not support `@font-face`, the `inactive` event will be triggered.
When loading fonts from multiple providers, each provider may or may not support a given browser. If Web Font Loader determines that the current browser can support `@font-face`, and *at least* one provider is able to serve fonts, the fonts from that provider will be loaded. When finished, the `active` event will be triggered.
For fonts loaded from supported providers, the `fontactive` event will be triggered. For fonts loaded from a provider that *does not* support the current browser, the `fontinactive` event will be triggered.
For example:
```javascript
WebFontConfig = {
providerA: 'Family1',
providerB: 'Family2'
};
```
If `providerA` can serve fonts to a browser, but `providerB` cannot, The `fontinactive` event will be triggered for `Family2`. The `fontactive` event will be triggered for `Family1` once it loads, as will the `active` event.
## Copyright and License
Web Font Loader Copyright (c) 2010-2017 Adobe Systems Incorporated, Google Incorporated.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
================================================
FILE: Rakefile
================================================
require 'rubygems'
require 'rake'
require 'date'
#############################################################################
#
# Helper functions
#
#############################################################################
def name
@name ||= Dir['*.gemspec'].first.split('.').first
end
def version
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
end
def date
Date.today.to_s
end
def gemspec_file
"#{name}.gemspec"
end
def gem_file
"#{name}-#{version}.gem"
end
def replace_header(head, header_name)
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
end
#############################################################################
#
# Standard tasks
#
#############################################################################
desc "Open an irb session preloaded with this library"
task :console do
sh "irb -rubygems -r ./lib/#{name}.rb"
end
#############################################################################
#
# Custom tasks (add your own tasks here)
#
#############################################################################
require 'rake/clean'
$LOAD_PATH.unshift File.dirname(__FILE__) + "/lib"
require 'webfontloader'
#
# Setup
#
# Build targets (remove with `rake clean`)
CLEAN.include("target")
CLEAN.include("tmp")
# JsCompiler
JsCompilerJar = "tools/compiler/compiler.jar"
# JS Source dependencies
AllJs = FileList["{src,src-test}/**/*"]
SourceJs = FileList["src/**/*"]
# JS Source loader
@modules = WebFontLoader::Modules.new
#
# Build
#
directory "target"
directory "tmp"
desc "Compile the JavaScript into target/webfont.js"
task :compile, [:modules] => "target/webfont.js"
file "webfontloader.js" => "target/webfont.js" do
cp "target/webfont.js", "webfontloader.js"
end
file "target/webfont.js", [:modules] => SourceJs + ["target"] do |t, args|
args.with_defaults(:modules => 'custom google typekit monotype fontdeck')
modules = args[:modules].split ' '
output_marker = "%output%"
output_wrapper = @modules.js_output_wrapper(output_marker, version)
args = [
["-jar", JsCompilerJar],
["--compilation_level", "ADVANCED_OPTIMIZATIONS"],
["--js_output_file", t.name],
["--output_wrapper", %("#{output_wrapper}")],
["--warning_level", "VERBOSE"],
["--summary_detail_level", "3"],
["--externs", "externs.js"],
"--define goog.DEBUG=false"
]
args.concat modules.map { |m| "--define INCLUDE_" + m.upcase + "_MODULE" }
# Extra args to add warnings.
args.concat([
["--warning_level", "VERBOSE"],
["--summary_detail_level", "1"]
])
source = @modules.all_source_files
args.concat source.map { |f| ["--js", f] }
output = `java #{args.flatten.join(' ')} 2>&1`
$?.success? ? (puts output) : (fail output)
end
desc "Creates debug version into target/webfont.js"
task :debug, [:modules] => "target/webfont_debug.js"
file "target/webfont_debug.js", [:modules] => SourceJs + ["target"] do |t, args|
args.with_defaults(:modules => 'custom google typekit monotype fontdeck')
modules = args[:modules].split ' '
output_marker = "%output%"
output_wrapper = @modules.js_output_wrapper(output_marker, version)
args = [
["-jar", JsCompilerJar],
["--compilation_level", "ADVANCED_OPTIMIZATIONS"],
["--js_output_file", t.name],
["--output_wrapper", %("#{output_wrapper}")],
["--warning_level", "VERBOSE"],
["--summary_detail_level", "3"],
["--externs", "externs.js"],
"--debug=true",
"--formatting=PRETTY_PRINT",
"--formatting=PRINT_INPUT_DELIMITER"
]
args.concat modules.map { |m| "--define INCLUDE_" + m.upcase + "_MODULE" }
# Extra args to add warnings.
args.concat([
["--warning_level", "VERBOSE"],
["--summary_detail_level", "1"]
])
source = @modules.all_source_files
args.concat source.map { |f| ["--js", f] }
output = `java #{args.flatten.join(' ')} 2>&1`
$?.success? ? (puts output) : (fail output)
end
#
# Run
#
desc "BrowserStack tests"
task :bstest do |t|
exec "browserstack-test -u $BROWSERSTACK_USERNAME -p $BROWSERSTACK_PASSWORD -k $BROWSERSTACK_KEY -b browsers.json -t 300 http://localhost:9999/spec/index.html"
end
desc "Test everything"
task :default => [:clean, :gzipbytes, :test]
desc "Run all tests"
task :test do |t|
exec "phantomjs tools/jasmine-phantomjs/jasmine-phantomjs.js spec/index.html"
end
desc "Start the demo server"
task :demo => "target/webfont.js" do |t|
js = t.prerequisites.first
exec "bin/webfontloader-demos -F --compiled_js #{js}"
end
desc "Start the demo server for development"
task :demodev do
exec "bin/webfontloader-demos -F -L --modules"
end
desc "Find out how many bytes the source is"
task :bytes => [:clean, "target/webfont.js"] do |t|
js = t.prerequisites.last
bytes = File.read(js).size
puts "#{bytes} bytes uncompressed"
end
desc "Find out how many bytes the source is when gzipped"
task :gzipbytes => [:clean, "target/webfont.js"] do |t|
require 'zlib'
js = t.prerequisites.last
bytes = Zlib::Deflate.deflate(File.read(js)).size
puts "#{bytes} bytes gzipped"
end
#############################################################################
#
# Packaging tasks
#
#############################################################################
task :release => [:build] do
unless `git branch` =~ /^\* master$/
puts "You must be on the master branch to release!"
exit!
end
sh "git add webfontloader.js"
sh "git commit --allow-empty -a -m 'Release #{version}'"
sh "npm version #{version}"
sh "git push --tags origin master"
sh "gem push pkg/#{name}-#{version}.gem"
sh "npm publish"
end
task :build => :gemspec do
Rake::Task["target/webfont.js"].execute
Rake::Task["webfontloader.js"].execute
sh "mkdir -p pkg"
sh "gem build #{gemspec_file}"
sh "mv #{gem_file} pkg"
end
task :gemspec => :validate do
# read spec file and split out manifest section
spec = File.read(gemspec_file)
head, manifest, tail = spec.split(" # = MANIFEST =\n")
# replace name version and date
replace_header(head, :name)
replace_header(head, :version)
replace_header(head, :date)
# determine file list from git ls-files
files = `git ls-files`.
split("\n").
sort.
reject { |file| file =~ /^\./ }.
reject { |file| file =~ /^(rdoc|pkg)/ }.
map { |file| " #{file.gsub(/\s/, '\ ')}" }.
join("\n")
# piece file back together and write
manifest = " s.files = %w[\n#{files}\n ]\n"
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
File.open(gemspec_file, 'w') { |io| io.write(spec) }
puts "Updated #{gemspec_file}"
end
task :validate do
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
unless libfiles.empty?
puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
exit!
end
unless Dir['VERSION*'].empty?
puts "A `VERSION` file at root level violates Gem best practices."
exit!
end
end
================================================
FILE: bin/webfontloader-demos
================================================
#!/usr/bin/env ruby
require 'rubygems'
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
require 'webfontloader'
begin
require 'webfontloader/demo/server'
rescue LoadError => e
abort "Please gem install sinatra"
end
begin
require 'vegas'
rescue LoadError => e
abort "Please gem install vegas"
end
Vegas::Runner.new(WebFontLoader::Demo::Server, 'font-demos', :host => "localhost") do |runner, opts, app|
opts.on('--compiled_js FILE', "Dynamically build the JS with the given modules") { |file|
app.set :compiled_js, File.read(file)
}
opts.on('--modules [MODULES]', "Dynamically build the JS with the given modules") { |opt_modules|
modules = opt_modules ? opt_modules.split(",") : []
app.set :modules, WebFontLoader::Modules.new(*modules)
}
end
================================================
FILE: bower.json
================================================
{
"name": "webfontloader",
"main": "webfontloader.js",
"description": "Web Font Loader gives you added control when using linked fonts via @font-face.",
"moduleType": ["amd", "node"],
"license": "Apache-2.0",
"ignore": [
"**/.*",
"node_modules",
"spec",
"tools",
"lib",
"bin"
],
"keywords": [
"web",
"fonts",
"webfonts",
"font",
"loader",
"@font-face"
]
}
================================================
FILE: browsers.json
================================================
[
{
"os_version": "XP",
"os": "Windows",
"browser_version": "12.16",
"browser": "opera"
},
{
"os_version": "XP",
"os": "Windows",
"browser_version": "3.6",
"browser": "firefox"
},
{
"os_version": "XP",
"os": "Windows",
"browser_version": "20.0",
"browser": "firefox"
},
{
"os_version": "XP",
"os": "Windows",
"browser_version": "6.0",
"browser": "ie",
"timeout": "300"
},
{
"os_version": "XP",
"os": "Windows",
"browser_version": "7.0",
"browser": "ie",
"timeout": "300"
},
{
"os_version": "XP",
"os": "Windows",
"browser_version": "8.0",
"browser": "ie"
},
{
"os_version": "8",
"os": "Windows",
"browser_version": "10.0 Desktop",
"browser": "ie"
},
{
"os_version": "7",
"os": "Windows",
"browser_version": "9.0",
"browser": "ie"
},
{
"os_version": "Mountain Lion",
"os": "OS X",
"browser_version": "6.0",
"browser": "safari"
},
{
"os_version": "Mountain Lion",
"os": "OS X",
"browser_version": "25.0",
"browser": "chrome"
},
{
"os_version": "Snow Leopard",
"os": "OS X",
"browser_version": "5.0",
"browser": "safari"
},
{
"os_version": "5.0",
"device": "iPad 2 (5.0)",
"os": "ios",
"browser": "Mobile Safari",
"timeout": "300"
},
{
"os_version": "5.1",
"device": "iPad 3rd",
"os": "ios",
"browser": "Mobile Safari",
"timeout": "300"
},
{
"os_version": "6.0",
"device": "iPhone 5",
"os": "ios",
"browser": "Mobile Safari",
"timeout": "300"
},
{
"os_version": "4.3.2",
"device": "iPad 2",
"os": "ios",
"browser": "Mobile Safari",
"timeout": "300"
},
{
"os_version": "2.3",
"device": "Samsung Galaxy S II",
"os": "android",
"browser": "Android Browser",
"timeout": "300"
},
{
"os_version": "2.2",
"device": "Samsung Galaxy S",
"os": "android",
"browser": "Android Browser",
"timeout": "300"
},
{
"os_version": "4.2",
"device": "LG Nexus 4",
"os": "android",
"browser": "Android Browser",
"timeout": "300"
},
{
"os_version": "4.0",
"device": "Samsung Galaxy Nexus",
"os": "android",
"browser": "Android Browser",
"timeout": "300"
},
{
"os_version": "4.1",
"device": "Samsung Galaxy S III",
"os": "android",
"browser": "Android Browser",
"timeout": "300"
}
]
================================================
FILE: externs.js
================================================
/**
* @type {function(function():*)}
*/
var define;
/**
* @type {boolean?}
*/
define.amd;
/**
* @type {Object}
*/
var module;
/**
* @type {Object?}
*/
module.exports;
================================================
FILE: lib/webfontloader/demo/public/basic.css
================================================
body {
line-height: 1;
font-size: 14px;
margin: 10px;
}
h1 {
font-size: 5em;
margin: 0;
}
================================================
FILE: lib/webfontloader/demo/public/blank.html
================================================
Blank page
================================================
FILE: lib/webfontloader/demo/public/custom-iframe.html
================================================
Custom Module
The goal of this page is to use CSS to show a loading message while the
headline's font is loading, then hide the loading message and show the
headline once the font has rendered.
The goal of this page is to verify that the two default font stacks in
FontWatchRunner have different widths on a given platform when rendering the
default test string. The pairs of headings below should render in different
fonts and the results above should indicate that they all have different
widths.
The goal of this page is simply to use fonts via the JavaScript API.
================================================
FILE: lib/webfontloader/demo/public/ie-fast-js.html
================================================
Show me Fonts
The goal of this page is to show non-blocking behavior in MSIE. This
causes IE to load fonts without blocking the entire page.
================================================
FILE: lib/webfontloader/demo/public/ie-slow-js.html
================================================
Show me Fonts
The goal of this page is to restore blocking behavior in MSIE. This causes
IE to block the entire page while loading fonts.
================================================
FILE: lib/webfontloader/demo/public/ie-slow-link.html
================================================
Show me Fonts
Demonstrations of pure CSS and JavaScript-enhanced use of @font-face.
Note that many of these demonstrations use a slow proxy to
increase the amount of time it takes to load a font. We do this to make it
more obvious that the events system is working. It does not represent
real world usage.
Modules
Web Font Loader provides modules to load fonts from many places.
Google / CSS Link: Load fonts from Google with a link tag. Consider this a base case for font loading.
The goal of this page is to show how Typekit fonts load. Note that it uses
a minimal Typekit script in order to reduce dependencies. This script
simply provides the system font 'Georgia' in italic and bold italic
instead of loading a web font.
The goal of this page is to show how Typekit fonts load.
You must load this page on "localhost" in order for the fonts to load.
================================================
FILE: lib/webfontloader/demo/server.rb
================================================
require 'sinatra/base'
require 'open-uri'
module WebFontLoader
module Demo
class Server < Sinatra::Base
DemoRoot = File.expand_path(File.join(File.dirname(__FILE__)))
ProjectRoot = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", ".."))
GoogleApi = "https://fonts.googleapis.com/css"
GoogleFontApi = "https://themes.googleusercontent.com/font"
set :app_file, __FILE__
set :sessions, false
set :static, true
set :modules, nil
set :compiled_js, nil
get '/' do
File.read(File.join(DemoRoot, "public", "index.html"))
end
get '/webfont.js' do
headers 'Content-Type' => "application/javascript"
headers 'Cache-Control' => 'max-age=300'
get_js_code
end
get '/webfont-fontwatchrunner.js' do
headers 'Content-Type' => 'application/javascript'
headers 'Cache-Control' => 'max-age=300'
[
'var webfont = {};',
File.read(File.join(WebFontLoader::ProjectRoot, 'src/core/fontwatchrunner.js'))
]
end
get '/fonts/api' do
url = "#{GoogleApi}?#{env['QUERY_STRING']}"
headers 'Content-Type' => 'text/css'
headers 'Cache-Control' => 'max-age=300'
response = open(url, 'User-Agent' => env['HTTP_USER_AGENT'])
source = response.read
source.gsub!(%r[https://themes.googleusercontent.com/font], '/fonts/font')
source
end
get '/fonts/font' do
sleep 1
url = "#{GoogleFontApi}?#{env['QUERY_STRING']}"
headers 'Cache-Control' => 'max-age=300'
headers 'Content-Encoding' => 'gzip'
response = open(url, 'User-Agent' => env['HTTP_USER_AGENT'])
response.read
end
get %r[/typekit/(\w+)\.js] do |kit_id|
headers 'Content-Type' => 'application/javascript'
headers 'Cache-Control' => 'max-age=300'
case kit_id
when "kitwitharialblack"
families = "['Arial Black']"
variations = "{}"
when "kitwithgeorgia"
families = "['Georgia']"
variations = "{ 'Georgia': ['i4', 'i7' ]}"
else
families = "[]"
variations = "{}"
end
<<-JS
if (window.__webfonttypekitmodule__) {
var module = window.__webfonttypekitmodule__['#{kit_id}'];
if (module) {
module(function(userAgent, configuration, init) {
// Here you may use the userAgent object to determine
// browser support.
init(true, #{families}, #{variations});
});
}
}
JS
end
protected
def get_js_code
if settings.compiled_js
settings.compiled_js
elsif settings.modules
settings.modules.all_source_files.map { |file| File.read(File.join(WebFontLoader::ProjectRoot, file)) }
else
"alert('No JavaScript has been configured in the demo server');"
end
end
end
end
end
================================================
FILE: lib/webfontloader/modules.rb
================================================
module WebFontLoader
class Modules
def initialize(*modules)
@project_root = WebFontLoader::ProjectRoot
@js_src = "src"
@js_test = "src-test"
@modules = modules.empty? ? config.keys : modules
# Make sure 'core' is first.
@modules.unshift "core"
@modules.uniq!
end
attr_reader :modules
attr_accessor :project_root, :js_src, :js_test
def all_source_files
@all_source_files ||= begin
modules.map { |mod| config[mod] }.compact.flatten.map { |f| File.join(js_src, f) }
end
end
def all_test_globs
@all_test_globs ||= begin
js_test_dirs = Dir[File.join(project_root, js_test, "*")].map { |d| File.basename(d) }
js_test_dirs.map { |dir| File.join(js_test, dir, "*.js") if modules.include?(dir) }.compact
end
end
def js_output_wrapper(source, version)
File.read(File.join(js_src, "closure.js")).sub("{{source}}", source).sub("{{version}}", version).gsub(/\n|\r/,"")
end
protected
def config
@config ||= begin
path = File.join(project_root, js_src)
YAML.load_file(File.join(path, "modules.yml"))
end
end
end
end
================================================
FILE: lib/webfontloader.rb
================================================
require 'yaml'
require 'webfontloader/modules'
module WebFontLoader
VERSION = '1.6.28'
ProjectRoot = File.expand_path(File.dirname(__FILE__) + "/..")
end
================================================
FILE: package.json
================================================
{
"name": "webfontloader",
"version": "1.6.28",
"description": "Web Font Loader gives you added control when using linked fonts via @font-face.",
"main": "webfontloader.js",
"scripts": {
"test": "phantomjs tools/jasmine-phantomjs/jasmine-phantomjs.js spec/index.html"
},
"repository": {
"type": "git",
"url": "git://github.com/typekit/webfontloader.git"
},
"keywords": [
"web",
"fonts",
"webfonts",
"font",
"loader",
"@font-face"
],
"files": [
"webfontloader.js",
"src/**/*.js"
],
"contributors": [
"Ryan Carver ",
"Jeremie Lenfant-engelmann ",
"Sean McBride ",
"Bram Stein "
],
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/typekit/webfontloader/issues"
},
"homepage": "https://github.com/typekit/webfontloader",
"devDependencies": {}
}
================================================
FILE: spec/core/cssclassname_spec.js
================================================
describe('CssClassName', function () {
var CssClassName = webfont.CssClassName,
sanitizer = new CssClassName();
describe('#sanitize', function () {
it('should sanitize spaces in names', function () {
expect(sanitizer.sanitize(' My Family ')).toEqual('myfamily');
});
it('should sanitize numbers in names', function () {
expect(sanitizer.sanitize('99 My Family 99')).toEqual('99myfamily99');;
});
it('should sanitize other characters', function () {
expect(sanitizer.sanitize('_My+Family!-')).toEqual('myfamily');
});
});
describe('#build', function () {
it('should build many parts', function () {
expect(sanitizer.build('pre_', 'My Family', '_post')).toEqual('pre-myfamily-post');
});
it('should build some parts', function () {
expect(sanitizer.build('pre!', 'My Family')).toEqual('pre-myfamily');
});
});
describe('#constructor', function () {
it('should use a hyphen as a default separator', function () {
var sanitizer = new CssClassName();
expect(sanitizer.build('pre', 'post')).toEqual('pre-post');
});
it('should use the configured separator', function () {
var sanitizer = new CssClassName('_');
expect(sanitizer.build('pre', 'post')).toEqual('pre_post');
});
});
});
================================================
FILE: spec/core/domhelper_spec.js
================================================
describe('DomHelper', function () {
var DomHelper = webfont.DomHelper,
domHelper = new DomHelper(window);
describe('#createElement', function () {
it('should create an element', function () {
var div = domHelper.createElement('div');
expect(div).not.toBeNull();
});
it('should create an element with inline content', function () {
var div = domHelper.createElement('div', {}, 'moo');
expect(div).not.toBeNull();
expect(div.innerHTML).toEqual('moo');
});
it('should create an element with attributes and inline content', function () {
var div = domHelper.createElement('div', {
style: 'font-size: 42px',
id: 'mySpan'
}, 'hello');
expect(div).not.toBeNull();
expect(div.innerHTML).toEqual('hello');
expect(div.style.fontSize).toEqual('42px');
expect(div.id).toEqual('mySpan');
});
it('should work with augmented Object.prototype', function () {
Object.prototype.evil = function () {};
var div = domHelper.createElement('div', { id: 'augmented' });
var parentDiv = domHelper.createElement('div', { id: 'parentaugmented' });
parentDiv.appendChild(div);
expect(div).not.toBeNull();
expect(!!div.getAttribute('evil')).toBe(false);
expect(-1, parentDiv.innerHTML.indexOf('evil'));
delete Object.prototype.evil;
});
});
describe('#appendClassName', function () {
it('should have added a class name', function () {
var div = domHelper.createElement('div');
domHelper.appendClassName(div, 'moo');
expect(div.className).toEqual('moo');
});
it('should not add duplicate class names', function () {
var div = domHelper.createElement('div');
domHelper.appendClassName(div, 'moo');
domHelper.appendClassName(div, 'meu');
domHelper.appendClassName(div, 'moo');
expect(div.className).toEqual('moo meu');
});
it('should add multiple class names', function () {
var div = domHelper.createElement('div');
domHelper.appendClassName(div, 'moo meu moo');
expect(div.className).toEqual('moo meu moo');
});
it('should normalize spaces and tabs', function () {
var div = domHelper.createElement('div');
domHelper.appendClassName(div, 'meu');
domHelper.appendClassName(div, ' foo ');
expect(div.className).toEqual('meu foo');
});
});
describe('#removeClassName', function () {
it('should remove class names', function () {
var div = domHelper.createElement('div');
domHelper.appendClassName(div, 'meu moo');
expect(div.className).toEqual('meu moo');
domHelper.removeClassName(div, 'meu');
expect(div.className).toEqual('moo');
});
it('should not remove non-existing classes', function () {
var div = domHelper.createElement('div');
domHelper.appendClassName(div, 'moo');
expect(div.className).toEqual('moo');
domHelper.removeClassName(div, 'boo');
expect(div.className).toEqual('moo');
});
});
describe('#updateClassName', function () {
it('should handle optional arguments correctly', function () {
var div = domHelper.createElement('div');
domHelper.appendClassName(div, 'moo');
domHelper.updateClassName(div);
expect(div.className).toEqual('moo');
domHelper.updateClassName(div, [], []);
expect(div.className).toEqual('moo');
domHelper.updateClassName(div, null, null);
expect(div.className).toEqual('moo');
});
it('should have added a class name', function () {
var div = domHelper.createElement('div');
domHelper.updateClassName(div, ['moo']);
expect(div.className).toEqual('moo');
});
it('should not add duplicate class names', function () {
var div = domHelper.createElement('div');
domHelper.appendClassName(div, 'moo');
domHelper.updateClassName(div, ['moo']);
expect(div.className).toEqual('moo');
});
it('should add multiple class names', function () {
var div = domHelper.createElement('div');
domHelper.updateClassName(div, ['moo', 'meu', 'moo']);
expect(div.className).toEqual('moo meu');
});
it('should normalize spaces and tabs', function () {
var div = domHelper.createElement('div');
domHelper.updateClassName(div, ['meu', ' foo']);
expect(div.className).toEqual('meu foo');
});
it('should remove class names', function () {
var div = domHelper.createElement('div');
domHelper.appendClassName(div, 'meu moo');
expect(div.className).toEqual('meu moo');
domHelper.updateClassName(div, null, ['meu']);
expect(div.className).toEqual('moo');
});
it('should remove multiple class names', function () {
var div = domHelper.createElement('div');
domHelper.appendClassName(div, 'meu');
domHelper.appendClassName(div, 'moo');
expect(div.className).toEqual('meu moo');
domHelper.updateClassName(div, null, ['meu', 'moo']);
expect(div.className).toEqual('');
});
it('should not remove non-existing classes', function () {
var div = domHelper.createElement('div');
domHelper.appendClassName(div, 'moo');
expect(div.className).toEqual('moo');
domHelper.updateClassName(div, null, 'boo');
expect(div.className).toEqual('moo');
});
it('should add and remove class names', function () {
var div = domHelper.createElement('div');
domHelper.appendClassName(div, 'moo');
domHelper.appendClassName(div, 'meh');
expect(div.className).toEqual('moo meh');
domHelper.updateClassName(div, ['meu'], ['moo', 'meh']);
expect(div.className).toEqual('meu');
});
it('should update one of many class names', function () {
var div = domHelper.createElement('div');
domHelper.appendClassName(div, 'moo');
domHelper.appendClassName(div, 'meh');
expect(div.className).toEqual('moo meh');
domHelper.updateClassName(div, ['meu'], ['moo']);
expect(div.className).toEqual('meh meu');
});
});
describe('#hasClassName', function () {
var div = null;
beforeEach(function () {
div = domHelper.createElement('div');
domHelper.appendClassName(div, 'moo moo-meu');
});
it('should return true', function () {
expect(domHelper.hasClassName(div, 'moo')).toBe(true);
expect(domHelper.hasClassName(div, 'moo-meu')).toBe(true);
});
it('should return false', function () {
expect(domHelper.hasClassName(div, 'boo')).toBe(false);
expect(domHelper.hasClassName(div, 'meu')).toBe(false);
});
});
describe('#setStyle', function () {
var div = null;
beforeEach(function () {
div = domHelper.createElement('div');
});
it('should set the style correctly', function () {
domHelper.setStyle(div, 'left:3px;top:1px;');
expect(div.style.left).toEqual('3px');
expect(div.style.top).toEqual('1px');
});
});
describe('#createStyle', function () {
var style = null;
beforeEach(function () {
style = domHelper.createStyle('blockquote{font-size:300px}');
domHelper.insertInto('head', style);
});
afterEach(function () {
domHelper.removeElement(style);
});
it('should create a style element', function () {
expect(style).not.toBeNull();
expect(style.nodeName).toEqual('STYLE');
});
it('should set the css content correctly', function () {
var text = style.styleSheet ? style.styleSheet.cssText : style.textContent;
expect(text.replace(/[\s;]/g, '').toLowerCase()).toEqual('blockquote{font-size:300px}');
});
});
describe('#loadStylesheet', function () {
it('should load the stylesheet', function () {
var el = null,
width = null,
link = null;
runs(function () {
el = domHelper.createElement('div', { id: 'TEST_ELEMENT' });
domHelper.insertInto('body', el);
width = el.offsetWidth;
link = domHelper.loadStylesheet('fixtures/external_stylesheet.css');
});
waitsFor(function () {
return width !== el.offsetWidth;
});
runs(function () {
expect(link).not.toBeNull();
expect(link.rel).toEqual('stylesheet');
expect(el.offsetWidth).toEqual(300);
});
});
});
describe('#loadStylesheet with callback', function () {
it('should load the stylesheet', function () {
var el = null,
width = null,
callbackMade = false;
function callback() {
callbackMade = true;
}
runs(function () {
el = domHelper.createElement('div', { id: 'TEST_ELEMENT' });
domHelper.insertInto('body', el);
width = el.offsetWidth;
domHelper.loadStylesheet('fixtures/external_stylesheet.css', callback);
});
waitsFor(function () {
return callbackMade;
});
runs(function () {
expect(el.offsetWidth).toEqual(300);
});
});
});
describe('#loadStylesheet with async and callback', function () {
it('should load the stylesheet', function () {
var el = null,
width = null,
callbackMade = false;
function callback() {
callbackMade = true;
}
runs(function () {
el = domHelper.createElement('div', { id: 'TEST_ELEMENT' });
domHelper.insertInto('body', el);
width = el.offsetWidth;
domHelper.loadStylesheet('fixtures/external_stylesheet.css', callback, true);
});
waitsFor(function () {
return callbackMade;
});
runs(function () {
expect(el.offsetWidth).toEqual(300);
});
});
});
describe('#loadScript', function () {
it('should load the script', function () {
runs(function () {
domHelper.loadScript('fixtures/external_script.js');
});
waitsFor(function () {
return window.EXTERNAL_SCRIPT_LOADED;
}, 'script was never inserted', 1000);
runs(function () {
expect(window.EXTERNAL_SCRIPT_LOADED).toBe(true);
});
});
it('should call the callback', function () {
var called = false,
error = null;
runs(function () {
domHelper.loadScript('fixtures/external_script.js', function (err) {
called = true;
error = err;
});
});
waitsFor(function () {
return called;
}, 'callback was never called', 1000);
runs(function () {
expect(called).toBe(true);
expect(error).toBeFalsy();
});
});
it('should return a script element', function () {
var script = domHelper.loadScript('fixtures/external_script.js');
expect(script).not.toBeNull();
expect(script.nodeName).toEqual('SCRIPT');
});
it('should timeout if the script does not load or is very slow', function () {
var called = false,
error = false;
// Spy on createElement so the all loadScript code is executed but
// the "script" won't actually load.
spyOn(domHelper, 'createElement').andCallFake(function (name) {
return document.createElement('div');
});
runs(function () {
domHelper.loadScript('fixtures/external_script.js', function (err) {
called = true;
error = err;
}, 100);
});
waitsFor(function () {
return called;
});
runs(function () {
expect(called).toBe(true);
expect(error).toBeTruthy();
});
});
});
describe('#getHostname', function () {
it('should return the hostname', function () {
var domHelper = new DomHelper({
location: {
hostname: 'example.com'
}
});
expect(domHelper.getHostName()).toEqual('example.com');
});
it('should return the hostname from the iframe if present', function () {
var domHelper = new DomHelper({
location: {
hostname: 'example.com'
}
}, {
location: {
hostname: 'example.org'
}
});
expect(domHelper.getHostName()).toEqual('example.org');
});
});
describe('#insertInto', function () {
it('should insert an element', function () {
var a = domHelper.createElement('div');
var result = domHelper.insertInto('body', a);
expect(result).toBe(true);
expect(a.parentNode.nodeName).toEqual('BODY');
});
});
describe('#whenBodyExists', function () {
var domHelper = null,
callback = null;
beforeEach(function () {
domHelper = new DomHelper({
document: {
addEventListener: function (event, callback) {
function check() {
if (domHelper.document_.body) {
callback();
} else {
setTimeout(check, 10);
}
}
check();
}
}
});
callback = jasmine.createSpy('callback');
});
it('should wait until the body exists before calling the callback', function () {
runs(function () {
domHelper.whenBodyExists(callback);
});
waits(200);
runs(function () {
domHelper.document_.body = true;
});
waitsFor(function () {
return callback.wasCalled;
}, 'callback was never called', 100);
runs(function () {
expect(callback).toHaveBeenCalled();
});
});
it('should not call the callback if the body is not available', function () {
runs(function () {
domHelper.whenBodyExists(callback);
});
waits(100);
runs(function () {
expect(callback).not.toHaveBeenCalled();
});
});
});
describe('#removeElement', function () {
it('should remove an element', function () {
var a = domHelper.createElement('div'),
b = domHelper.createElement('div');
a.appendChild(b);
var result = domHelper.removeElement(b);
expect(result).toBe(true);
expect(b.parentNode).not.toEqual(a);
});
it('should return false when failing to remove an element', function () {
var a = domHelper.createElement('div');
var result = domHelper.removeElement(a);
expect(result).toBe(false);
});
});
describe('#getMainWindow', function () {
it('should return the main window', function () {
var domHelper = new DomHelper(1, 2);
expect(domHelper.getMainWindow()).toEqual(1);
});
});
describe('#getLoadWindow', function () {
it('should return the load window', function () {
var domHelper = new DomHelper(1, 2);
expect(domHelper.getLoadWindow()).toEqual(2);
});
});
});
================================================
FILE: spec/core/eventdispatcher_spec.js
================================================
describe('EventDispatcher', function () {
var EventDispatcher = webfont.EventDispatcher,
DomHelper = webfont.DomHelper,
Font = webfont.Font,
domHelper = new DomHelper(window),
element = null
eventDispatcher = null,
font = null;
beforeEach(function () {
element = domHelper.getLoadWindow().document.documentElement;
config = {
loading: jasmine.createSpy('loading'),
active: jasmine.createSpy('active'),
inactive: jasmine.createSpy('inactive'),
fontloading: jasmine.createSpy('fontloading'),
fontactive: jasmine.createSpy('fontactive'),
fontinactive: jasmine.createSpy('fontinactive'),
classes: true,
events: true
};
element.className = '';
eventDispatcher = new EventDispatcher(domHelper, config);
font = new Font('My Family', 'n4');
});
describe('#dispatchLoading', function () {
beforeEach(function () {
eventDispatcher.dispatchLoading();
});
it('should call the correct callback', function () {
expect(config.loading).toHaveBeenCalled();
});
it('should set the correct class name', function () {
expect(element.className).toEqual('wf-loading');
});
});
describe('#dispatchFontLoading', function () {
beforeEach(function () {
eventDispatcher.dispatchFontLoading(font);
});
it('should call the correct callback', function () {
expect(config.fontloading).toHaveBeenCalledWith('My Family', 'n4');
});
it('should set the correct class name', function () {
expect(element.className).toEqual('wf-myfamily-n4-loading');
});
});
describe('#dispatchFontInactive', function () {
beforeEach(function () {
eventDispatcher.dispatchFontInactive(font);
});
it('should call the correct callback', function () {
expect(config.fontinactive).toHaveBeenCalledWith('My Family', 'n4');
});
it('should set the correct class name', function () {
expect(element.className).toEqual('wf-myfamily-n4-inactive');
});
});
describe('#dispatchFontInactive - with loading class', function () {
beforeEach(function () {
eventDispatcher.dispatchFontLoading(font);
eventDispatcher.dispatchFontInactive(font);
});
it('should set the correct class name', function () {
expect(element.className).toEqual('wf-myfamily-n4-inactive');
});
});
describe('#dispatchFontInactive - with active class', function () {
beforeEach(function () {
eventDispatcher.dispatchFontActive(font);
eventDispatcher.dispatchFontInactive(font);
});
it('should not append the inactive class name', function () {
expect(element.className).toEqual('wf-myfamily-n4-active');
});
it('should still call the correct callback', function () {
expect(config.fontinactive).toHaveBeenCalledWith('My Family', 'n4');
});
});
describe('#dispatchFontActive', function () {
beforeEach(function () {
eventDispatcher.dispatchFontActive(font);
});
it('should call the correct callback', function () {
expect(config.fontactive).toHaveBeenCalledWith('My Family', 'n4');
});
it('should set the correct class name', function () {
expect(element.className).toEqual('wf-myfamily-n4-active');
});
});
describe('#dispatchFontActive - with loading class', function () {
beforeEach(function () {
eventDispatcher.dispatchFontLoading(font);
eventDispatcher.dispatchFontActive(font);
});
it('should set the correct class name', function () {
expect(element.className).toEqual('wf-myfamily-n4-active');
});
});
describe('#dispatchFontActive - with inactive class', function () {
beforeEach(function () {
eventDispatcher.dispatchFontInactive(font);
eventDispatcher.dispatchFontActive(font);
});
it('should set the correct class', function () {
expect(element.className).toEqual('wf-myfamily-n4-active');
});
});
describe('#dispatchInactive', function () {
beforeEach(function () {
eventDispatcher.dispatchInactive();
});
it('should call the correct callback', function () {
expect(config.inactive).toHaveBeenCalled();
});
it('should set the correct class name', function () {
expect(element.className).toEqual('wf-inactive');
});
});
describe('#dispatchInactive - with loading class', function () {
beforeEach(function () {
eventDispatcher.dispatchLoading();
eventDispatcher.dispatchInactive();
});
it('should set the correct class name', function () {
expect(element.className).toEqual('wf-inactive');
});
});
describe('#dispatchInactive - with active class', function () {
beforeEach(function () {
eventDispatcher.dispatchActive();
eventDispatcher.dispatchInactive();
});
it('should not set the the inactive class', function () {
expect(element.className).toEqual('wf-active');
});
it('should still call the inactive callback', function () {
expect(config.inactive).toHaveBeenCalled();
});
});
describe('#dispatchActive', function () {
beforeEach(function () {
eventDispatcher.dispatchActive();
});
it('should call the correct callback', function () {
expect(config.active).toHaveBeenCalled();
});
it('should set the correct class name', function () {
expect(element.className).toEqual('wf-active');
});
});
describe('#dispatchActive - with loading class', function () {
beforeEach(function () {
eventDispatcher.dispatchLoading();
eventDispatcher.dispatchActive();
});
it('should set the correct class name', function () {
expect(element.className).toEqual('wf-active');
});
});
describe('#dispatchActive - with inactive class', function () {
beforeEach(function () {
eventDispatcher.dispatchInactive();
eventDispatcher.dispatchActive();
});
it('should set the correct class name', function () {
expect(element.className).toEqual('wf-active');
});
});
describe('disable classes and events', function () {
beforeEach(function () {
config.classes = false;
config.events = false;
eventDispatcher = new EventDispatcher(domHelper, config);
eventDispatcher.dispatchInactive();
eventDispatcher.dispatchActive();
eventDispatcher.dispatchLoading();
eventDispatcher.dispatchFontInactive(font);
eventDispatcher.dispatchFontActive(font);
eventDispatcher.dispatchFontLoading(font);
});
afterEach(function () {
config.classes = true;
config.events = true;
});
it('should not fire any events', function () {
expect(config.inactive).not.toHaveBeenCalled();
expect(config.active).not.toHaveBeenCalled();
expect(config.loading).not.toHaveBeenCalled();
expect(config.fontinactive).not.toHaveBeenCalled();
expect(config.fontactive).not.toHaveBeenCalled();
expect(config.fontloading).not.toHaveBeenCalled();
});
});
describe('disable classes', function () {
beforeEach(function () {
config.classes = false;
eventDispatcher = new EventDispatcher(domHelper, config);
eventDispatcher.dispatchInactive();
eventDispatcher.dispatchActive();
eventDispatcher.dispatchLoading();
eventDispatcher.dispatchFontInactive(font);
eventDispatcher.dispatchFontActive(font);
eventDispatcher.dispatchFontLoading(font);
});
afterEach(function () {
config.classes = true;
});
it('should not fire any events', function () {
expect(element.className).toEqual('');
});
});
});
================================================
FILE: spec/core/font_spec.js
================================================
describe('Font', function () {
var Font = webfont.Font;
describe('#quote', function () {
var quote = function (font) {
return new Font(font).getCssName();
};
it('should quote names with spaces', function () {
expect(quote('My Family')).toEqual("'My Family'");
});
it('should quote names with spaces and double quotes', function () {
expect(quote('"My Family"')).toEqual("'My Family'");
});
it('should quote names with spaces and single quotes', function () {
expect(quote("'My Family'")).toEqual("'My Family'");
});
it('should quote multiple single quoted names separated with a comma', function () {
expect(quote("'family 1','family 2'")).toEqual("'family 1','family 2'");
});
it('should quote multiple single quoted names separated with a comma and space', function () {
expect(quote("'family 1', 'family 2'")).toEqual("'family 1','family 2'");
});
it('should quote family names starting with a number', function () {
expect(quote('5test')).toEqual("'5test'");
});
it('should not quote when there is no space', function () {
expect(quote('MyFamily')).toEqual('MyFamily');
});
it('should remove quotes when they are unnecesssary', function () {
expect(quote('"MyFamily"')).toEqual('MyFamily');
});
it('should not quote multiple names when there is no space', function () {
expect(quote("'family-1', 'family-2'")).toEqual('family-1,family-2');
});
});
describe('#toCssString', function () {
function toCssString(fvd) {
return new Font('My Family', fvd).toCssString();
}
it('should expand font styles correctly', function () {
expect(toCssString('n4')).toEqual("normal 400 300px 'My Family'");
expect(toCssString('i4')).toEqual("italic 400 300px 'My Family'");
expect(toCssString('o4')).toEqual("oblique 400 300px 'My Family'");
});
it('should expand weights correctly', function () {
for (var i = 1; i < 10; i += 1) {
expect(toCssString('n' + i)).toEqual("normal " + (i * 100) + " 300px 'My Family'");
}
});
});
describe('#getCssVariation', function () {
function toCss(fvd) {
return new Font('My Family', fvd).getCssVariation();
}
it('should expand font-style', function () {
expect(toCss('n4')).toEqual('font-style:normal;font-weight:400;');
expect(toCss('i4')).toEqual('font-style:italic;font-weight:400;');
expect(toCss('o4')).toEqual('font-style:oblique;font-weight:400;');
});
it('should expand weights correctly', function () {
for (var i = 1; i < 10; i += 1) {
expect(toCss('n' + i)).toEqual('font-style:normal;font-weight:' + (i * 100) + ';');
}
});
it('should not expand incorrect input', function () {
expect(toCss('')).toEqual('font-style:normal;font-weight:400;');
expect(toCss('n')).toEqual('font-style:normal;font-weight:400;');
expect(toCss('1')).toEqual('font-style:normal;font-weight:400;');
expect(toCss('n1x')).toEqual('font-style:normal;font-weight:400;');
});
});
describe('parseCssVariation', function () {
function toFvd(css) {
return Font.parseCssVariation(css);
}
it('should default to n4 when there is no description', function () {
expect(toFvd('')).toEqual('n4');
expect(toFvd(null)).toEqual('n4');
expect(toFvd(undefined)).toEqual('n4');
});
it('should compact font style', function () {
expect(toFvd('font-style: normal;')).toEqual('n4');
expect(toFvd('font-style: italic;')).toEqual('i4');
expect(toFvd('font-style: oblique;')).toEqual('o4');
});
it('should return the default value when font-style is incorrect', function () {
expect(toFvd('font-style: other;')).toEqual('n4');
});
it('should compact font weight', function () {
expect(toFvd('font-weight: normal;')).toEqual('n4');
expect(toFvd('font-weight: bold;')).toEqual('n7');
for (var i = 1; i < 10; i += 1) {
expect(toFvd('font-weight: ' + (i * 100) + ';')).toEqual('n' + i);
}
});
it('should return the default value when font-weight is incorrect', function () {
expect(toFvd('font-weight: 140;')).toEqual('n4');
expect(toFvd('font-weight: other;')).toEqual('n4');
});
it('should compact multiple properties', function () {
expect(toFvd('font-weight: bold; font-style: italic;')).toEqual('i7');
expect(toFvd('; font-weight: bold; font-style: italic;')).toEqual('i7');
expect(toFvd('font-style:italic;font-weight:bold;')).toEqual('i7');
expect(toFvd(' font-style: italic ;\n\nfont-weight: bold; ')).toEqual('i7');
});
it('should return default values for incorrect individual properties', function () {
expect(toFvd('src: url(/font.otf)')).toEqual('n4');
expect(toFvd('font-weight: 900; src: url(/font.otf);')).toEqual('n9');
expect(toFvd('font-weight: 800; font-stretch:condensed;')).toEqual('n8');
});
});
});
================================================
FILE: spec/core/fontmoduleloader_spec.js
================================================
describe('FontModuleLoader', function () {
var FontModuleLoader = webfont.FontModuleLoader;
describe('#getModules', function () {
var fontModuleLoader = null;
beforeEach(function () {
fontModuleLoader = new FontModuleLoader();
});
it('should return an empty array without modules', function () {
var modules = fontModuleLoader.getModules();
expect(modules).not.toBeNull();
expect(modules.length).toEqual(0);
});
it('should have modules', function () {
fontModuleLoader.addModuleFactory('booh', function () {
return {
scary: true
};
});
fontModuleLoader.addModuleFactory('haha', function () {
return {
funny: true
};
});
fontModuleLoader.addModuleFactory('moo', function () {
return {
cowy: true
};
});
var modules = fontModuleLoader.getModules({
booh: {},
moo: {},
nothing: {}
});
expect(modules).not.toBeNull();
expect(modules.length).toEqual(2);
var module = modules[0];
expect(module).not.toBeNull();
expect(module.scary || module.cowy).toBe(true);
var module = modules[1];
expect(module).not.toBeNull();
expect(module.scary || module.cowy).toBe(true);
});
});
});
================================================
FILE: spec/core/fontruler_spec.js
================================================
describe('FontRuler', function () {
var Font = webfont.Font,
FontRuler = webfont.FontRuler,
DomHelper = webfont.DomHelper,
Size = webfont.Size,
domHelper = null,
font = null;
beforeEach(function () {
font = new Font('sans-serif');
domHelper = new DomHelper(window);
});
it('should prevent a long test string from word wrapping', function () {
var fontRulerA = new FontRuler(domHelper, 'abc'),
fontRulerB = new FontRuler(domHelper, 'abc HelloWorld,thisshouldwrap!!!!');
fontRulerA.insert();
fontRulerB.insert();
fontRulerA.setFont(font);
fontRulerB.setFont(font);
var widthA = fontRulerA.getWidth(),
widthB = fontRulerB.getWidth();
expect(widthA).not.toEqual(widthB);
});
});
================================================
FILE: spec/core/fontwatcher_spec.js
================================================
describe('FontWatcher', function () {
var FontWatcher = webfont.FontWatcher,
FontWatchRunner = webfont.FontWatchRunner,
NativeFontWatchRunner = webfont.NativeFontWatchRunner,
Font = webfont.Font,
DomHelper = webfont.DomHelper,
Version = webfont.Version,
domHelper = new DomHelper(window),
eventDispatcher = {},
testStrings = null,
timeout = null,
font1 = null,
font2 = null,
font3 = null,
font4 = null,
activeFonts = [];
beforeEach(function () {
font1 = new Font('font1');
font2 = new Font('font2');
font3 = new Font('font3');
font4 = new Font('font4');
activeFonts = [];
testStrings = jasmine.createSpy('testStrings');
timeout = jasmine.createSpy('timeout');
eventDispatcher.dispatchLoading = jasmine.createSpy('dispatchLoading');
eventDispatcher.dispatchFontLoading = jasmine.createSpy('dispatchFontLoading');
eventDispatcher.dispatchFontActive = jasmine.createSpy('dispatchFontActive');
eventDispatcher.dispatchFontInactive = jasmine.createSpy('dispatchFontInactive');
eventDispatcher.dispatchActive = jasmine.createSpy('dispatchActive');
eventDispatcher.dispatchInactive = jasmine.createSpy('dispatchInactive');
var fakeStart = function (font, fontTestString) {
var found = false;
testStrings(this.fontTestString_);
timeout(this.timeout_);
for (var i = 0; i < activeFonts.length; i += 1) {
if (activeFonts[i].getName() === this.font_.getName()) {
found = true;
break;
}
}
if (found) {
this.activeCallback_(this.font_);
} else {
this.inactiveCallback_(this.font_);
}
};
spyOn(FontWatchRunner.prototype, 'start').andCallFake(fakeStart);
spyOn(NativeFontWatchRunner.prototype, 'start').andCallFake(fakeStart);
});
if (!!window.FontFace) {
describe('use native font loading API', function () {
beforeEach(function () {
FontWatcher.SHOULD_USE_NATIVE_LOADER = null;
});
it('works on Chrome', function () {
spyOn(FontWatcher, 'getUserAgent').andReturn('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36');
expect(FontWatcher.shouldUseNativeLoader()).toEqual(true);
});
it('is disabled on Firefox <= 42', function () {
spyOn(FontWatcher, 'getUserAgent').andReturn('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:42.0) Gecko/20100101 Firefox/42.0')
expect(FontWatcher.shouldUseNativeLoader()).toEqual(false);
});
it('is enabled on Firefox > 43', function () {
spyOn(FontWatcher, 'getUserAgent').andReturn('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:43.0) Gecko/20100101 Firefox/43.0');
expect(FontWatcher.shouldUseNativeLoader()).toEqual(true);
});
it('is disabled on Safari > 10', function () {
spyOn(FontWatcher, 'getUserAgent').andReturn('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/602.2.14 (KHTML, like Gecko) Version/10.0.1 Safari/602.2.14');
spyOn(FontWatcher, 'getVendor').andReturn('Apple');
expect(FontWatcher.shouldUseNativeLoader()).toEqual(false);
});
});
}
describe('watch zero fonts', function () {
it('should call inactive when there are no fonts to load', function () {
activeFonts = [];
var fontWatcher = new FontWatcher(domHelper, eventDispatcher);
fontWatcher.watchFonts([], {}, null, true);
expect(eventDispatcher.dispatchInactive).toHaveBeenCalled();
});
it('should not call inactive when there are no fonts to load, but this is not the last set', function () {
activeFonts = [];
var fontWatcher = new FontWatcher(domHelper, eventDispatcher);
fontWatcher.watchFonts([], {}, null, false);
expect(eventDispatcher.dispatchInactive).not.toHaveBeenCalled();
});
});
describe('watch one font not last', function () {
it('should not call font inactive, inactive or active', function () {
activeFonts = [font1];
var fontWatcher = new FontWatcher(domHelper, eventDispatcher);
fontWatcher.watchFonts([font1], {}, null, false);
expect(eventDispatcher.dispatchFontInactive).not.toHaveBeenCalled();
expect(eventDispatcher.dispatchActive).not.toHaveBeenCalled();
expect(eventDispatcher.dispatchInactive).not.toHaveBeenCalled();
});
});
describe('watch one font active', function () {
it('should call font active and active', function () {
activeFonts = [font1];
var fontWatcher = new FontWatcher(domHelper, eventDispatcher);
fontWatcher.watchFonts([font1], {}, null, true);
expect(eventDispatcher.dispatchFontLoading).toHaveBeenCalledWith(font1);
expect(eventDispatcher.dispatchFontActive).toHaveBeenCalledWith(font1);
expect(eventDispatcher.dispatchFontInactive).not.toHaveBeenCalled();
expect(eventDispatcher.dispatchActive).toHaveBeenCalled();
expect(eventDispatcher.dispatchInactive).not.toHaveBeenCalled();
});
});
describe('watch one font inactive', function () {
it('should call inactive', function () {
activeFonts = [];
var fontWatcher = new FontWatcher(domHelper, eventDispatcher);
fontWatcher.watchFonts([font1], {}, null, true);
expect(eventDispatcher.dispatchFontLoading).toHaveBeenCalledWith(font1);
expect(eventDispatcher.dispatchFontActive).not.toHaveBeenCalled();
expect(eventDispatcher.dispatchFontInactive).toHaveBeenCalledWith(font1);
expect(eventDispatcher.dispatchActive).not.toHaveBeenCalled();
expect(eventDispatcher.dispatchInactive).toHaveBeenCalled();
});
});
describe('watch multiple fonts active', function () {
it('should call font active and active', function () {
activeFonts = [font1, font2, font3];
var fontWatcher = new FontWatcher(domHelper, eventDispatcher);
fontWatcher.watchFonts([font1, font2, font3], {}, null, true);
expect(eventDispatcher.dispatchFontLoading).toHaveBeenCalledWith(font1);
expect(eventDispatcher.dispatchFontActive).toHaveBeenCalledWith(font1);
expect(eventDispatcher.dispatchFontInactive).not.toHaveBeenCalled();
expect(eventDispatcher.dispatchActive).toHaveBeenCalled();
expect(eventDispatcher.dispatchInactive).not.toHaveBeenCalled();
});
});
describe('watch multiple fonts inactive', function () {
it('should call inactive', function () {
activeFonts = [];
var fontWatcher = new FontWatcher(domHelper, eventDispatcher);
fontWatcher.watchFonts([font1, font2, font3], {}, null, true);
expect(eventDispatcher.dispatchFontLoading).toHaveBeenCalledWith(font1);
expect(eventDispatcher.dispatchFontActive).not.toHaveBeenCalled();
expect(eventDispatcher.dispatchFontInactive).toHaveBeenCalledWith(font1);
expect(eventDispatcher.dispatchActive).not.toHaveBeenCalled();
expect(eventDispatcher.dispatchInactive).toHaveBeenCalled();
});
});
describe('watch multiple fonts mixed', function () {
it('should call the correct callbacks', function () {
activeFonts = [font1, font3];
var fontWatcher = new FontWatcher(domHelper, eventDispatcher);
fontWatcher.watchFonts([font1, font2, font3], {}, null, true);
expect(eventDispatcher.dispatchFontLoading.callCount).toEqual(3);
expect(eventDispatcher.dispatchFontLoading.calls[0].args[0]).toEqual(font1);
expect(eventDispatcher.dispatchFontLoading.calls[1].args[0]).toEqual(font2);
expect(eventDispatcher.dispatchFontLoading.calls[2].args[0]).toEqual(font3);
expect(eventDispatcher.dispatchFontActive.callCount).toEqual(2);
expect(eventDispatcher.dispatchFontActive.calls[0].args[0]).toEqual(font1);
expect(eventDispatcher.dispatchFontActive.calls[1].args[0]).toEqual(font3);
expect(eventDispatcher.dispatchFontInactive.callCount).toEqual(1);
expect(eventDispatcher.dispatchFontInactive.calls[0].args[0]).toEqual(font2);
expect(eventDispatcher.dispatchActive).toHaveBeenCalled();
expect(eventDispatcher.dispatchInactive).not.toHaveBeenCalled();
});
});
describe('watch multiple fonts with descriptions', function () {
it('should call the correct callbacks', function () {
var font5 = new Font('font4', 'i7'),
font6 = new Font('font5'),
font7 = new Font('font6'),
font8 = new Font('font7', 'i4'),
font9 = new Font('font8', 'n7');
activeFonts = [font5, font6];
var fontWatcher = new FontWatcher(domHelper, eventDispatcher);
fontWatcher.watchFonts([font5, font6, font7, font8, font9], {}, null, true);
expect(eventDispatcher.dispatchFontLoading.callCount).toEqual(5);
expect(eventDispatcher.dispatchFontLoading).toHaveBeenCalledWith(font5);
expect(eventDispatcher.dispatchFontLoading).toHaveBeenCalledWith(font6);
expect(eventDispatcher.dispatchFontLoading).toHaveBeenCalledWith(font7);
expect(eventDispatcher.dispatchFontLoading).toHaveBeenCalledWith(font8);
expect(eventDispatcher.dispatchFontLoading).toHaveBeenCalledWith(font9);
expect(eventDispatcher.dispatchFontActive.callCount).toEqual(2);
expect(eventDispatcher.dispatchFontActive).toHaveBeenCalledWith(font5);
expect(eventDispatcher.dispatchFontActive).toHaveBeenCalledWith(font6);
expect(eventDispatcher.dispatchFontInactive.callCount).toEqual(3);
expect(eventDispatcher.dispatchFontInactive).toHaveBeenCalledWith(font7);
expect(eventDispatcher.dispatchFontInactive).toHaveBeenCalledWith(font8);
expect(eventDispatcher.dispatchFontInactive).toHaveBeenCalledWith(font9);
expect(eventDispatcher.dispatchInactive).not.toHaveBeenCalled();
expect(eventDispatcher.dispatchActive).toHaveBeenCalled();
});
});
describe('watch multiple fonts with test strings', function () {
it('should use the correct tests strings', function () {
activeFonts = [font1, font2];
var defaultTestString = FontWatcher.SHOULD_USE_NATIVE_LOADER ? undefined : FontWatchRunner.DEFAULT_TEST_STRING;
var fontWatcher = new FontWatcher(domHelper, eventDispatcher);
fontWatcher.watchFonts([font1, font2, font3, font4], {
'font1': 'testString1',
'font2': null,
'font3': 'testString2',
'font4': null
}, null, true);
expect(testStrings.callCount).toEqual(4);
expect(testStrings.calls[0].args[0]).toEqual('testString1');
expect(testStrings.calls[1].args[0]).toEqual(defaultTestString);
expect(testStrings.calls[2].args[0]).toEqual('testString2');
expect(testStrings.calls[3].args[0]).toEqual(defaultTestString);
});
});
it('should pass on the timeout to FontWatchRunner', function () {
var fontWatcher = new FontWatcher(domHelper, eventDispatcher, 4000);
fontWatcher.watchFonts([font1], {}, null, true);
expect(timeout).toHaveBeenCalledWith(4000);
});
});
================================================
FILE: spec/core/fontwatchrunner_spec.js
================================================
describe('FontWatchRunner', function () {
var FontWatchRunner = webfont.FontWatchRunner,
Font = webfont.Font,
BrowserInfo = webfont.BrowserInfo,
DomHelper = webfont.DomHelper,
FontRuler = webfont.FontRuler;
var domHelper = null,
activeCallback = null,
inactiveCallback = null,
nullFont = null,
sourceSansA = null,
sourceSansB = null,
elena = null;
beforeEach(function () {
domHelper = new DomHelper(window);
activeCallback = jasmine.createSpy('activeCallback');
inactiveCallback = jasmine.createSpy('inactiveCallback');
nullFont = new Font('__webfontloader_test__');
sourceSansA = new Font('SourceSansA');
sourceSansB = new Font('SourceSansB');
elena = new Font('Elena');
});
it('should fail to load a null font', function () {
var fontWatchRunner = new FontWatchRunner(activeCallback, inactiveCallback,
domHelper, nullFont, 500, {});
runs(function () {
fontWatchRunner.start();
});
waitsFor(function () {
return activeCallback.wasCalled || inactiveCallback.wasCalled;
});
runs(function () {
expect(inactiveCallback).toHaveBeenCalledWith(nullFont);
});
});
it('should load font succesfully', function () {
var fontWatchRunner = new FontWatchRunner(activeCallback, inactiveCallback,
domHelper, sourceSansA, 5000),
ruler = new FontRuler(domHelper, 'abcdef'),
monospace = new Font('monospace'),
sourceSansAFallback = new Font("'SourceSansA', monospace"),
activeWidth = null,
originalWidth = null,
finalCheck = false;
runs(function () {
ruler.insert();
ruler.setFont(monospace);
originalWidth = ruler.getWidth();
ruler.setFont(sourceSansAFallback);
fontWatchRunner.start();
});
waitsFor(function () {
return activeCallback.wasCalled || inactiveCallback.wasCalled;
});
runs(function () {
expect(activeCallback).toHaveBeenCalledWith(sourceSansA);
activeWidth = ruler.getWidth();
expect(activeWidth).not.toEqual(originalWidth);
window.setTimeout(function () {
finalCheck = true;
}, 200);
});
waitsFor(function () {
return finalCheck;
});
runs(function () {
expect(ruler.getWidth()).not.toEqual(originalWidth);
expect(ruler.getWidth()).toEqual(activeWidth);
});
});
it('should attempt to load a non-existing font', function () {
var fontWatchRunner = new FontWatchRunner(activeCallback, inactiveCallback,
domHelper, elena, 500, {});
runs(function () {
fontWatchRunner.start();
});
waitsFor(function () {
return activeCallback.wasCalled || inactiveCallback.wasCalled;
});
runs(function () {
expect(inactiveCallback).toHaveBeenCalledWith(elena);
});
});
it('should load even if @font-face is inserted after watching has started', function () {
var fontWatchRunner = new FontWatchRunner(activeCallback, inactiveCallback,
domHelper, sourceSansB, 5000),
ruler = new FontRuler(domHelper, 'abcdef'),
monospace = new Font('monospace'),
sourceSansBFallback = new Font("'SourceSansB', monospace"),
activeWidth = null,
originalWidth = null,
finalCheck = false;
runs(function () {
ruler.insert();
ruler.setFont(monospace);
originalWidth = ruler.getWidth();
ruler.setFont(sourceSansBFallback);
fontWatchRunner.start();
var link = document.createElement('link');
link.rel = "stylesheet";
link.href= "fixtures/fonts/sourcesansb.css";
domHelper.insertInto('head', link);
});
waitsFor(function () {
return activeCallback.wasCalled || inactiveCallback.wasCalled;
});
runs(function () {
expect(activeCallback).toHaveBeenCalledWith(sourceSansB);
activeWidth = ruler.getWidth();
expect(activeWidth).not.toEqual(originalWidth);
window.setTimeout(function () {
finalCheck = true;
}, 200);
});
waitsFor(function () {
return finalCheck;
});
runs(function () {
expect(ruler.getWidth()).not.toEqual(originalWidth);
expect(ruler.getWidth()).toEqual(activeWidth);
});
});
});
================================================
FILE: spec/core/nativefontwatchrunner_spec.js
================================================
describe('NativeFontWatchRunner', function () {
var NativeFontWatchRunner = webfont.NativeFontWatchRunner,
Font = webfont.Font,
DomHelper = webfont.DomHelper,
FontRuler = webfont.FontRuler;
var domHelper = null,
activeCallback = null,
inactiveCallback = null,
nullFont = null,
sourceSansC = null,
sourceSansDup = null,
elena = null;
beforeEach(function () {
domHelper = new DomHelper(window);
activeCallback = jasmine.createSpy('activeCallback');
inactiveCallback = jasmine.createSpy('inactiveCallback');
nullFont = new Font('__webfontloader_test_3__');
sourceSansC = new Font('SourceSansC');
sourceSansDup = new Font('SourceSansDup');
elena = new Font('Elena');
});
if (window['FontFace']) {
it('should fail to load a null font', function () {
var fontWatchRunner = new NativeFontWatchRunner(activeCallback, inactiveCallback,
domHelper, nullFont, 500);
runs(function () {
fontWatchRunner.start();
});
waitsFor(function () {
return activeCallback.wasCalled || inactiveCallback.wasCalled;
});
runs(function () {
expect(inactiveCallback).toHaveBeenCalledWith(nullFont);
});
});
function succesfulLoadingSpec(getFontToBeLoaded, getFontNameToBeLoaded) {
var fontToBeLoaded = getFontToBeLoaded(),
fontNameToBeLoaded = getFontNameToBeLoaded(),
fontWatchRunner = new NativeFontWatchRunner(activeCallback, inactiveCallback,
domHelper, fontToBeLoaded),
ruler = new FontRuler(domHelper, 'abcdef'),
monospace = new Font('monospace'),
fallbackFont = new Font(fontNameToBeLoaded + ', monospace'),
activeWidth = null,
originalWidth = null,
finalCheck = false;
runs(function () {
ruler.insert();
ruler.setFont(monospace);
originalWidth = ruler.getWidth();
ruler.setFont(fallbackFont);
fontWatchRunner.start();
});
waitsFor(function () {
return activeCallback.wasCalled || inactiveCallback.wasCalled;
});
runs(function () {
expect(activeCallback).toHaveBeenCalledWith(fontToBeLoaded);
activeWidth = ruler.getWidth();
expect(activeWidth).not.toEqual(originalWidth);
window.setTimeout(function () {
finalCheck = true;
}, 200);
});
waitsFor(function () {
return finalCheck;
});
runs(function () {
expect(ruler.getWidth()).not.toEqual(originalWidth);
expect(ruler.getWidth()).toEqual(activeWidth);
});
}
it('should load font succesfully',
succesfulLoadingSpec.bind(null, function() { return sourceSansC; }, function() { return 'SourceSansC'; }));
it('should load font succesfully even if it is duplicated',
succesfulLoadingSpec.bind(null, function() { return sourceSansDup; }, function() { return 'SourceSansDup'; }));
it('should attempt to load a non-existing font', function () {
var fontWatchRunner = new NativeFontWatchRunner(activeCallback, inactiveCallback,
domHelper, elena, 500);
runs(function () {
fontWatchRunner.start();
});
waitsFor(function () {
return activeCallback.wasCalled || inactiveCallback.wasCalled;
});
runs(function () {
expect(inactiveCallback).toHaveBeenCalledWith(elena);
});
});
}
});
================================================
FILE: spec/core/size_spec.js
================================================
describe('Size', function () {
var Size = webfont.Size;
it('should return true on identical sizes', function () {
expect(new Size(10, 10).equals(new Size(10, 10))).toBe(true);
});
it('should return false when two sizes are different', function () {
expect(new Size(10, 10).equals(new Size(20, 20))).toBe(false);
expect(new Size(10, 10).equals(new Size(10, 20))).toBe(false);
});
it('should return false when one font is undefined or null', function () {
expect(new Size(10, 10).equals(undefined)).toBe(false);
expect(new Size(10, 10).equals(null)).toBe(false);
});
});
================================================
FILE: spec/core/webfont_spec.js
================================================
describe('WebFont', function () {
var WebFont = webfont.WebFont,
Font = webfont.Font;
FontWatchRunner = webfont.FontWatchRunner,
NativeFontWatchRunner = webfont.NativeFontWatchRunner,
Version = webfont.Version,
Font = webfont.Font,
FontModuleLoader = webfont.FontModuleLoader,
fontModuleLoader = null;
beforeEach(function () {
fontModuleLoader = new FontModuleLoader();
});
describe('font load with context', function () {
var font = null,
testModule = null,
fakeMainWindow = {
document: {}
};
beforeEach(function () {
font = new WebFont(fakeMainWindow);
font.addModule('test', function (conf, domHelper) {
testModule = new function () {
this.domHelper = domHelper;
};
testModule.load = function (onReady) {
onReady([]);
};
return testModule;
});
});
it('should load with the correct context', function () {
font.load({
test: {
somedata: 'in french a cow says meuh'
},
context: window
});
expect(testModule.domHelper).not.toBeNull();
expect(testModule.domHelper).not.toBeUndefined();
expect(testModule.domHelper.getMainWindow()).toEqual(fakeMainWindow);
expect(testModule.domHelper.getLoadWindow()).toEqual(window);
});
});
describe('module failed to provide families and descriptions because it did not initialize properly', function () {
var webfont = null,
testModule = null,
font = null,
inactive = jasmine.createSpy('inactive'),
active = jasmine.createSpy('active');
beforeEach(function () {
font = new Font('Font1');
jasmine.Clock.useMock();
webfont = new WebFont(window);
webfont.addModule('test', function (conf, domHelper) {
testModule = new function () {
this.conf = conf;
};
spyOn(FontWatchRunner.prototype, 'start').andCallFake(function () {
if (conf.id) {
active(font);
} else {
inactive(font);
}
});
spyOn(NativeFontWatchRunner.prototype, 'start').andCallFake(function () {
if (conf.id) {
active(font);
} else {
inactive(font);
}
});
testModule.load = function (onReady) {
if (conf.id) {
onReady([font]);
} else {
onReady([]);
}
};
return testModule;
});
});
it('should load with a project id', function () {
webfont.load({
test: {
id: 'hello world'
},
inactive: inactive,
active: active
});
jasmine.Clock.tick(1);
expect(testModule).not.toBeNull();
expect(active).toHaveBeenCalled();
});
it('should not load without a project id', function () {
webfont.load({
test: {
},
inactive: inactive,
active: active
});
jasmine.Clock.tick(1);
expect(testModule).not.toBeNull();
expect(inactive).toHaveBeenCalled();
});
});
describe('should pass both fonts and test strings to onready', function () {
var font = null,
fontTestStrings = null,
testModule = null;
beforeEach(function () {
font = new WebFont(window);
font.addModule('test', function (conf, domHelper) {
testModule = new function () {};
testModule.load = function (onReady) {
onReady([new Font('Elena')], { 'Elena': '1234567' });
};
return testModule;
});
spyOn(font, 'onModuleReady_');
});
it('should have called onModuleReady with the correct font and test string', function () {
font.load({
'test': {}
});
expect(font.onModuleReady_).toHaveBeenCalled();
expect(font.onModuleReady_.calls[0].args[2]).toEqual([new Font('Elena')]);
expect(font.onModuleReady_.calls[0].args[3]).toEqual({ 'Elena': '1234567' });
});
});
describe('module fails to load', function () {
var font = null,
testModule = null,
inactive = null,
active = null;
beforeEach(function () {
inactive = jasmine.createSpy('inactive'),
active = jasmine.createSpy('active');
font = new WebFont(window, fontModuleLoader);
font.addModule('test', function (conf, domHelper) {
testModule = new function () {};
testModule.load = function (onReady) {
onReady([]);
};
return testModule;
});
});
it('times out and calls inactive', function () {
runs(function () {
font.load({
'test': {},
inactive: inactive,
active: active
});
});
waitsFor(function () {
return active.wasCalled || inactive.wasCalled;
});
runs(function () {
expect(inactive).toHaveBeenCalled();
expect(active).not.toHaveBeenCalled();
});
});
});
describe('synchronous load event', function () {
var font = null,
testModule = null,
inactive = null,
loading = null,
active = null;
beforeEach(function () {
inactive = jasmine.createSpy('inactive'),
active = jasmine.createSpy('active');
loading = jasmine.createSpy('loading');
font = new WebFont(window, fontModuleLoader);
font.addModule('test', function (conf, domHelper) {
testModule = new function () {};
testModule.load = function (onReady) {
onReady([new Font('Elena')]);
};
return testModule;
});
});
it('fires loading event correctly', function () {
runs(function () {
font.load({
'test': {},
inactive: inactive,
active: active,
loading: loading
});
expect(loading).toHaveBeenCalled();
});
waitsFor(function () {
return active.wasCalled || inactive.wasCalled;
});
runs(function () {
expect(inactive).toHaveBeenCalled();
expect(active).not.toHaveBeenCalled();
});
});
});
});
================================================
FILE: spec/deps.js
================================================
// This file was autogenerated by calcdeps.js
goog.addDependency("../../src/closure.js", [], []);
goog.addDependency("../../src/core/cssclassname.js", ["webfont.CssClassName"], []);
goog.addDependency("../../src/core/domhelper.js", ["webfont.DomHelper"], []);
goog.addDependency("../../src/core/stylesheetwaiter.js", ["webfont.StyleSheetWaiter"], []);
goog.addDependency("../../src/core/eventdispatcher.js", ["webfont.EventDispatcher"], ["webfont.CssClassName"]);
goog.addDependency("../../src/core/font.js", ["webfont.Font"], []);
goog.addDependency("../../src/core/fontmodule.js", ["webfont.FontModule"], []);
goog.addDependency("../../src/core/fontmoduleloader.js", ["webfont.FontModuleLoader","webfont.FontModuleFactory"], []);
goog.addDependency("../../src/core/fontruler.js", ["webfont.FontRuler"], []);
goog.addDependency("../../src/core/fontwatcher.js", ["webfont.FontWatcher"], ["webfont.FontWatchRunner","webfont.NativeFontWatchRunner"]);
goog.addDependency("../../src/core/fontwatchrunner.js", ["webfont.FontWatchRunner"], ["webfont.Font","webfont.FontRuler"]);
goog.addDependency("../../src/core/initialize.js", ["webfont"], ["webfont.WebFont","webfont.modules.Typekit","webfont.modules.Fontdeck","webfont.modules.Monotype","webfont.modules.Custom","webfont.modules.google.GoogleFontApi"]);
goog.addDependency("../../src/core/nativefontwatchrunner.js", ["webfont.NativeFontWatchRunner"], ["webfont.Font"]);
goog.addDependency("../../src/core/webfont.js", ["webfont.WebFont"], ["webfont.DomHelper","webfont.EventDispatcher","webfont.FontWatcher","webfont.FontModuleLoader"]);
goog.addDependency("../../src/modules/custom.js", ["webfont.modules.Custom"], ["webfont.Font", "webfont.StyleSheetWaiter"]);
goog.addDependency("../../src/modules/fontdeck.js", ["webfont.modules.Fontdeck"], ["webfont.Font"]);
goog.addDependency("../../src/modules/google/fontapiparser.js", ["webfont.modules.google.FontApiParser"], ["webfont.Font"]);
goog.addDependency("../../src/modules/google/fontapiurlbuilder.js", ["webfont.modules.google.FontApiUrlBuilder"], []);
goog.addDependency("../../src/modules/google/googlefontapi.js", ["webfont.modules.google.GoogleFontApi"], ["webfont.modules.google.FontApiUrlBuilder","webfont.modules.google.FontApiParser","webfont.FontWatchRunner", "webfont.StyleSheetWaiter"]);
goog.addDependency("../../src/modules/monotype.js", ["webfont.modules.Monotype"], ["webfont.Font"]);
goog.addDependency("../../src/modules/typekit.js", ["webfont.modules.Typekit"], ["webfont.Font"]);
================================================
FILE: spec/fixtures/external_script.js
================================================
window.EXTERNAL_SCRIPT_LOADED = true;
================================================
FILE: spec/fixtures/external_stylesheet.css
================================================
#TEST_ELEMENT {
display: block;
position: absolute;
top: 0;
left: 0;
width: 300px;
}
================================================
FILE: spec/fixtures/fonts/LICENSE.txt
================================================
Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
================================================
FILE: spec/fixtures/fonts/nullfont.css
================================================
@font-face{font-family:__webfontloader_test__;src:url(data:application/x-font-woff;base64,) format('woff'),url(data:font/truetype;base64,) format('truetype');}
================================================
FILE: spec/fixtures/fonts/nullfont1.css
================================================
@font-face{font-family:__webfontloader_test_1__;src:url(data:application/x-font-woff;base64,) format('woff'),url(data:font/truetype;base64,) format('truetype');}
================================================
FILE: spec/fixtures/fonts/nullfont2.css
================================================
@font-face{font-family:__webfontloader_test_2__;src:url(data:application/x-font-woff;base64,) format('woff'),url(data:font/truetype;base64,) format('truetype');}
================================================
FILE: spec/fixtures/fonts/nullfont3.css
================================================
@font-face{font-family:__webfontloader_test_3__;src:url(data:application/x-font-woff;base64,) format('woff'),url(data:font/truetype;base64,) format('truetype');}
================================================
FILE: spec/fixtures/fonts/sourcesansa.css
================================================
@font-face{font-family:SourceSansA;src:url(sourcesans.eot?#iefix) format('embedded-opentype'),url(sourcesans.woff) format('woff'),url(sourcesans.otf) format('opentype'),url(sourcesans.ttf) format('truetype'),url(sourcesans.svg#source_sans_proregular) format('svg');}
================================================
FILE: spec/fixtures/fonts/sourcesansb.css
================================================
@font-face{font-family:SourceSansB;src:url(sourcesans.eot?#iefix) format('embedded-opentype'),url(sourcesans.woff) format('woff'),url(sourcesans.otf) format('opentype'),url(sourcesans.ttf) format('truetype'),url(sourcesans.svg#source_sans_proregular) format('svg');}
================================================
FILE: spec/fixtures/fonts/sourcesansc.css
================================================
@font-face{font-family:SourceSansC;src:url(sourcesans.eot?#iefix) format('embedded-opentype'),url(sourcesans.woff) format('woff'),url(sourcesans.otf) format('opentype'),url(sourcesans.ttf) format('truetype'),url(sourcesans.svg#source_sans_proregular) format('svg');}
================================================
FILE: spec/fixtures/fonts/sourcesansd.css
================================================
@font-face{font-family:SourceSansD;src:url(sourcesans.eot?#iefix) format('embedded-opentype'),url(sourcesans.woff) format('woff'),url(sourcesans.otf) format('opentype'),url(sourcesans.ttf) format('truetype'),url(sourcesans.svg#source_sans_proregular) format('svg');}
================================================
FILE: spec/fixtures/fonts/sourcesansdup1.css
================================================
@font-face{font-family:SourceSansDup;src:url(sourcesans.eot?#iefix) format('embedded-opentype'),url(sourcesans.woff) format('woff'),url(sourcesans.otf) format('opentype'),url(sourcesans.ttf) format('truetype'),url(sourcesans.svg#source_sans_proregular) format('svg');}
================================================
FILE: spec/fixtures/fonts/sourcesansdup2.css
================================================
@font-face{font-family:SourceSansDup;src:url(sourcesans.eot?#iefix) format('embedded-opentype'),url(sourcesans.woff) format('woff'),url(sourcesans.otf) format('opentype'),url(sourcesans.ttf) format('truetype'),url(sourcesans.svg#source_sans_proregular) format('svg');}
================================================
FILE: spec/index.html
================================================
Webfontloader tests
================================================
FILE: spec/modules/custom_spec.js
================================================
describe('modules.Custom', function () {
var Custom = webfont.modules.Custom,
FontFamily = webfont.FontFamily,
Any = jasmine.Matchers.Any;
describe('insert links correctly', function () {
var fakeDomHelper = null,
load = null;
function notifySheetsLoaded() {
var argsForCall = fakeDomHelper.loadStylesheet.argsForCall;
for (var i = 0; i < argsForCall.length; i++) {
var args = argsForCall[i];
args[1]();
}
}
beforeEach(function () {
fakeDomHelper = {
loadStylesheet: jasmine.createSpy('createCssLink')
};
load = jasmine.createSpy('load');
var defaultModule = new Custom(fakeDomHelper, {
families: ['Font1', 'Font2', 'Font3'],
urls: ['https://moo', 'https://meuh'],
testStrings: {
Font3: 'hello world'
}
});
defaultModule.load(load);
});
it('should have inserted the links correctly', function () {
expect(fakeDomHelper.loadStylesheet.callCount).toEqual(2);
expect(fakeDomHelper.loadStylesheet).toHaveBeenCalledWith('https://moo', new Any(Function));
expect(fakeDomHelper.loadStylesheet).toHaveBeenCalledWith('https://meuh', new Any(Function));
});
if (webfont.DomHelper.CAN_WAIT_STYLESHEET) {
it('should not invoke callback before all CSS are loaded', function () {
expect(load.callCount).toEqual(0);
notifySheetsLoaded();
expect(load.callCount).toEqual(1);
});
}
it('should have loaded the families correctly', function () {
notifySheetsLoaded();
expect(load.callCount).toEqual(1);
expect(load.calls[0].args[0].length).toEqual(3);
expect(load.calls[0].args[0][0].getName()).toEqual('Font1');
expect(load.calls[0].args[0][1].getName()).toEqual('Font2');
expect(load.calls[0].args[0][2].getName()).toEqual('Font3');
});
it('should have set a custom test string', function () {
notifySheetsLoaded();
expect(load.callCount).toEqual(1);
expect(load.calls[0].args[1]).toEqual({ Font3: 'hello world' });
});
});
});
================================================
FILE: spec/modules/fontdeck_spec.js
================================================
describe('modules.Fontdeck', function () {
var Fontdeck = webfont.modules.Fontdeck,
Font = webfont.Font;
var configuration = {
id: '2282'
};
var apiResponse = {
"domain" : "localhost",
"cssurl" : "https://f.fontdeck.com/s/css/03BmCXiV2AHwX/Rp+OBFTfD2oFs/localhost/2282.css",
"project" : 2282,
"cssbase" : "https://f.fontdeck.com/s/css/03BmCXiV2AHwX/Rp+OBFTfD2oFs",
"fonts" : [
{
"font_family" : "'Fertigo Pro Regular', Fertigo, Constantia, Palatino, serif",
"font_size_adjust" : 0.508,
"name" : "Fertigo Pro Regular",
"style" : "normal",
"weight" : "normal",
"font_urls" : {
"eot" : "https://f.fontdeck.com/f/1/SUlFR0tid0kAA2vb11Ly/IGWDK+wV8TMAfV0J1Ej1J1GFRT1bssqrn6a.eot",
"ttf" : "https://f.fontdeck.com/f/1/SUlFR0tid0kAA2vb11Ly/IGWDK+wV8TMAfV0J1Ej1J1GFRT1bssqrn6a.ttf",
"woff" : "https://f.fontdeck.com/f/1/SUlFR0tid0kAA2vb11Ly/IGWDK+wV8TMAfV0J1Ej1J1GFRT1bssqrn6a.woff",
"svg" : "https://f.fontdeck.com/f/1/SUlFR0tid0kAA2vb11Ly/IGWDK+wV8TMAfV0J1Ej1J1GFRT1bssqrn6a.svg#104"
},
"id" : 104
},
{
"font_family" : "'Bodoni Display Bold Italic', Georgia, 'Times New Roman', Times, serif",
"font_size_adjust" : 0.45,
"name" : "Bodoni Display Bold Italic",
"style" : "italic",
"weight" : "bold",
"font_urls" : {
"eot" : "https://f.fontdeck.com/f/1/azJEbTVyc1QAA11+CAE5C93+l/bAQx1ipRo6Maba19w3Yy5ng+qVWlfj.eot",
"ttf" : "https://f.fontdeck.com/f/1/azJEbTVyc1QAA11+CAE5C93+l/bAQx1ipRo6Maba19w3Yy5ng+qVWlfj.ttf",
"woff" : "https://f.fontdeck.com/f/1/azJEbTVyc1QAA11+CAE5C93+l/bAQx1ipRo6Maba19w3Yy5ng+qVWlfj.woff",
"svg" : "https://f.fontdeck.com/f/1/azJEbTVyc1QAA11+CAE5C93+l/bAQx1ipRo6Maba19w3Yy5ng+qVWlfj.svg#2256"
},
"id" : 2256
}
]
};
var fakeDomHelper = null,
global = null;
beforeEach(function () {
global = {
location: {}
};
fakeDomHelper = {
loadScript: jasmine.createSpy('loadScript'),
getLoadWindow: jasmine.createSpy('getLoadWindow').andReturn(global),
getHostName: function () { return 'test-host-name'; }
};
});
describe('support and load life cycle', function () {
var fontdeck = null,
onReady = jasmine.createSpy('onReady');
beforeEach(function () {
fontdeck = new Fontdeck(fakeDomHelper, configuration);
fontdeck.load(onReady);
});
it('should create the script correctly', function () {
expect(fakeDomHelper.loadScript).toHaveBeenCalled();
expect(fakeDomHelper.loadScript.calls[0].args[0]).toEqual('https://f.fontdeck.com/s/css/js/test-host-name/2282.js');
});
it('should have created a global', function () {
expect(global.__webfontfontdeckmodule__).not.toBeNull();
expect(global.__webfontfontdeckmodule__['2282']).not.toBeNull();
});
it('should load correctly after calling the callback', function () {
global.__webfontfontdeckmodule__['2282'](true, apiResponse);
expect(fontdeck.fonts_).toEqual([new Font(apiResponse.fonts[0].name), new Font(apiResponse.fonts[1].name, 'i7')]);
});
});
describe('no project id', function () {
var fontdeck = null,
support = null;
beforeEach(function () {
fontdeck = new Fontdeck(fakeDomHelper, { id: null });
});
it('should not have loaded any fonts', function () {
expect(fontdeck.fonts_).toEqual([]);
});
});
});
================================================
FILE: spec/modules/google/fontapiparser_spec.js
================================================
describe('modules.google.FontApiParser', function () {
var FontApiParser = webfont.modules.google.FontApiParser,
Font = webfont.Font;
describe('parsed values are coherent', function () {
var parser = null;
beforeEach(function () {
parser = new FontApiParser(['Tangerine', 'Droid Serif:bi', 'Yanone Kaffeesatz:200,300,400,700', 'Cantarell:italic,b', 'Exo:100italic', 'Lobster:200n']);
parser.parse();
});
it('should parse fonts correctly', function () {
var fonts = parser.getFonts();
expect(fonts.length).toEqual(10);
expect(fonts).toEqual([
new Font('Tangerine', 'n4'),
new Font('Droid Serif', 'i7'),
new Font('Yanone Kaffeesatz', 'n2'),
new Font('Yanone Kaffeesatz', 'n3'),
new Font('Yanone Kaffeesatz', 'n4'),
new Font('Yanone Kaffeesatz', 'n7'),
new Font('Cantarell', 'i4'),
new Font('Cantarell', 'n7'),
new Font('Exo', 'i1'),
new Font('Lobster', 'n2')
]);
});
});
describe('mix of numeric weight and style', function () {
var parser = null;
beforeEach(function () {
parser = new FontApiParser(['Nobile:700i,b,200i,r,i700']);
parser.parse();
});
it('should parse fonts correctly', function () {
var fonts = parser.getFonts();
expect(fonts.length).toEqual(4);
expect(fonts).toEqual([
new Font('Nobile', 'i7'),
new Font('Nobile', 'n7'),
new Font('Nobile', 'i2'),
new Font('Nobile', 'n4')
]);
});
});
describe('typo bild instead of bold', function () {
var parser = null;
beforeEach(function () {
parser = new FontApiParser(['Nobile:bild']);
parser.parse();
});
it('should parse families correctly', function () {
var fonts = parser.getFonts();
expect(fonts.length).toEqual(1);
expect(fonts[0]).toEqual(new Font('Nobile', 'n4'));
});
});
describe('variations with dashes', function () {
var parser = null;
beforeEach(function () {
parser = new FontApiParser(['Nobile:semi-bold']);
parser.parse();
});
it('should parse the variation correctly', function () {
var fonts = parser.getFonts();
expect(fonts.length).toEqual(1);
expect(fonts[0]).toEqual(new Font('Nobile', 'n6'));
});
});
describe('nonsense', function () {
var parser = null;
beforeEach(function () {
parser = new FontApiParser(['Nobile:dwe,^%^%fewf,$9940@#!@#$%^&*()_+}POIBJ{}{']);
parser.parse();
});
it('should parse families correctly', function () {
var fonts = parser.getFonts();
expect(fonts.length).toEqual(1);
expect(fonts[0]).toEqual(new Font('Nobile', 'n4'));
});
});
describe('no weight and one subset defined', function () {
var parser = null;
beforeEach(function () {
parser = new FontApiParser(['Cantarell::greek']);
parser.parse();
});
it('should parse families correctly', function () {
var fonts = parser.getFonts();
expect(fonts.length).toEqual(1);
expect(fonts[0]).toEqual(new Font('Cantarell', 'n4'));
});
it('should parse pick test strings correctly', function () {
var testStrings = parser.getFontTestStrings(),
cantarell = testStrings['Cantarell'];
expect(cantarell).not.toBeNull();
expect(cantarell).toEqual(FontApiParser.INT_FONTS['greek']);
});
});
describe('no weight and multiple subsets defined', function () {
var parser = null;
beforeEach(function () {
parser = new FontApiParser(['Cantarell::cyrillic,greek,latin']);
parser.parse();
});
it('should parse families correctly', function () {
var fonts = parser.getFonts();
expect(fonts.length).toEqual(1);
expect(fonts[0]).toEqual(new Font('Cantarell', 'n4'));
});
it('should parse pick test strings correctly', function () {
var testStrings = parser.getFontTestStrings(),
cantarell = testStrings['Cantarell'];
expect(cantarell).not.toBeNull();
expect(cantarell).toEqual(FontApiParser.INT_FONTS['cyrillic']);
});
});
describe('weight and multiple subsets defined', function () {
var parser = null;
beforeEach(function () {
parser = new FontApiParser(['Cantarell:regular,bold:cyrillic,greek,latin']);
parser.parse();
});
it('should parse families correctly', function () {
var fonts = parser.getFonts();
expect(fonts.length).toEqual(2);
expect(fonts).toEqual([
new Font('Cantarell', 'n4'),
new Font('Cantarell', 'n7')
]);
});
it('should parse pick test strings correctly', function () {
var testStrings = parser.getFontTestStrings(),
cantarell = testStrings['Cantarell'];
expect(cantarell).not.toBeNull();
expect(cantarell).toEqual(FontApiParser.INT_FONTS['cyrillic']);
});
});
describe('Hanuman is backward compatible', function () {
var parser = null;
beforeEach(function () {
parser = new FontApiParser(['Hanuman']);
parser.parse();
});
it('should parse families correctly', function () {
var fonts = parser.getFonts();
expect(fonts.length).toEqual(1);
expect(fonts[0]).toEqual(new Font('Hanuman', 'n4'));
});
it('should parse pick test strings correctly', function () {
var testStrings = parser.getFontTestStrings(),
hanuman = testStrings['Hanuman'];
expect(hanuman).not.toBeNull();
expect(hanuman).toEqual(FontApiParser.INT_FONTS['Hanuman']);
});
});
describe('Hanuman is forward compatible', function () {
var parser = null;
beforeEach(function () {
parser = new FontApiParser(['Hanuman::khmer']);
parser.parse();
});
it('should parse families correctly', function () {
var fonts = parser.getFonts();
expect(fonts.length).toEqual(1);
expect(fonts[0]).toEqual(new Font('Hanuman', 'n4'));
});
it('should parse pick test strings correctly', function () {
var testStrings = parser.getFontTestStrings(),
hanuman = testStrings['Hanuman'];
expect(hanuman).not.toBeNull();
expect(hanuman).toEqual(FontApiParser.INT_FONTS['khmer']);
});
});
describe('plus replaced with space', function () {
var parser = null;
beforeEach(function () {
parser = new FontApiParser(['Erica+One', 'Droid+Serif::latin', 'Yanone+Kaffeesatz:400,700:latin']);
parser.parse();
});
it('should parse families correctly', function () {
var fonts = parser.getFonts();
expect(fonts.length).toEqual(4);
expect(fonts).toEqual([
new Font('Erica One', 'n4'),
new Font('Droid Serif', 'n4'),
new Font('Yanone Kaffeesatz', 'n4'),
new Font('Yanone Kaffeesatz', 'n7')
]);
});
});
});
================================================
FILE: spec/modules/google/fontapiurlbuilder_spec.js
================================================
describe('modules.google.FontApiUrlBuilder', function () {
var FontApiUrlBuilder = webfont.modules.google.FontApiUrlBuilder;
it('should throw an exception if there are no font families', function () {
var builder = new FontApiUrlBuilder('https://moo');
expect(builder.build).toThrow();
});
it('should build a proper url', function () {
var builder = new FontApiUrlBuilder('https://moo');
builder.setFontFamilies(['Font1', 'Font2']);
expect(builder.build()).toEqual('https://moo?family=Font1%7CFont2');
});
it('should build a proper url', function () {
var builder = new FontApiUrlBuilder(undefined);
builder.setFontFamilies(['Font1', 'Font2']);
expect(builder.build()).toEqual(
FontApiUrlBuilder.DEFAULT_API_URL +
'?family=Font1%7CFont2');
});
it('should build a proper url', function () {
var builder = new FontApiUrlBuilder(undefined);
builder.setFontFamilies(['Font1:bold:greek,cyrillic', 'Font2:italic', 'Font3']);
expect(builder.build()).toEqual(
FontApiUrlBuilder.DEFAULT_API_URL +
'?family=Font1:bold%7CFont2:italic%7CFont3' +
'&subset=greek,cyrillic');
});
it('should build a proper url', function () {
var builder = new FontApiUrlBuilder(undefined);
builder.setFontFamilies(['Font1:bold,italic:greek,cyrillic', 'Font2:italic', 'Font3::latin']);
expect(builder.build()).toEqual(
FontApiUrlBuilder.DEFAULT_API_URL +
'?family=Font1:bold,italic%7CFont2:italic%7CFont3' +
'&subset=greek,cyrillic,latin');
});
});
================================================
FILE: spec/modules/google/googlefontapi_spec.js
================================================
describe('modules.google.GoogleFontApi', function () {
var GoogleFontApi = webfont.modules.google.GoogleFontApi,
Any = jasmine.Matchers.Any,
Font = webfont.Font,
link = '',
insert = '',
onload = null,
fakeDomHelper = {
whenBodyExists: function (callback) {
callback();
},
loadStylesheet: function (cssLink, cb) {
link = cssLink;
onload = cb;
}
};
beforeEach(function () {
insert = '';
link = '';
onload = null;
});
function notifySheetsLoaded() {
if (onload)
onload();
};
describe('call onReady with font family loading', function () {
var googleFontApi = null,
fonts = null;
beforeEach(function () {
googleFontApi = new GoogleFontApi(fakeDomHelper, { families: ['Font1', 'Font2'] });
googleFontApi.load(function (f) {
fonts = f;
});
});
it('has inserted the link element correctly', function () {
expect(link).toEqual('https://fonts.googleapis.com/css?family=Font1%7CFont2');
});
it('has the correct families', function () {
notifySheetsLoaded();
expect(fonts).not.toBeNull();
expect(fonts.length).toEqual(2);
expect(fonts[0]).toEqual(new Font('Font1'));
expect(fonts[1]).toEqual(new Font('Font2'));
});
});
describe('call onReady with font family loading and custom API url', function () {
var googleFontApi = null;
var loaded = false;
beforeEach(function () {
loaded = false;
googleFontApi = new GoogleFontApi(fakeDomHelper, {
api: 'https://moo',
families: ['Font1', 'Font2']
});
googleFontApi.load(function () { loaded = true; });
});
it('has inserted the link element correctly', function () {
expect(link).toEqual('https://moo?family=Font1%7CFont2');
});
if (webfont.DomHelper.CAN_WAIT_STYLESHEET) {
it('does not call onReady until sheets are loaded', function () {
expect(onload).toMatch(new Any(Function));
expect(loaded).toBe(false);
notifySheetsLoaded();
expect(loaded).toBe(true);
});
}
});
describe('spaces replaced by plus', function () {
var googleFontApi = null;
beforeEach(function () {
googleFontApi = new GoogleFontApi(fakeDomHelper, { families: ['Font1 WithSpace', 'Font2 WithSpaceToo'] });
googleFontApi.load(function () {});
});
it('has inserted the link element correctly', function () {
expect(link).toEqual('https://fonts.googleapis.com/css?family=Font1+WithSpace%7CFont2+WithSpaceToo');
});
});
describe('load with variations', function () {
var googleFontApi = null;
beforeEach(function () {
googleFontApi = new GoogleFontApi(fakeDomHelper, { families: ['Font1 WithSpace:bi', 'Font2 WithSpaceToo:b,r'] });
googleFontApi.load(function () {});
});
it('has inserted the link element correctly', function () {
expect(link).toEqual('https://fonts.googleapis.com/css?family=Font1+WithSpace:bi%7CFont2+WithSpaceToo:b,r');
});
});
});
================================================
FILE: spec/modules/monotype_spec.js
================================================
describe('modules.Monotype', function () {
var Monotype = webfont.modules.Monotype,
Font = webfont.Font,
BrowserInfo = webfont.BrowserInfo,
UserAgent = webfont.UserAgent,
Version = webfont.Version;
var configuration = {
projectId: '01e2ff27-25bf-4801-a23e-73d328e6c7cc',
api: 'https://fast.fonts.net/jsapidev'
};
var fakeDomHelper = null,
global = null,
script = {},
monotype = null,
load = null,
support = null;
beforeEach(function () {
global = {};
fakeDomHelper = {
loadScript: jasmine.createSpy('loadScript').andCallFake(function (src, callback) {
script.onload = callback;
return script;
}),
getLoadWindow: jasmine.createSpy('getLoadWindow').andReturn(global)
};
support = jasmine.createSpy('support');
load = jasmine.createSpy('load');
monotype = new Monotype(fakeDomHelper, configuration);
monotype.load(load);
global[Monotype.HOOK + configuration.projectId] = function () {
return [{fontfamily: 'aachen bold'}, {fontfamily: 'kid print regular'}];
};
script.onload();
});
it('should create a script element', function () {
expect(fakeDomHelper.loadScript).toHaveBeenCalled();
expect(fakeDomHelper.loadScript.calls[0].args[0]).toEqual('https://fast.fonts.net/jsapidev/01e2ff27-25bf-4801-a23e-73d328e6c7cc.js');
expect(load).toHaveBeenCalledWith([new Font('aachen bold'), new Font('kid print regular')]);
});
});
================================================
FILE: spec/modules/typekit_spec.js
================================================
describe('modules.Typekit', function () {
var Typekit = webfont.modules.Typekit,
Font = webfont.Font;
var configuration = {
id: 'abc'
};
var fakeDomHelper = null,
global = {},
load = null,
onReady = null;
beforeEach(function () {
global = {
Typekit: {
config: {
fn: ['Font1', ['n4'], 'Font2', ['n4', 'n7']]
},
load: jasmine.createSpy('load')
}
};
onReady = jasmine.createSpy('onReady');
load = jasmine.createSpy('load');
fakeDomHelper = {
loadScript: jasmine.createSpy('loadScript').andCallFake(function (url, cb) {
cb(null);
}),
getLoadWindow: jasmine.createSpy('getLoadWindow').andReturn(global)
};
});
it('should load with variations', function () {
var typekit = new Typekit(fakeDomHelper, configuration);
typekit.load(onReady);
expect(fakeDomHelper.loadScript).toHaveBeenCalled();
expect(fakeDomHelper.loadScript.calls[0].args[0]).toEqual('https://use.typekit.net/abc.js');
expect(global.Typekit.load).toHaveBeenCalled();
typekit.load(load);
expect(load).toHaveBeenCalledWith([new Font('Font1', 'n4'), new Font('Font2', 'n4'), new Font('Font2', 'n7')]);
});
it('should load through the alternative API', function () {
var typekit = new Typekit(fakeDomHelper, { id: 'abc', api: '/test' });
typekit.load(onReady);
expect(fakeDomHelper.loadScript).toHaveBeenCalled();
expect(fakeDomHelper.loadScript.calls[0].args[0]).toEqual('/test/abc.js');
});
it('should not load without a kit id', function () {
var typekit = new Typekit(fakeDomHelper, { id: null });
typekit.load(onReady);
expect(fakeDomHelper.loadScript).not.toHaveBeenCalled();
typekit.load(load);
expect(load).toHaveBeenCalledWith([]);
});
});
================================================
FILE: src/closure.js
================================================
/* Web Font Loader v{{version}} - (c) Adobe Systems, Google. License: Apache 2.0 */
(function(){{{source}}}());
================================================
FILE: src/core/cssclassname.js
================================================
goog.provide('webfont.CssClassName');
/**
* Handles sanitization and construction of css class names.
* @param {string=} opt_joinChar The character to join parts of the name on.
* Defaults to '-'.
* @constructor
*/
webfont.CssClassName = function(opt_joinChar) {
/** @type {string} */
this.joinChar_ = opt_joinChar || webfont.CssClassName.DEFAULT_JOIN_CHAR;
};
/**
* @const
* @type {string}
*/
webfont.CssClassName.DEFAULT_JOIN_CHAR = '-';
goog.scope(function () {
var CssClassName = webfont.CssClassName;
/**
* Sanitizes a string for use as a css class name. Removes non-word and
* underscore characters.
* @param {string} name The string.
* @return {string} The sanitized string.
*/
CssClassName.prototype.sanitize = function(name) {
return name.replace(/[\W_]+/g, '').toLowerCase();
};
/**
* Builds a complete css class name given a variable number of parts.
* Sanitizes, then joins the parts together.
* @param {...string} var_args The parts to join.
* @return {string} The sanitized and joined string.
*/
CssClassName.prototype.build = function(var_args) {
var parts = []
for (var i = 0; i < arguments.length; i++) {
parts.push(this.sanitize(arguments[i]));
}
return parts.join(this.joinChar_);
};
});
================================================
FILE: src/core/domhelper.js
================================================
goog.provide('webfont.DomHelper');
/**
* Handles common DOM manipulation tasks. The aim of this library is to cover
* the needs of typical font loading. Not more, not less.
* @param {Window} mainWindow The main window webfontloader.js is loaded in.
* @param {Window=} opt_loadWindow The window we'll load the font into. By
* default, the main window is used.
* @constructor
*/
webfont.DomHelper = function(mainWindow, opt_loadWindow) {
this.mainWindow_ = mainWindow;
this.loadWindow_ = opt_loadWindow || mainWindow;
/** @type {string} */
this.protocol_;
/** @type {Document} */
this.document_ = this.loadWindow_.document;
};
goog.scope(function () {
var DomHelper = webfont.DomHelper;
/**
* The NativeFontWatchRunnner depends on the correct and reliable
* |onload| event, and browsers with the native font loading API
* have reliable @onload support as far as we know. So we use the
* event for such a case and unconditionally invokes the callback
* otherwise.
*
* @const
* @type {boolean}
*/
DomHelper.CAN_WAIT_STYLESHEET = !!window['FontFace'];
/**
* Creates an element.
* @param {string} elem The element type.
* @param {Object=} opt_attr A hash of attribute key/value pairs.
* @param {string=} opt_innerHtml Contents of the element.
* @return {Element} the new element.
*/
DomHelper.prototype.createElement = function(elem, opt_attr,
opt_innerHtml) {
var domElement = this.document_.createElement(elem);
if (opt_attr) {
for (var attr in opt_attr) {
// protect against native prototype augmentations
if (opt_attr.hasOwnProperty(attr)) {
if (attr == "style") {
this.setStyle(domElement, opt_attr[attr]);
} else {
domElement.setAttribute(attr, opt_attr[attr]);
}
}
}
}
if (opt_innerHtml) {
domElement.appendChild(this.document_.createTextNode(opt_innerHtml));
}
return domElement;
};
/**
* Inserts an element into the document. This is intended for unambiguous
* elements such as html, body, head.
* @param {string} tagName The element name.
* @param {Element} e The element to append.
* @return {boolean} True if the element was inserted.
*/
DomHelper.prototype.insertInto = function(tagName, e) {
var t = this.document_.getElementsByTagName(tagName)[0];
if (!t) { // opera allows documents without a head
t = document.documentElement;
}
// This is safer than appendChild in IE. appendChild causes random
// JS errors in IE. Sometimes errors in other JS exectution, sometimes
// complete 'This page cannot be displayed' errors. For our purposes,
// it's equivalent because we don't need to insert at any specific
// location.
t.insertBefore(e, t.lastChild);
return true;
};
/**
* Calls a function when the body tag exists.
* @param {function()} callback The function to call.
*/
DomHelper.prototype.whenBodyExists = function(callback) {
var that = this;
if (that.document_.body) {
callback();
} else {
if (that.document_.addEventListener) {
that.document_.addEventListener('DOMContentLoaded', callback);
} else {
that.document_.attachEvent('onreadystatechange', function () {
if (that.document_.readyState == 'interactive' || that.document_.readyState == 'complete') {
callback();
}
});
}
}
};
/**
* Removes an element from the DOM.
* @param {Element} node The element to remove.
* @return {boolean} True if the element was removed.
*/
DomHelper.prototype.removeElement = function(node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
return true;
}
return false;
};
/**
* @deprecated Use updateClassName().
*
* Appends a name to an element's class attribute.
* @param {Element} e The element.
* @param {string} name The class name to add.
*/
DomHelper.prototype.appendClassName = function(e, name) {
this.updateClassName(e, [name]);
};
/**
* @deprecated Use updateClassName().
*
* Removes a name to an element's class attribute.
* @param {Element} e The element.
* @param {string} name The class name to remove.
*/
DomHelper.prototype.removeClassName = function(e, name) {
this.updateClassName(e, null, [name]);
};
/**
* Updates an element's class attribute in a single change. This
* allows multiple updates in a single class name change so there
* is no chance for a browser to relayout in between changes.
*
* @param {Element} e The element.
* @param {Array.=} opt_add List of class names to add.
* @param {Array.=} opt_remove List of class names to remove.
*/
DomHelper.prototype.updateClassName = function (e, opt_add, opt_remove) {
var add = opt_add || [],
remove = opt_remove || [];
var classes = e.className.split(/\s+/);
for (var i = 0; i < add.length; i += 1) {
var found = false;
for (var j = 0; j < classes.length; j += 1) {
if (add[i] === classes[j]) {
found = true;
break;
}
}
if (!found) {
classes.push(add[i]);
}
}
var remainingClasses = [];
for (var i = 0; i < classes.length; i += 1) {
var found = false;
for (var j = 0; j < remove.length; j += 1) {
if (classes[i] === remove[j]) {
found = true;
break;
}
}
if (!found) {
remainingClasses.push(classes[i]);
}
}
e.className = remainingClasses.join(' ')
.replace(/\s+/g, ' ')
.replace(/^\s+|\s+$/, '');
};
/**
* Returns true if an element has a given class name and false otherwise.
* @param {Element} e The element.
* @param {string} name The class name to check for.
* @return {boolean} Whether or not the element has this class name.
*/
DomHelper.prototype.hasClassName = function(e, name) {
var classes = e.className.split(/\s+/);
for (var i = 0, len = classes.length; i < len; i++) {
if (classes[i] == name) {
return true;
}
}
return false;
};
/**
* Sets the style attribute on an element.
* @param {Element} e The element.
* @param {string} styleString The style string.
*/
DomHelper.prototype.setStyle = function(e, styleString) {
e.style.cssText = styleString;
};
/**
* @return {Window} The main window webfontloader.js is loaded in (for config).
*/
DomHelper.prototype.getMainWindow = function() {
return this.mainWindow_;
};
/**
* @return {Window} The window that we're loading the font(s) into.
*/
DomHelper.prototype.getLoadWindow = function() {
return this.loadWindow_;
};
/**
* Returns the hostname of the current document.
* @return {string} hostname.
*/
DomHelper.prototype.getHostName = function() {
return this.getLoadWindow().location.hostname || this.getMainWindow().location.hostname;
};
/**
* Creates a style element.
* @param {string} css Contents of the style element.
* @return {Element} a DOM element.
*/
DomHelper.prototype.createStyle = function(css) {
var e = this.createElement('style');
e.setAttribute('type', 'text/css');
if (e.styleSheet) { // IE
e.styleSheet.cssText = css;
} else {
e.appendChild(document.createTextNode(css));
}
return e;
};
/**
* Loads an external stylesheet.
*
* @param {string} href the URL of the stylesheet
* @param {function(Error)=} opt_callback Called when the stylesheet has loaded or failed to
* load. Note that the callback is *NOT* guaranteed to be called in all browsers. The first
* argument to the callback is an error object that is falsy when there are no errors and
* truthy when there are.
* @param {boolean=} opt_async True if the stylesheet should be loaded asynchronously. Defaults to false.
* @return {Element} The link element
*/
DomHelper.prototype.loadStylesheet = function (href, opt_callback, opt_async) {
var link = this.createElement('link', {
'rel': 'stylesheet',
'href': href,
'media': (opt_async ? 'only x' : 'all')
});
var sheets = this.document_.styleSheets,
eventFired = false,
asyncResolved = !opt_async,
callbackArg = null,
callback = opt_callback || null;
function mayInvokeCallback() {
if (callback && eventFired && asyncResolved) {
callback(callbackArg);
callback = null;
}
}
if (DomHelper.CAN_WAIT_STYLESHEET) {
link.onload = function () {
eventFired = true;
mayInvokeCallback();
};
link.onerror = function () {
eventFired = true;
callbackArg = new Error('Stylesheet failed to load');
mayInvokeCallback();
};
} else {
// Some callers expect opt_callback being called asynchronously.
setTimeout(function () {
eventFired = true;
mayInvokeCallback();
}, 0);
}
function onStylesheetAvailable(callback) {
for (var i = 0; i < sheets.length; i++) {
if (sheets[i].href && sheets[i].href.indexOf(href) !== -1) {
return callback();
}
}
setTimeout(function () {
onStylesheetAvailable(callback);
}, 0);
}
function onMediaAvailable(callback) {
for (var i = 0; i < sheets.length; i++) {
if (sheets[i].href && sheets[i].href.indexOf(href) !== -1 && sheets[i].media) {
/**
* @type {string|MediaList|null}
*/
var media = sheets[i].media;
if (media === "all" || (media.mediaText && media.mediaText === "all")) {
return callback();
}
}
}
setTimeout(function () {
onMediaAvailable(callback);
}, 0);
}
this.insertInto('head', link);
if (opt_async) {
onStylesheetAvailable(function () {
link.media = "all";
// The media type change doesn't take effect immediately on Chrome, so
// we'll query the media attribute on the stylesheet until it changes
// to "all".
onMediaAvailable(function () {
asyncResolved = true;
mayInvokeCallback();
});
});
}
return link;
};
/**
* Loads an external script file.
* @param {string} src URL of the script.
* @param {function(Error)=} opt_callback callback when the script has loaded. The first argument to
* the callback is an error object that is falsy when there are no errors and truthy when there are.
* @param {number=} opt_timeout The number of milliseconds after which the callback will be called
* with a timeout error. Defaults to 5 seconds.
* @return {Element} The script element
*/
DomHelper.prototype.loadScript = function(src, opt_callback, opt_timeout) {
var head = this.document_.getElementsByTagName('head')[0];
if (head) {
var script = this.createElement('script', {
'src': src
});
var done = false;
script.onload = script.onreadystatechange = function() {
if (!done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')) {
done = true;
if (opt_callback) {
opt_callback(null);
}
script.onload = script.onreadystatechange = null;
// Avoid a bizarre issue with unclosed tag in IE6 - http://blog.dotsmart.net/2008/04/
if (script.parentNode.tagName == 'HEAD') head.removeChild(script);
}
};
head.appendChild(script);
setTimeout(function () {
if (!done) {
done = true;
if (opt_callback) {
opt_callback(new Error('Script load timeout'));
}
}
}, opt_timeout || 5000);
return script;
}
return null;
};
});
================================================
FILE: src/core/eventdispatcher.js
================================================
goog.provide('webfont.EventDispatcher');
goog.require('webfont.CssClassName');
/**
* A class to dispatch events and manage the event class names on an html
* element that represent the current state of fonts on the page. Active class
* names always overwrite inactive class names of the same type, while loading
* class names may be present whenever a font is loading (regardless of if an
* associated active or inactive class name is also present).
*
* @param {webfont.DomHelper} domHelper
* @param {Object} config
* @constructor
*/
webfont.EventDispatcher = function(domHelper, config) {
this.domHelper_ = domHelper;
this.htmlElement_ = domHelper.getLoadWindow().document.documentElement;
this.callbacks_ = config;
this.namespace_ = webfont.EventDispatcher.DEFAULT_NAMESPACE;
this.cssClassName_ = new webfont.CssClassName('-');
this.dispatchEvents_ = config['events'] !== false;
this.setClasses_ = config['classes'] !== false;
};
/**
* @const
* @type {string}
*/
webfont.EventDispatcher.DEFAULT_NAMESPACE = 'wf';
/**
* @const
* @type {string}
*/
webfont.EventDispatcher.LOADING = 'loading';
/**
* @const
* @type {string}
*/
webfont.EventDispatcher.ACTIVE = 'active';
/**
* @const
* @type {string}
*/
webfont.EventDispatcher.INACTIVE = 'inactive';
/**
* @const
* @type {string}
*/
webfont.EventDispatcher.FONT = 'font';
goog.scope(function () {
var EventDispatcher = webfont.EventDispatcher;
/**
* Dispatch the loading event and append the loading class name.
*/
EventDispatcher.prototype.dispatchLoading = function() {
if (this.setClasses_) {
this.domHelper_.updateClassName(this.htmlElement_,
[
this.cssClassName_.build(this.namespace_, webfont.EventDispatcher.LOADING)
]
);
}
this.dispatch_(webfont.EventDispatcher.LOADING);
};
/**
* Dispatch the font loading event and append the font loading class name.
* @param {webfont.Font} font
*/
EventDispatcher.prototype.dispatchFontLoading = function(font) {
if (this.setClasses_) {
this.domHelper_.updateClassName(this.htmlElement_,
[
this.cssClassName_.build(this.namespace_, font.getName(), font.getVariation().toString(), webfont.EventDispatcher.LOADING)
]
);
}
this.dispatch_(webfont.EventDispatcher.FONT + webfont.EventDispatcher.LOADING, font);
};
/**
* Dispatch the font active event, remove the font loading class name, remove
* the font inactive class name, and append the font active class name.
* @param {webfont.Font} font
*/
EventDispatcher.prototype.dispatchFontActive = function(font) {
if (this.setClasses_) {
this.domHelper_.updateClassName(
this.htmlElement_,
[
this.cssClassName_.build(this.namespace_, font.getName(), font.getVariation().toString(), webfont.EventDispatcher.ACTIVE)
],
[
this.cssClassName_.build(this.namespace_, font.getName(), font.getVariation().toString(), webfont.EventDispatcher.LOADING),
this.cssClassName_.build(this.namespace_, font.getName(), font.getVariation().toString(), webfont.EventDispatcher.INACTIVE)
]
);
}
this.dispatch_(webfont.EventDispatcher.FONT + webfont.EventDispatcher.ACTIVE, font);
};
/**
* Dispatch the font inactive event, remove the font loading class name, and
* append the font inactive class name (unless the font active class name is
* already present).
* @param {webfont.Font} font
*/
EventDispatcher.prototype.dispatchFontInactive = function(font) {
if (this.setClasses_) {
var hasFontActive = this.domHelper_.hasClassName(this.htmlElement_,
this.cssClassName_.build(this.namespace_, font.getName(), font.getVariation().toString(), webfont.EventDispatcher.ACTIVE)
),
add = [],
remove = [
this.cssClassName_.build(this.namespace_, font.getName(), font.getVariation().toString(), webfont.EventDispatcher.LOADING)
];
if (!hasFontActive) {
add.push(this.cssClassName_.build(this.namespace_, font.getName(), font.getVariation().toString(), webfont.EventDispatcher.INACTIVE));
}
this.domHelper_.updateClassName(this.htmlElement_, add, remove);
}
this.dispatch_(webfont.EventDispatcher.FONT + webfont.EventDispatcher.INACTIVE, font);
};
/**
* Dispatch the inactive event, remove the loading class name, and append the
* inactive class name (unless the active class name is already present).
*/
EventDispatcher.prototype.dispatchInactive = function() {
if (this.setClasses_) {
var hasActive = this.domHelper_.hasClassName(this.htmlElement_,
this.cssClassName_.build(this.namespace_, webfont.EventDispatcher.ACTIVE)
),
add = [],
remove = [
this.cssClassName_.build(this.namespace_, webfont.EventDispatcher.LOADING)
];
if (!hasActive) {
add.push(this.cssClassName_.build(this.namespace_, webfont.EventDispatcher.INACTIVE));
}
this.domHelper_.updateClassName(this.htmlElement_, add, remove);
}
this.dispatch_(webfont.EventDispatcher.INACTIVE);
};
/**
* Dispatch the active event, remove the loading class name, remove the inactive
* class name, and append the active class name.
*/
EventDispatcher.prototype.dispatchActive = function() {
if (this.setClasses_) {
this.domHelper_.updateClassName(this.htmlElement_,
[
this.cssClassName_.build(this.namespace_, webfont.EventDispatcher.ACTIVE)
],
[
this.cssClassName_.build(this.namespace_, webfont.EventDispatcher.LOADING),
this.cssClassName_.build(this.namespace_, webfont.EventDispatcher.INACTIVE)
]
);
}
this.dispatch_(webfont.EventDispatcher.ACTIVE);
};
/**
* @param {string} event
* @param {webfont.Font=} opt_font
*/
EventDispatcher.prototype.dispatch_ = function(event, opt_font) {
if (this.dispatchEvents_ && this.callbacks_[event]) {
if (opt_font) {
this.callbacks_[event](opt_font.getName(), opt_font.getVariation());
} else {
this.callbacks_[event]();
}
}
};
});
================================================
FILE: src/core/font.js
================================================
goog.provide('webfont.Font');
/**
* This class is an abstraction for a single font or typeface.
* It contains the font name and the variation (i.e. style
* and weight.) A collection Font instances can represent a
* font family.
*
* @constructor
* @param {string} name The font family name
* @param {string=} opt_variation A font variation description
*/
webfont.Font = function (name, opt_variation) {
this.name_ = name;
this.weight_ = 4;
this.style_ = 'n'
var variation = opt_variation || 'n4',
match = variation.match(/^([nio])([1-9])$/i);
if (match) {
this.style_ = match[1];
this.weight_ = parseInt(match[2], 10);
}
};
goog.scope(function () {
var Font = webfont.Font;
/**
* @return {string}
*/
Font.prototype.getName = function () {
return this.name_;
};
/**
* @return {string}
*/
Font.prototype.getCssName = function () {
return this.quote_(this.name_);
};
/**
* Returns a CSS string representation of the font that
* can be used as the CSS font property shorthand.
*
* @return {string}
*/
Font.prototype.toCssString = function () {
return this.getCssStyle() + ' ' + this.getCssWeight() + ' 300px ' + this.getCssName();
};
/**
* @private
* @param {string} name
* @return {string}
*/
Font.prototype.quote_ = function (name) {
var quoted = [];
var split = name.split(/,\s*/);
for (var i = 0; i < split.length; i++) {
var part = split[i].replace(/['"]/g, '');
if (part.indexOf(' ') == -1 && !(/^\d/.test(part))) {
quoted.push(part);
} else {
quoted.push("'" + part + "'");
}
}
return quoted.join(',');
};
/**
* @return {string}
*/
Font.prototype.getVariation = function () {
return this.style_ + this.weight_;
};
/**
* @return {string}
*/
Font.prototype.getCssVariation = function () {
return 'font-style:' + this.getCssStyle() + ';font-weight:' + this.getCssWeight() + ';';
};
/**
* @return {string}
*/
Font.prototype.getCssWeight = function () {
return this.weight_ + '00';
};
/**
* @return {string}
*/
Font.prototype.getCssStyle = function () {
var style = 'normal';
if (this.style_ === 'o') {
style = 'oblique';
} else if (this.style_ === 'i') {
style = 'italic';
}
return style;
};
/**
* Parses a CSS font declaration and returns a font
* variation description.
*
* @param {string} css
* @return {string}
*/
Font.parseCssVariation = function (css) {
var weight = 4,
style = 'n',
m = null;
if (css) {
m = css.match(/(normal|oblique|italic)/i);
if (m && m[1]) {
style = m[1].substr(0, 1).toLowerCase();
}
m = css.match(/([1-9]00|normal|bold)/i);
if (m && m[1]) {
if (/bold/i.test(m[1])) {
weight = 7;
} else if (/[1-9]00/.test(m[1])) {
weight = parseInt(m[1].substr(0, 1), 10);
}
}
}
return style + weight;
}
});
================================================
FILE: src/core/fontmodule.js
================================================
goog.provide('webfont.FontModule');
/**
* @interface
*/
webfont.FontModule = function () {};
goog.scope(function () {
var FontModule = webfont.FontModule;
/**
* @param {function(Array., webfont.FontTestStrings=, Object.=)} onReady
*/
FontModule.prototype.load = function (onReady) {};
});
================================================
FILE: src/core/fontmoduleloader.js
================================================
goog.provide('webfont.FontModuleLoader');
goog.provide('webfont.FontModuleFactory');
/** @typedef {function(Object, webfont.DomHelper): webfont.FontModule} */
webfont.FontModuleFactory;
/**
* @constructor
*/
webfont.FontModuleLoader = function() {
/**
* @type {Object.}
*/
this.modules_ = {};
};
goog.scope(function () {
var FontModuleLoader = webfont.FontModuleLoader;
/**
* @param {string} name
* @param {webfont.FontModuleFactory} factory
*/
FontModuleLoader.prototype.addModuleFactory = function(name, factory) {
this.modules_[name] = factory;
};
/**
* @param {Object} configuration
* @param {webfont.DomHelper} domHelper
* @return {Array.}
*/
FontModuleLoader.prototype.getModules = function(configuration, domHelper) {
var modules = [];
for (var key in configuration) {
if (configuration.hasOwnProperty(key)) {
var moduleFactory = this.modules_[key];
if (moduleFactory) {
modules.push(moduleFactory(configuration[key], domHelper));
}
}
}
return modules;
};
});
================================================
FILE: src/core/fontruler.js
================================================
goog.provide('webfont.FontRuler');
/**
* An element that can be used to measure the metrics
* of a given font and string.
* @constructor
* @param {webfont.DomHelper} domHelper
* @param {string} fontTestString
*/
webfont.FontRuler = function (domHelper, fontTestString) {
this.domHelper_ = domHelper;
this.fontTestString_ = fontTestString;
this.el_ = this.domHelper_.createElement('span', {
"aria-hidden": "true"
}, this.fontTestString_);
};
goog.scope(function () {
var FontRuler = webfont.FontRuler;
/**
* @param {webfont.Font} font
*/
FontRuler.prototype.setFont = function(font) {
this.domHelper_.setStyle(this.el_, this.computeStyleString_(font));
};
/**
* Inserts the ruler into the DOM.
*/
FontRuler.prototype.insert = function() {
this.domHelper_.insertInto('body', this.el_);
};
/**
* @private
* @param {webfont.Font} font
* @return {string}
*/
FontRuler.prototype.computeStyleString_ = function(font) {
return "display:block;position:absolute;top:-9999px;left:-9999px;" +
"font-size:300px;width:auto;height:auto;line-height:normal;margin:0;" +
"padding:0;font-variant:normal;white-space:nowrap;font-family:" +
font.getCssName() + ";" + font.getCssVariation();
};
/**
* @return {number}
*/
FontRuler.prototype.getWidth = function() {
return this.el_.offsetWidth;
};
/**
* Removes the ruler element from the DOM.
*/
FontRuler.prototype.remove = function() {
this.domHelper_.removeElement(this.el_);
};
});
================================================
FILE: src/core/fontwatcher.js
================================================
goog.provide('webfont.FontWatcher');
goog.require('webfont.FontWatchRunner');
goog.require('webfont.NativeFontWatchRunner');
/**
* @typedef {Object.>}
*/
webfont.FontTestStrings;
/**
* @constructor
* @param {webfont.DomHelper} domHelper
* @param {webfont.EventDispatcher} eventDispatcher
* @param {number=} opt_timeout
*/
webfont.FontWatcher = function(domHelper, eventDispatcher, opt_timeout) {
this.domHelper_ = domHelper;
this.eventDispatcher_ = eventDispatcher;
this.currentlyWatched_ = 0;
this.last_ = false;
this.success_ = false;
this.timeout_ = opt_timeout;
};
goog.scope(function () {
var FontWatcher = webfont.FontWatcher,
FontWatchRunner = webfont.FontWatchRunner,
NativeFontWatchRunner = webfont.NativeFontWatchRunner;
/**
* @type {null|boolean}
*/
FontWatcher.SHOULD_USE_NATIVE_LOADER = null;
/**
* @return {string}
*/
FontWatcher.getUserAgent = function () {
return window.navigator.userAgent;
};
/**
* @return {string}
*/
FontWatcher.getVendor = function () {
return window.navigator.vendor;
};
/**
* Returns true if this browser has support for
* the CSS font loading API.
*
* @return {boolean}
*/
FontWatcher.shouldUseNativeLoader = function () {
if (FontWatcher.SHOULD_USE_NATIVE_LOADER === null) {
if (!!window.FontFace) {
var match = /Gecko.*Firefox\/(\d+)/.exec(FontWatcher.getUserAgent());
var safari10Match = /OS X.*Version\/10\..*Safari/.exec(FontWatcher.getUserAgent()) && /Apple/.exec(FontWatcher.getVendor());
if (match) {
FontWatcher.SHOULD_USE_NATIVE_LOADER = parseInt(match[1], 10) > 42;
} else if (safari10Match) {
FontWatcher.SHOULD_USE_NATIVE_LOADER = false;
} else {
FontWatcher.SHOULD_USE_NATIVE_LOADER = true;
}
} else {
FontWatcher.SHOULD_USE_NATIVE_LOADER = false;
}
}
return FontWatcher.SHOULD_USE_NATIVE_LOADER;
};
/**
* Watches a set of font families.
* @param {Array.} fonts The fonts to watch.
* @param {webfont.FontTestStrings} fontTestStrings The font test strings for
* each family.
* @param {Object.} metricCompatibleFonts
* @param {boolean} last True if this is the last set of fonts to watch.
*/
FontWatcher.prototype.watchFonts = function(fonts,
fontTestStrings, metricCompatibleFonts, last) {
var length = fonts.length,
testStrings = fontTestStrings || {};
if (length === 0 && last) {
this.eventDispatcher_.dispatchInactive();
return;
}
this.currentlyWatched_ += fonts.length;
if (last) {
this.last_ = last;
}
var i, fontWatchRunners = [];
for (i = 0; i < fonts.length; i++) {
var font = fonts[i],
testString = testStrings[font.getName()];
this.eventDispatcher_.dispatchFontLoading(font);
var fontWatchRunner = null;
if (FontWatcher.shouldUseNativeLoader()) {
fontWatchRunner = new NativeFontWatchRunner(
goog.bind(this.fontActive_, this),
goog.bind(this.fontInactive_, this),
this.domHelper_,
font,
this.timeout_,
testString
);
} else {
fontWatchRunner = new FontWatchRunner(
goog.bind(this.fontActive_, this),
goog.bind(this.fontInactive_, this),
this.domHelper_,
font,
this.timeout_,
metricCompatibleFonts,
testString
);
}
fontWatchRunners.push(fontWatchRunner);
}
for (i = 0; i < fontWatchRunners.length; i++) {
fontWatchRunners[i].start();
}
};
/**
* Called by a FontWatchRunner when a font has been detected as active.
* @param {webfont.Font} font
* @private
*/
FontWatcher.prototype.fontActive_ = function(font) {
this.eventDispatcher_.dispatchFontActive(font);
this.success_ = true;
this.decreaseCurrentlyWatched_();
};
/**
* Called by a FontWatchRunner when a font has been detected as inactive.
* @param {webfont.Font} font
* @private
*/
FontWatcher.prototype.fontInactive_ = function(font) {
this.eventDispatcher_.dispatchFontInactive(font);
this.decreaseCurrentlyWatched_();
};
/**
* @private
*/
FontWatcher.prototype.decreaseCurrentlyWatched_ = function() {
if (--this.currentlyWatched_ == 0 && this.last_) {
if (this.success_) {
this.eventDispatcher_.dispatchActive();
} else {
this.eventDispatcher_.dispatchInactive();
}
}
};
});
================================================
FILE: src/core/fontwatchrunner.js
================================================
goog.provide('webfont.FontWatchRunner');
goog.require('webfont.Font');
goog.require('webfont.FontRuler');
/**
* @constructor
* @param {function(webfont.Font)} activeCallback
* @param {function(webfont.Font)} inactiveCallback
* @param {webfont.DomHelper} domHelper
* @param {webfont.Font} font
* @param {number=} opt_timeout
* @param {Object.=} opt_metricCompatibleFonts
* @param {string=} opt_fontTestString
*/
webfont.FontWatchRunner = function(activeCallback, inactiveCallback, domHelper,
font, opt_timeout, opt_metricCompatibleFonts, opt_fontTestString) {
this.activeCallback_ = activeCallback;
this.inactiveCallback_ = inactiveCallback;
this.domHelper_ = domHelper;
this.font_ = font;
this.fontTestString_ = opt_fontTestString || webfont.FontWatchRunner.DEFAULT_TEST_STRING;
this.lastResortWidths_ = {};
this.timeout_ = opt_timeout || 3000;
this.metricCompatibleFonts_ = opt_metricCompatibleFonts || null;
this.fontRulerA_ = null;
this.fontRulerB_ = null;
this.lastResortRulerA_ = null;
this.lastResortRulerB_ = null;
this.setupRulers_();
};
/**
* @enum {string}
* @const
*/
webfont.FontWatchRunner.LastResortFonts = {
SERIF: 'serif',
SANS_SERIF: 'sans-serif'
};
/**
* Default test string. Characters are chosen so that their widths vary a lot
* between the fonts in the default stacks. We want each fallback stack
* to always start out at a different width than the other.
* @type {string}
* @const
*/
webfont.FontWatchRunner.DEFAULT_TEST_STRING = 'BESbswy';
goog.scope(function () {
var FontWatchRunner = webfont.FontWatchRunner,
Font = webfont.Font,
FontRuler = webfont.FontRuler;
/**
* @type {null|boolean}
*/
FontWatchRunner.HAS_WEBKIT_FALLBACK_BUG = null;
/**
* @return {string}
*/
FontWatchRunner.getUserAgent = function () {
return window.navigator.userAgent;
};
/**
* Returns true if this browser is WebKit and it has the fallback bug
* which is present in WebKit 536.11 and earlier.
*
* @return {boolean}
*/
FontWatchRunner.hasWebKitFallbackBug = function () {
if (FontWatchRunner.HAS_WEBKIT_FALLBACK_BUG === null) {
var match = /AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(FontWatchRunner.getUserAgent());
FontWatchRunner.HAS_WEBKIT_FALLBACK_BUG = !!match &&
(parseInt(match[1], 10) < 536 ||
(parseInt(match[1], 10) === 536 &&
parseInt(match[2], 10) <= 11));
}
return FontWatchRunner.HAS_WEBKIT_FALLBACK_BUG;
};
/**
* @private
*/
FontWatchRunner.prototype.setupRulers_ = function() {
this.fontRulerA_ = new FontRuler(this.domHelper_, this.fontTestString_);
this.fontRulerB_ = new FontRuler(this.domHelper_, this.fontTestString_);
this.lastResortRulerA_ = new FontRuler(this.domHelper_, this.fontTestString_);
this.lastResortRulerB_ = new FontRuler(this.domHelper_, this.fontTestString_);
this.fontRulerA_.setFont(new Font(this.font_.getName() + ',' + FontWatchRunner.LastResortFonts.SERIF, this.font_.getVariation()));
this.fontRulerB_.setFont(new Font(this.font_.getName() + ',' + FontWatchRunner.LastResortFonts.SANS_SERIF, this.font_.getVariation()));
this.lastResortRulerA_.setFont(new Font(FontWatchRunner.LastResortFonts.SERIF, this.font_.getVariation()));
this.lastResortRulerB_.setFont(new Font(FontWatchRunner.LastResortFonts.SANS_SERIF, this.font_.getVariation()));
this.fontRulerA_.insert();
this.fontRulerB_.insert();
this.lastResortRulerA_.insert();
this.lastResortRulerB_.insert();
};
FontWatchRunner.prototype.start = function() {
this.lastResortWidths_[FontWatchRunner.LastResortFonts.SERIF] = this.lastResortRulerA_.getWidth();
this.lastResortWidths_[FontWatchRunner.LastResortFonts.SANS_SERIF] = this.lastResortRulerB_.getWidth();
this.started_ = goog.now();
this.check_();
};
/**
* Returns true if the given width matches the generic font family width.
*
* @private
* @param {number} width
* @param {string} lastResortFont
* @return {boolean}
*/
FontWatchRunner.prototype.widthMatches_ = function(width, lastResortFont) {
return width === this.lastResortWidths_[lastResortFont];
};
/**
* Return true if the given widths match any of the generic font family
* widths.
*
* @private
* @param {number} a
* @param {number} b
* @return {boolean}
*/
FontWatchRunner.prototype.widthsMatchLastResortWidths_ = function(a, b) {
for (var font in FontWatchRunner.LastResortFonts) {
if (FontWatchRunner.LastResortFonts.hasOwnProperty(font)) {
if (this.widthMatches_(a, FontWatchRunner.LastResortFonts[font]) &&
this.widthMatches_(b, FontWatchRunner.LastResortFonts[font])) {
return true;
}
}
}
return false;
};
/**
* @private
* Returns true if the loading has timed out.
* @return {boolean}
*/
FontWatchRunner.prototype.hasTimedOut_ = function() {
return goog.now() - this.started_ >= this.timeout_;
};
/**
* Returns true if both fonts match the normal fallback fonts.
*
* @private
* @param {number} a
* @param {number} b
* @return {boolean}
*/
FontWatchRunner.prototype.isFallbackFont_ = function (a, b) {
return this.widthMatches_(a, FontWatchRunner.LastResortFonts.SERIF) &&
this.widthMatches_(b, FontWatchRunner.LastResortFonts.SANS_SERIF);
};
/**
* Returns true if the WebKit bug is present and both widths match a last resort font.
*
* @private
* @param {number} a
* @param {number} b
* @return {boolean}
*/
FontWatchRunner.prototype.isLastResortFont_ = function (a, b) {
return FontWatchRunner.hasWebKitFallbackBug() && this.widthsMatchLastResortWidths_(a, b);
};
/**
* Returns true if the current font is metric compatible. Also returns true
* if we do not have a list of metric compatible fonts.
*
* @private
* @return {boolean}
*/
FontWatchRunner.prototype.isMetricCompatibleFont_ = function () {
return this.metricCompatibleFonts_ === null || this.metricCompatibleFonts_.hasOwnProperty(this.font_.getName());
};
/**
* Checks the width of the two spans against their original widths during each
* async loop. If the width of one of the spans is different than the original
* width, then we know that the font is rendering and finish with the active
* callback. If we wait more than 5 seconds and nothing has changed, we finish
* with the inactive callback.
*
* @private
*/
FontWatchRunner.prototype.check_ = function() {
var widthA = this.fontRulerA_.getWidth();
var widthB = this.fontRulerB_.getWidth();
if (this.isFallbackFont_(widthA, widthB) || this.isLastResortFont_(widthA, widthB)) {
if (this.hasTimedOut_()) {
if (this.isLastResortFont_(widthA, widthB) && this.isMetricCompatibleFont_()) {
this.finish_(this.activeCallback_);
} else {
this.finish_(this.inactiveCallback_);
}
} else {
this.asyncCheck_();
}
} else {
this.finish_(this.activeCallback_);
}
};
/**
* @private
*/
FontWatchRunner.prototype.asyncCheck_ = function() {
setTimeout(goog.bind(function () {
this.check_();
}, this), 50);
};
/**
* @private
* @param {function(webfont.Font)} callback
*/
FontWatchRunner.prototype.finish_ = function(callback) {
// Remove elements and trigger callback (which adds active/inactive class) asynchronously to avoid reflow chain if
// several fonts are finished loading right after each other
setTimeout(goog.bind(function () {
this.fontRulerA_.remove();
this.fontRulerB_.remove();
this.lastResortRulerA_.remove();
this.lastResortRulerB_.remove();
callback(this.font_);
}, this), 0);
};
});
================================================
FILE: src/core/initialize.js
================================================
goog.provide('webfont');
goog.require('webfont.WebFont');
goog.require('webfont.modules.Typekit');
goog.require('webfont.modules.Fontdeck');
goog.require('webfont.modules.Monotype');
goog.require('webfont.modules.Custom');
goog.require('webfont.modules.google.GoogleFontApi');
/**
* @define {boolean}
*/
var INCLUDE_CUSTOM_MODULE = false;
/**
* @define {boolean}
*/
var INCLUDE_FONTDECK_MODULE = false;
/**
* @define {boolean}
*/
var INCLUDE_MONOTYPE_MODULE = false;
/**
* @define {boolean}
*/
var INCLUDE_TYPEKIT_MODULE = false;
/**
* @define {boolean}
*/
var INCLUDE_GOOGLE_MODULE = false;
/**
* @define {string}
*/
var WEBFONT = 'WebFont';
/**
* @define {string}
*/
var WEBFONT_CONFIG = 'WebFontConfig';
/**
* @type {webfont.WebFont}
*/
var webFontLoader = new webfont.WebFont(window);
if (INCLUDE_CUSTOM_MODULE) {
webFontLoader.addModule(webfont.modules.Custom.NAME, function (configuration, domHelper) {
return new webfont.modules.Custom(domHelper, configuration);
});
}
if (INCLUDE_FONTDECK_MODULE) {
webFontLoader.addModule(webfont.modules.Fontdeck.NAME, function (configuration, domHelper) {
return new webfont.modules.Fontdeck(domHelper, configuration);
});
}
if (INCLUDE_MONOTYPE_MODULE) {
webFontLoader.addModule(webfont.modules.Monotype.NAME, function (configuration, domHelper) {
return new webfont.modules.Monotype(domHelper, configuration);
});
}
if (INCLUDE_TYPEKIT_MODULE) {
webFontLoader.addModule(webfont.modules.Typekit.NAME, function (configuration, domHelper) {
return new webfont.modules.Typekit(domHelper, configuration);
});
}
if (INCLUDE_GOOGLE_MODULE) {
webFontLoader.addModule(webfont.modules.google.GoogleFontApi.NAME, function (configuration, domHelper) {
return new webfont.modules.google.GoogleFontApi(domHelper, configuration);
});
}
var exports = {
'load': goog.bind(webFontLoader.load, webFontLoader)
};
if (typeof define === "function" && define.amd) {
define(function () {
return exports;
});
} else if (typeof module !== "undefined" && module.exports) {
module.exports = exports;
} else {
window[WEBFONT] = exports;
if (window[WEBFONT_CONFIG]) {
webFontLoader.load(window[WEBFONT_CONFIG]);
}
}
================================================
FILE: src/core/nativefontwatchrunner.js
================================================
goog.provide('webfont.NativeFontWatchRunner');
goog.require('webfont.Font');
goog.scope(function () {
/**
* @constructor
* @param {function(webfont.Font)} activeCallback
* @param {function(webfont.Font)} inactiveCallback
* @param {webfont.DomHelper} domHelper
* @param {webfont.Font} font
* @param {number=} opt_timeout
* @param {string=} opt_fontTestString
*/
webfont.NativeFontWatchRunner = function(activeCallback, inactiveCallback, domHelper, font, opt_timeout, opt_fontTestString) {
this.activeCallback_ = activeCallback;
this.inactiveCallback_ = inactiveCallback;
this.font_ = font;
this.domHelper_ = domHelper;
this.timeout_ = opt_timeout || 3000;
this.fontTestString_ = opt_fontTestString || undefined;
};
var NativeFontWatchRunner = webfont.NativeFontWatchRunner;
NativeFontWatchRunner.prototype.start = function () {
var doc = this.domHelper_.getLoadWindow().document,
that = this;
var start = goog.now();
var loader = new Promise(function (resolve, reject) {
var check = function () {
var now = goog.now();
if (now - start >= that.timeout_) {
reject();
} else {
doc.fonts.load(that.font_.toCssString(), that.fontTestString_).then(function (fonts) {
if (fonts.length >= 1) {
resolve();
} else {
setTimeout(check, 25);
}
}, function () {
reject();
});
}
};
check();
});
var timeoutId = null,
timer = new Promise(function (resolve, reject) {
timeoutId = setTimeout(reject, that.timeout_);
});
Promise.race([timer, loader]).then(function () {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
that.activeCallback_(that.font_);
}, function () {
that.inactiveCallback_(that.font_);
});
};
});
================================================
FILE: src/core/stylesheetwaiter.js
================================================
goog.provide('webfont.StyleSheetWaiter');
/**
* A utility class for handling callback from DomHelper.loadStylesheet().
*
* @constructor
*/
webfont.StyleSheetWaiter = function() {
/** @private @type {number} */
this.waitingCount_ = 0;
/** @private @type {Function} */
this.onReady_ = null;
};
goog.scope(function () {
var StyleSheetWaiter = webfont.StyleSheetWaiter;
/**
* @return {function(Error)}
*/
StyleSheetWaiter.prototype.startWaitingLoad = function() {
var self = this;
self.waitingCount_++;
return function(error) {
self.waitingCount_--;
self.fireIfReady_();
};
};
/**
* @param {Function} fn
*/
StyleSheetWaiter.prototype.waitWhileNeededThen = function(fn) {
this.onReady_ = fn;
this.fireIfReady_();
};
/**
* @private
*/
StyleSheetWaiter.prototype.fireIfReady_ = function() {
var isReady = 0 == this.waitingCount_;
if (isReady && this.onReady_) {
this.onReady_();
this.onReady_ = null;
}
};
});
================================================
FILE: src/core/webfont.js
================================================
goog.provide('webfont.WebFont');
goog.require('webfont.DomHelper');
goog.require('webfont.EventDispatcher');
goog.require('webfont.FontWatcher');
goog.require('webfont.FontModuleLoader');
/**
* @param {Window} mainWindow The main application window containing
* webfontloader.js.
* @constructor
*/
webfont.WebFont = function(mainWindow) {
this.mainWindow_ = mainWindow;
this.fontModuleLoader_ = new webfont.FontModuleLoader();
this.moduleLoading_ = 0;
this.events_ = true;
this.classes_ = true;
};
goog.scope(function () {
var WebFont = webfont.WebFont,
DomHelper = webfont.DomHelper,
EventDispatcher = webfont.EventDispatcher,
FontWatcher = webfont.FontWatcher;
/**
* @param {string} name
* @param {webfont.FontModuleFactory} factory
*/
WebFont.prototype.addModule = function(name, factory) {
this.fontModuleLoader_.addModuleFactory(name, factory);
};
/**
* @param {Object} configuration
*/
WebFont.prototype.load = function(configuration) {
var context = configuration['context'] || this.mainWindow_;
this.domHelper_ = new DomHelper(this.mainWindow_, context);
this.events_ = configuration['events'] !== false;
this.classes_ = configuration['classes'] !== false;
var eventDispatcher = new EventDispatcher(
this.domHelper_,
configuration
);
this.load_(eventDispatcher, configuration);
};
/**
* @param {webfont.EventDispatcher} eventDispatcher
* @param {webfont.FontWatcher} fontWatcher
* @param {Array.} fonts
* @param {webfont.FontTestStrings=} opt_fontTestStrings
* @param {Object.=} opt_metricCompatibleFonts
*/
WebFont.prototype.onModuleReady_ = function(eventDispatcher, fontWatcher, fonts, opt_fontTestStrings, opt_metricCompatibleFonts) {
var allModulesLoaded = --this.moduleLoading_ == 0;
if (this.classes_ || this.events_) {
setTimeout(function () {
fontWatcher.watchFonts(fonts, opt_fontTestStrings || null, opt_metricCompatibleFonts || null, allModulesLoaded);
}, 0);
}
};
/**
* @param {webfont.EventDispatcher} eventDispatcher
* @param {Object} configuration
*/
WebFont.prototype.load_ = function(eventDispatcher, configuration) {
var modules = [],
timeout = configuration['timeout'],
self = this;
// Immediately dispatch the loading event before initializing the modules
// so we know for sure that the loading event is synchronous.
eventDispatcher.dispatchLoading();
modules = this.fontModuleLoader_.getModules(configuration, this.domHelper_);
var fontWatcher = new webfont.FontWatcher(this.domHelper_, eventDispatcher, timeout);
this.moduleLoading_ = modules.length;
for (var i = 0, len = modules.length; i < len; i++) {
var module = modules[i];
module.load(function (fonts, opt_fontTestStrings, opt_metricCompatibleFonts) {
self.onModuleReady_(eventDispatcher, fontWatcher, fonts, opt_fontTestStrings, opt_metricCompatibleFonts);
});
}
};
});
================================================
FILE: src/modules/custom.js
================================================
goog.provide('webfont.modules.Custom');
goog.require('webfont.Font');
goog.require('webfont.StyleSheetWaiter');
/**
*
* WebFont.load({
* custom: {
* families: ['Font1', 'Font2'],
* urls: [ 'https://moo', 'https://meuh' ] }
* });
*
* @constructor
* @implements {webfont.FontModule}
*/
webfont.modules.Custom = function(domHelper, configuration) {
this.domHelper_ = domHelper;
this.configuration_ = configuration;
};
/**
* @const
* @type {string}
*/
webfont.modules.Custom.NAME = 'custom';
goog.scope(function () {
var Custom = webfont.modules.Custom,
Font = webfont.Font,
StyleSheetWaiter = webfont.StyleSheetWaiter;
Custom.prototype.load = function(onReady) {
var i, len;
var urls = this.configuration_['urls'] || [];
var familiesConfiguration = this.configuration_['families'] || [];
var fontTestStrings = this.configuration_['testStrings'] || {};
var waiter = new StyleSheetWaiter();
for (i = 0, len = urls.length; i < len; i++) {
this.domHelper_.loadStylesheet(urls[i], waiter.startWaitingLoad());
}
var fonts = [];
for (i = 0, len = familiesConfiguration.length; i < len; i++) {
var components = familiesConfiguration[i].split(":");
if (components[1]) {
var variations = components[1].split(",");
for (var j = 0; j < variations.length; j += 1) {
fonts.push(new Font(components[0], variations[j]));
}
} else {
fonts.push(new Font(components[0]));
}
}
waiter.waitWhileNeededThen(function() {
onReady(fonts, fontTestStrings);
});
};
});
================================================
FILE: src/modules/fontdeck.js
================================================
goog.provide('webfont.modules.Fontdeck');
goog.require('webfont.Font');
/**
* @constructor
* @implements {webfont.FontModule}
*/
webfont.modules.Fontdeck = function(domHelper, configuration) {
this.domHelper_ = domHelper;
this.configuration_ = configuration;
this.fonts_ = [];
};
/**
* @const
* @type {string}
*/
webfont.modules.Fontdeck.NAME = 'fontdeck';
webfont.modules.Fontdeck.HOOK = '__webfontfontdeckmodule__';
webfont.modules.Fontdeck.API = 'https://f.fontdeck.com/s/css/js/';
goog.scope(function () {
var Fontdeck = webfont.modules.Fontdeck,
Font = webfont.Font,
FontVariationDescription = webfont.FontVariationDescription;
Fontdeck.prototype.getScriptSrc = function(projectId) {
// For empty iframes, fall back to main window's hostname.
var hostname = this.domHelper_.getHostName();
var api = this.configuration_['api'] || webfont.modules.Fontdeck.API;
return api + hostname + '/' + projectId + '.js';
};
Fontdeck.prototype.load = function(onReady) {
var projectId = this.configuration_['id'];
var loadWindow = this.domHelper_.getLoadWindow();
var self = this;
if (projectId) {
// Provide data to Fontdeck for processing.
if (!loadWindow[webfont.modules.Fontdeck.HOOK]) {
loadWindow[webfont.modules.Fontdeck.HOOK] = {};
}
// Fontdeck will call this function to indicate support status
// and what fonts are provided.
loadWindow[webfont.modules.Fontdeck.HOOK][projectId] = function(fontdeckSupports, data) {
for (var i = 0, j = data['fonts'].length; i= 2) {
var fvds = this.parseVariations_(elements[1]);
if (fvds.length > 0) {
variations = fvds;
}
if (elements.length == 3) {
var subsets = this.parseSubsets_(elements[2]);
if (subsets.length > 0) {
var fontTestString = FontApiParser.INT_FONTS[subsets[0]];
if (fontTestString) {
this.fontTestStrings_[fontFamily] = fontTestString;
}
}
}
}
// For backward compatibility
if (!this.fontTestStrings_[fontFamily]) {
var hanumanTestString = FontApiParser.INT_FONTS[fontFamily];
if (hanumanTestString) {
this.fontTestStrings_[fontFamily] = hanumanTestString;
}
}
for (var j = 0; j < variations.length; j += 1) {
this.parsedFonts_.push(new Font(fontFamily, variations[j]));
}
}
};
FontApiParser.prototype.generateFontVariationDescription_ = function(variation) {
if (!variation.match(/^[\w-]+$/)) {
return '';
}
var normalizedVariation = variation.toLowerCase();
var groups = FontApiParser.VARIATION_MATCH.exec(normalizedVariation);
if (groups == null) {
return '';
}
var styleMatch = this.normalizeStyle_(groups[2]);
var weightMatch = this.normalizeWeight_(groups[1]);
return [styleMatch, weightMatch].join('');
};
FontApiParser.prototype.normalizeStyle_ = function(parsedStyle) {
if (parsedStyle == null || parsedStyle == '') {
return 'n';
}
return FontApiParser.STYLES[parsedStyle];
};
FontApiParser.prototype.normalizeWeight_ = function(parsedWeight) {
if (parsedWeight == null || parsedWeight == '') {
return '4';
}
var weight = FontApiParser.WEIGHTS[parsedWeight];
if (weight) {
return weight;
}
if (isNaN(parsedWeight)) {
return '4';
}
return parsedWeight.substr(0, 1);
};
FontApiParser.prototype.parseVariations_ = function(variations) {
var finalVariations = [];
if (!variations) {
return finalVariations;
}
var providedVariations = variations.split(",");
var length = providedVariations.length;
for (var i = 0; i < length; i++) {
var variation = providedVariations[i];
var fvd = this.generateFontVariationDescription_(variation);
if (fvd) {
finalVariations.push(fvd);
}
}
return finalVariations;
};
FontApiParser.prototype.parseSubsets_ = function(subsets) {
var finalSubsets = [];
if (!subsets) {
return finalSubsets;
}
return subsets.split(",");
};
FontApiParser.prototype.getFonts = function() {
return this.parsedFonts_;
};
FontApiParser.prototype.getFontTestStrings = function() {
return this.fontTestStrings_;
};
});
================================================
FILE: src/modules/google/fontapiurlbuilder.js
================================================
goog.provide('webfont.modules.google.FontApiUrlBuilder');
/**
* @constructor
*/
webfont.modules.google.FontApiUrlBuilder = function(apiUrl, text) {
if (apiUrl) {
this.apiUrl_ = apiUrl;
} else {
this.apiUrl_ = webfont.modules.google.FontApiUrlBuilder.DEFAULT_API_URL;
}
this.fontFamilies_ = [];
this.subsets_ = [];
this.text_ = text || '';
};
webfont.modules.google.FontApiUrlBuilder.DEFAULT_API_URL = 'https://fonts.googleapis.com/css';
goog.scope(function () {
var FontApiUrlBuilder = webfont.modules.google.FontApiUrlBuilder;
FontApiUrlBuilder.prototype.setFontFamilies = function(fontFamilies) {
this.parseFontFamilies_(fontFamilies);
};
FontApiUrlBuilder.prototype.parseFontFamilies_ =
function(fontFamilies) {
var length = fontFamilies.length;
for (var i = 0; i < length; i++) {
var elements = fontFamilies[i].split(':');
if (elements.length == 3) {
this.subsets_.push(elements.pop());
}
var joinCharacter = '';
if (elements.length == 2 && elements[1] != ''){
joinCharacter = ':';
}
this.fontFamilies_.push(elements.join(joinCharacter));
}
};
FontApiUrlBuilder.prototype.webSafe = function(string) {
return string.replace(/ /g, '+');
};
FontApiUrlBuilder.prototype.build = function() {
if (this.fontFamilies_.length == 0) {
throw new Error('No fonts to load!');
}
if (this.apiUrl_.indexOf("kit=") != -1) {
return this.apiUrl_;
}
var length = this.fontFamilies_.length;
var sb = [];
for (var i = 0; i < length; i++) {
sb.push(this.webSafe(this.fontFamilies_[i]));
}
var url = this.apiUrl_ + '?family=' + sb.join('%7C'); // '|' escaped.
if (this.subsets_.length > 0) {
url += '&subset=' + this.subsets_.join(',');
}
if (this.text_.length > 0) {
url += '&text=' + encodeURIComponent(this.text_);
}
return url;
};
});
================================================
FILE: src/modules/google/googlefontapi.js
================================================
goog.provide('webfont.modules.google.GoogleFontApi');
goog.require('webfont.modules.google.FontApiUrlBuilder');
goog.require('webfont.modules.google.FontApiParser');
goog.require('webfont.FontWatchRunner');
goog.require('webfont.StyleSheetWaiter');
/**
* @constructor
* @implements {webfont.FontModule}
*/
webfont.modules.google.GoogleFontApi = function(domHelper, configuration) {
this.domHelper_ = domHelper;
this.configuration_ = configuration;
};
/**
* @const
* @type {string}
*/
webfont.modules.google.GoogleFontApi.NAME = 'google';
goog.scope(function () {
var GoogleFontApi = webfont.modules.google.GoogleFontApi,
FontWatchRunner = webfont.FontWatchRunner,
StyleSheetWaiter = webfont.StyleSheetWaiter,
FontApiUrlBuilder = webfont.modules.google.FontApiUrlBuilder,
FontApiParser = webfont.modules.google.FontApiParser;
GoogleFontApi.METRICS_COMPATIBLE_FONTS = {
"Arimo": true,
"Cousine": true,
"Tinos": true
};
GoogleFontApi.prototype.load = function(onReady) {
var waiter = new StyleSheetWaiter();
var domHelper = this.domHelper_;
var fontApiUrlBuilder = new FontApiUrlBuilder(
this.configuration_['api'],
this.configuration_['text']
);
var fontFamilies = this.configuration_['families'];
fontApiUrlBuilder.setFontFamilies(fontFamilies);
var fontApiParser = new FontApiParser(fontFamilies);
fontApiParser.parse();
domHelper.loadStylesheet(fontApiUrlBuilder.build(), waiter.startWaitingLoad());
waiter.waitWhileNeededThen(function() {
onReady(fontApiParser.getFonts(), fontApiParser.getFontTestStrings(), GoogleFontApi.METRICS_COMPATIBLE_FONTS);
});
};
});
================================================
FILE: src/modules/monotype.js
================================================
goog.provide('webfont.modules.Monotype');
goog.require('webfont.Font');
/**
webfont.load({
monotype: {
projectId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'//this is your Fonts.com Web fonts projectId
}
});
*/
/**
* @constructor
* @implements {webfont.FontModule}
*/
webfont.modules.Monotype = function(domHelper, configuration) {
this.domHelper_ = domHelper;
this.configuration_ = configuration;
};
/**
* name of the module through which external API is supposed to call the MonotypeFontAPI.
*
* @const
* @type {string}
*/
webfont.modules.Monotype.NAME = 'monotype';
/**
* __mti_fntLst is the name of function that exposes Monotype's font list.
* @const
*/
webfont.modules.Monotype.HOOK = '__mti_fntLst';
/**
* __MonotypeAPIScript__ is the id of script added by google API. Currently 'fonts.com' supports only one script in a page.
* This may require change in future if 'fonts.com' begins supporting multiple scripts per page.
* @const
*/
webfont.modules.Monotype.SCRIPTID = '__MonotypeAPIScript__';
/**
* __MonotypeConfiguration__ is function exposed to fonts.com. fonts.com will use this function to get webfontloader configuration
* @const
*/
webfont.modules.Monotype.CONFIGURATION = '__MonotypeConfiguration__';
goog.scope(function() {
var Monotype = webfont.modules.Monotype,
Font = webfont.Font;
Monotype.prototype.getScriptSrc = function(projectId, version) {
var api = (this.configuration_['api'] || 'https://fast.fonts.net/jsapi')
return api + '/' + projectId + '.js' + (version ? '?v=' + version : '');
};
Monotype.prototype.load = function(onReady) {
var self = this;
var projectId = self.configuration_['projectId'];
var version = self.configuration_['version'];
function checkAndLoadIfDownloaded() {
if (loadWindow[Monotype.HOOK + projectId]) {
var mti_fnts = loadWindow[Monotype.HOOK + projectId](),
fonts = [],
fntVariation;
if (mti_fnts) {
for (var i = 0; i < mti_fnts.length; i++) {
var fnt = mti_fnts[i]["fontfamily"];
//Check if font-style and font-weight is available
if (mti_fnts[i]["fontStyle"] != undefined && mti_fnts[i]["fontWeight"] != undefined) {
fntVariation = mti_fnts[i]["fontStyle"] + mti_fnts[i]["fontWeight"];
fonts.push(new Font(fnt, fntVariation));
} else {
fonts.push(new Font(fnt));
}
}
}
onReady(fonts);
} else {
setTimeout(function() {
checkAndLoadIfDownloaded();
}, 50);
}
}
if (projectId) {
var loadWindow = self.domHelper_.getLoadWindow();
var script = this.domHelper_.loadScript(self.getScriptSrc(projectId, version), function(err) {
if (err) {
onReady([]);
} else {
loadWindow[Monotype.CONFIGURATION+ projectId] = function() {
return self.configuration_;
};
checkAndLoadIfDownloaded();
}
});
script["id"] = Monotype.SCRIPTID + projectId;
} else {
onReady([]);
}
};
});
================================================
FILE: src/modules/typekit.js
================================================
goog.provide('webfont.modules.Typekit');
goog.require('webfont.Font');
/**
* @constructor
* @implements {webfont.FontModule}
*/
webfont.modules.Typekit = function(domHelper, configuration) {
this.domHelper_ = domHelper;
this.configuration_ = configuration;
};
/**
* @const
* @type {string}
*/
webfont.modules.Typekit.NAME = 'typekit';
goog.scope(function () {
var Typekit = webfont.modules.Typekit,
Font = webfont.Font;
Typekit.prototype.getScriptSrc = function(kitId) {
var api = this.configuration_['api'] || 'https://use.typekit.net';
return api + '/' + kitId + '.js';
};
Typekit.prototype.load = function(onReady) {
var kitId = this.configuration_['id'];
var configuration = this.configuration_;
var loadWindow = this.domHelper_.getLoadWindow();
var that = this;
if (kitId) {
// Load the Typekit script. Once it is done loading we grab its configuration
// and use that to populate the fonts we should watch.
this.domHelper_.loadScript(this.getScriptSrc(kitId), function (err) {
if (err) {
onReady([]);
} else {
if (loadWindow['Typekit'] && loadWindow['Typekit']['config'] && loadWindow['Typekit']['config']['fn']) {
var fn = loadWindow['Typekit']['config']['fn'],
fonts = [];
for (var i = 0; i < fn.length; i += 2) {
var font = fn[i],
variations = fn[i + 1];
for (var j = 0; j < variations.length; j++) {
fonts.push(new Font(font, variations[j]));
}
}
// Kick off font loading but disable font events so
// we don't duplicate font watching.
try {
loadWindow['Typekit']['load']({
'events': false,
'classes': false,
'async': true
});
} catch (e) {}
onReady(fonts);
}
}
}, 2000);
} else {
onReady([]);
}
};
});
================================================
FILE: src/modules.yml
================================================
core:
- ../tools/compiler/base.js
- core/domhelper.js
- core/stylesheetwaiter.js
- core/cssclassname.js
- core/font.js
- core/eventdispatcher.js
- core/fontmodule.js
- core/fontmoduleloader.js
- core/fontruler.js
- core/nativefontwatchrunner.js
- core/fontwatchrunner.js
- core/fontwatcher.js
- core/webfont.js
- core/initialize.js
google:
- modules/google/fontapiurlbuilder.js
- modules/google/fontapiparser.js
- modules/google/googlefontapi.js
fontdeck:
- modules/fontdeck.js
typekit:
- modules/typekit.js
monotype:
- modules/monotype.js
custom:
- modules/custom.js
================================================
FILE: tools/compiler/base.js
================================================
// Copyright 2006 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @fileoverview Bootstrap for the Google JS Library (Closure).
*
* In uncompiled mode base.js will write out Closure's deps file, unless the
* global CLOSURE_NO_DEPS is set to true. This allows projects to
* include their own deps file(s) from different locations.
*
* @author arv@google.com (Erik Arvidsson)
*
* @provideGoog
*/
var CLOSURE_NO_DEPS = true;
/**
* @define {boolean} Overridden to true by the compiler when
* --process_closure_primitives is specified.
*/
var COMPILED = false;
/**
* Base namespace for the Closure library. Checks to see goog is already
* defined in the current scope before assigning to prevent clobbering if
* base.js is loaded more than once.
*
* @const
*/
var goog = goog || {};
/**
* Reference to the global context. In most cases this will be 'window'.
*/
goog.global = this;
/**
* A hook for overriding the define values in uncompiled mode.
*
* In uncompiled mode, {@code CLOSURE_UNCOMPILED_DEFINES} may be defined before
* loading base.js. If a key is defined in {@code CLOSURE_UNCOMPILED_DEFINES},
* {@code goog.define} will use the value instead of the default value. This
* allows flags to be overwritten without compilation (this is normally
* accomplished with the compiler's "define" flag).
*
* Example:
*
* var CLOSURE_UNCOMPILED_DEFINES = {'goog.DEBUG': false};
*
*
* @type {Object|undefined}
*/
goog.global.CLOSURE_UNCOMPILED_DEFINES;
/**
* A hook for overriding the define values in uncompiled or compiled mode,
* like CLOSURE_UNCOMPILED_DEFINES but effective in compiled code. In
* uncompiled code CLOSURE_UNCOMPILED_DEFINES takes precedence.
*
* Also unlike CLOSURE_UNCOMPILED_DEFINES the values must be number, boolean or
* string literals or the compiler will emit an error.
*
* While any @define value may be set, only those set with goog.define will be
* effective for uncompiled code.
*
* Example:
*
* var CLOSURE_DEFINES = {'goog.DEBUG': false} ;
*
*
* @type {Object|undefined}
*/
goog.global.CLOSURE_DEFINES;
/**
* Returns true if the specified value is not undefined.
* WARNING: Do not use this to test if an object has a property. Use the in
* operator instead.
*
* @param {?} val Variable to test.
* @return {boolean} Whether variable is defined.
*/
goog.isDef = function(val) {
// void 0 always evaluates to undefined and hence we do not need to depend on
// the definition of the global variable named 'undefined'.
return val !== void 0;
};
/**
* Builds an object structure for the provided namespace path, ensuring that
* names that already exist are not overwritten. For example:
* "a.b.c" -> a = {};a.b={};a.b.c={};
* Used by goog.provide and goog.exportSymbol.
* @param {string} name name of the object that this file defines.
* @param {*=} opt_object the object to expose at the end of the path.
* @param {Object=} opt_objectToExportTo The object to add the path to; default
* is |goog.global|.
* @private
*/
goog.exportPath_ = function(name, opt_object, opt_objectToExportTo) {
var parts = name.split('.');
var cur = opt_objectToExportTo || goog.global;
// Internet Explorer exhibits strange behavior when throwing errors from
// methods externed in this manner. See the testExportSymbolExceptions in
// base_test.html for an example.
if (!(parts[0] in cur) && cur.execScript) {
cur.execScript('var ' + parts[0]);
}
// Certain browsers cannot parse code in the form for((a in b); c;);
// This pattern is produced by the JSCompiler when it collapses the
// statement above into the conditional loop below. To prevent this from
// happening, use a for-loop and reserve the init logic as below.
// Parentheses added to eliminate strict JS warning in Firefox.
for (var part; parts.length && (part = parts.shift());) {
if (!parts.length && goog.isDef(opt_object)) {
// last part and we have an object; use it
cur[part] = opt_object;
} else if (cur[part]) {
cur = cur[part];
} else {
cur = cur[part] = {};
}
}
};
/**
* Defines a named value. In uncompiled mode, the value is retrieved from
* CLOSURE_DEFINES or CLOSURE_UNCOMPILED_DEFINES if the object is defined and
* has the property specified, and otherwise used the defined defaultValue.
* When compiled the default can be overridden using the compiler
* options or the value set in the CLOSURE_DEFINES object.
*
* @param {string} name The distinguished name to provide.
* @param {string|number|boolean} defaultValue
*/
goog.define = function(name, defaultValue) {
var value = defaultValue;
if (!COMPILED) {
if (goog.global.CLOSURE_UNCOMPILED_DEFINES &&
Object.prototype.hasOwnProperty.call(
goog.global.CLOSURE_UNCOMPILED_DEFINES, name)) {
value = goog.global.CLOSURE_UNCOMPILED_DEFINES[name];
} else if (goog.global.CLOSURE_DEFINES &&
Object.prototype.hasOwnProperty.call(
goog.global.CLOSURE_DEFINES, name)) {
value = goog.global.CLOSURE_DEFINES[name];
}
}
goog.exportPath_(name, value);
};
/**
* @define {boolean} DEBUG is provided as a convenience so that debugging code
* that should not be included in a production js_binary can be easily stripped
* by specifying --define goog.DEBUG=false to the JSCompiler. For example, most
* toString() methods should be declared inside an "if (goog.DEBUG)" conditional
* because they are generally used for debugging purposes and it is difficult
* for the JSCompiler to statically determine whether they are used.
*/
goog.define('goog.DEBUG', true);
/**
* @define {string} LOCALE defines the locale being used for compilation. It is
* used to select locale specific data to be compiled in js binary. BUILD rule
* can specify this value by "--define goog.LOCALE=" as JSCompiler
* option.
*
* Take into account that the locale code format is important. You should use
* the canonical Unicode format with hyphen as a delimiter. Language must be
* lowercase, Language Script - Capitalized, Region - UPPERCASE.
* There are few examples: pt-BR, en, en-US, sr-Latin-BO, zh-Hans-CN.
*
* See more info about locale codes here:
* http://www.unicode.org/reports/tr35/#Unicode_Language_and_Locale_Identifiers
*
* For language codes you should use values defined by ISO 693-1. See it here
* http://www.w3.org/WAI/ER/IG/ert/iso639.htm. There is only one exception from
* this rule: the Hebrew language. For legacy reasons the old code (iw) should
* be used instead of the new code (he), see http://wiki/Main/IIISynonyms.
*/
goog.define('goog.LOCALE', 'en'); // default to en
/**
* @define {boolean} Whether this code is running on trusted sites.
*
* On untrusted sites, several native functions can be defined or overridden by
* external libraries like Prototype, Datejs, and JQuery and setting this flag
* to false forces closure to use its own implementations when possible.
*
* If your JavaScript can be loaded by a third party site and you are wary about
* relying on non-standard implementations, specify
* "--define goog.TRUSTED_SITE=false" to the JSCompiler.
*/
goog.define('goog.TRUSTED_SITE', true);
/**
* @define {boolean} Whether a project is expected to be running in strict mode.
*
* This define can be used to trigger alternate implementations compatible with
* running in EcmaScript Strict mode or warn about unavailable functionality.
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/Strict_mode
*
*/
goog.define('goog.STRICT_MODE_COMPATIBLE', false);
/**
* @define {boolean} Whether code that calls {@link goog.setTestOnly} should
* be disallowed in the compilation unit.
*/
goog.define('goog.DISALLOW_TEST_ONLY_CODE', COMPILED && !goog.DEBUG);
/**
* @define {boolean} Whether to use a Chrome app CSP-compliant method for
* loading scripts via goog.require. @see appendScriptSrcNode_.
*/
goog.define('goog.ENABLE_CHROME_APP_SAFE_SCRIPT_LOADING', false);
/**
* Defines a namespace in Closure.
*
* A namespace may only be defined once in a codebase. It may be defined using
* goog.provide() or goog.module().
*
* The presence of one or more goog.provide() calls in a file indicates
* that the file defines the given objects/namespaces.
* Provided symbols must not be null or undefined.
*
* In addition, goog.provide() creates the object stubs for a namespace
* (for example, goog.provide("goog.foo.bar") will create the object
* goog.foo.bar if it does not already exist).
*
* Build tools also scan for provide/require/module statements
* to discern dependencies, build dependency files (see deps.js), etc.
*
* @see goog.require
* @see goog.module
* @param {string} name Namespace provided by this file in the form
* "goog.package.part".
*/
goog.provide = function(name) {
if (!COMPILED) {
// Ensure that the same namespace isn't provided twice.
// A goog.module/goog.provide maps a goog.require to a specific file
if (goog.isProvided_(name)) {
throw Error('Namespace "' + name + '" already declared.');
}
}
goog.constructNamespace_(name);
};
/**
* @param {string} name Namespace provided by this file in the form
* "goog.package.part".
* @param {Object=} opt_obj The object to embed in the namespace.
* @private
*/
goog.constructNamespace_ = function(name, opt_obj) {
if (!COMPILED) {
delete goog.implicitNamespaces_[name];
var namespace = name;
while ((namespace = namespace.substring(0, namespace.lastIndexOf('.')))) {
if (goog.getObjectByName(namespace)) {
break;
}
goog.implicitNamespaces_[namespace] = true;
}
}
goog.exportPath_(name, opt_obj);
};
/**
* Module identifier validation regexp.
* Note: This is a conservative check, it is very possible to be more lenient,
* the primary exclusion here is "/" and "\" and a leading ".", these
* restrictions are intended to leave the door open for using goog.require
* with relative file paths rather than module identifiers.
* @private
*/
goog.VALID_MODULE_RE_ = /^[a-zA-Z_$][a-zA-Z0-9._$]*$/;
/**
* Defines a module in Closure.
*
* Marks that this file must be loaded as a module and claims the namespace.
*
* A namespace may only be defined once in a codebase. It may be defined using
* goog.provide() or goog.module().
*
* goog.module() has three requirements:
* - goog.module may not be used in the same file as goog.provide.
* - goog.module must be the first statement in the file.
* - only one goog.module is allowed per file.
*
* When a goog.module annotated file is loaded, it is enclosed in
* a strict function closure. This means that:
* - any variables declared in a goog.module file are private to the file
* (not global), though the compiler is expected to inline the module.
* - The code must obey all the rules of "strict" JavaScript.
* - the file will be marked as "use strict"
*
* NOTE: unlike goog.provide, goog.module does not declare any symbols by
* itself. If declared symbols are desired, use
* goog.module.declareLegacyNamespace().
*
*
* See the public goog.module proposal: http://goo.gl/Va1hin
*
* @param {string} name Namespace provided by this file in the form
* "goog.package.part", is expected but not required.
*/
goog.module = function(name) {
if (!goog.isString(name) ||
!name ||
name.search(goog.VALID_MODULE_RE_) == -1) {
throw Error('Invalid module identifier');
}
if (!goog.isInModuleLoader_()) {
throw Error('Module ' + name + ' has been loaded incorrectly.');
}
if (goog.moduleLoaderState_.moduleName) {
throw Error('goog.module may only be called once per module.');
}
// Store the module name for the loader.
goog.moduleLoaderState_.moduleName = name;
if (!COMPILED) {
// Ensure that the same namespace isn't provided twice.
// A goog.module/goog.provide maps a goog.require to a specific file
if (goog.isProvided_(name)) {
throw Error('Namespace "' + name + '" already declared.');
}
delete goog.implicitNamespaces_[name];
}
};
/**
* @param {string} name The module identifier.
* @return {?} The module exports for an already loaded module or null.
*
* Note: This is not an alternative to goog.require, it does not
* indicate a hard dependency, instead it is used to indicate
* an optional dependency or to access the exports of a module
* that has already been loaded.
* @suppress {missingProvide}
*/
goog.module.get = function(name) {
return goog.module.getInternal_(name);
};
/**
* @param {string} name The module identifier.
* @return {?} The module exports for an already loaded module or null.
* @private
*/
goog.module.getInternal_ = function(name) {
if (!COMPILED) {
if (goog.isProvided_(name)) {
// goog.require only return a value with-in goog.module files.
return name in goog.loadedModules_ ?
goog.loadedModules_[name] :
goog.getObjectByName(name);
} else {
return null;
}
}
};
/**
* @private {?{moduleName: (string|undefined), declareLegacyNamespace:boolean}}
*/
goog.moduleLoaderState_ = null;
/**
* @private
* @return {boolean} Whether a goog.module is currently being initialized.
*/
goog.isInModuleLoader_ = function() {
return goog.moduleLoaderState_ != null;
};
/**
* Provide the module's exports as a globally accessible object under the
* module's declared name. This is intended to ease migration to goog.module
* for files that have existing usages.
* @suppress {missingProvide}
*/
goog.module.declareLegacyNamespace = function() {
if (!COMPILED && !goog.isInModuleLoader_()) {
throw new Error('goog.module.declareLegacyNamespace must be called from ' +
'within a goog.module');
}
if (!COMPILED && !goog.moduleLoaderState_.moduleName) {
throw Error('goog.module must be called prior to ' +
'goog.module.declareLegacyNamespace.');
}
goog.moduleLoaderState_.declareLegacyNamespace = true;
};
/**
* Marks that the current file should only be used for testing, and never for
* live code in production.
*
* In the case of unit tests, the message may optionally be an exact namespace
* for the test (e.g. 'goog.stringTest'). The linter will then ignore the extra
* provide (if not explicitly defined in the code).
*
* @param {string=} opt_message Optional message to add to the error that's
* raised when used in production code.
*/
goog.setTestOnly = function(opt_message) {
if (goog.DISALLOW_TEST_ONLY_CODE) {
opt_message = opt_message || '';
throw Error('Importing test-only code into non-debug environment' +
(opt_message ? ': ' + opt_message : '.'));
}
};
/**
* Forward declares a symbol. This is an indication to the compiler that the
* symbol may be used in the source yet is not required and may not be provided
* in compilation.
*
* The most common usage of forward declaration is code that takes a type as a
* function parameter but does not need to require it. By forward declaring
* instead of requiring, no hard dependency is made, and (if not required
* elsewhere) the namespace may never be required and thus, not be pulled
* into the JavaScript binary. If it is required elsewhere, it will be type
* checked as normal.
*
*
* @param {string} name The namespace to forward declare in the form of
* "goog.package.part".
*/
goog.forwardDeclare = function(name) {};
/**
* Forward declare type information. Used to assign types to goog.global
* referenced object that would otherwise result in unknown type references
* and thus block property disambiguation.
*/
goog.forwardDeclare('Document');
goog.forwardDeclare('HTMLScriptElement');
goog.forwardDeclare('XMLHttpRequest');
if (!COMPILED) {
/**
* Check if the given name has been goog.provided. This will return false for
* names that are available only as implicit namespaces.
* @param {string} name name of the object to look for.
* @return {boolean} Whether the name has been provided.
* @private
*/
goog.isProvided_ = function(name) {
return (name in goog.loadedModules_) ||
(!goog.implicitNamespaces_[name] &&
goog.isDefAndNotNull(goog.getObjectByName(name)));
};
/**
* Namespaces implicitly defined by goog.provide. For example,
* goog.provide('goog.events.Event') implicitly declares that 'goog' and
* 'goog.events' must be namespaces.
*
* @type {!Object}
* @private
*/
goog.implicitNamespaces_ = {'goog.module': true};
// NOTE: We add goog.module as an implicit namespace as goog.module is defined
// here and because the existing module package has not been moved yet out of
// the goog.module namespace. This satisifies both the debug loader and
// ahead-of-time dependency management.
}
/**
* Returns an object based on its fully qualified external name. The object
* is not found if null or undefined. If you are using a compilation pass that
* renames property names beware that using this function will not find renamed
* properties.
*
* @param {string} name The fully qualified name.
* @param {Object=} opt_obj The object within which to look; default is
* |goog.global|.
* @return {?} The value (object or primitive) or, if not found, null.
*/
goog.getObjectByName = function(name, opt_obj) {
var parts = name.split('.');
var cur = opt_obj || goog.global;
for (var part; part = parts.shift(); ) {
if (goog.isDefAndNotNull(cur[part])) {
cur = cur[part];
} else {
return null;
}
}
return cur;
};
/**
* Globalizes a whole namespace, such as goog or goog.lang.
*
* @param {!Object} obj The namespace to globalize.
* @param {Object=} opt_global The object to add the properties to.
* @deprecated Properties may be explicitly exported to the global scope, but
* this should no longer be done in bulk.
*/
goog.globalize = function(obj, opt_global) {
var global = opt_global || goog.global;
for (var x in obj) {
global[x] = obj[x];
}
};
/**
* Adds a dependency from a file to the files it requires.
* @param {string} relPath The path to the js file.
* @param {!Array} provides An array of strings with
* the names of the objects this file provides.
* @param {!Array} requires An array of strings with
* the names of the objects this file requires.
* @param {boolean=} opt_isModule Whether this dependency must be loaded as
* a module as declared by goog.module.
*/
goog.addDependency = function(relPath, provides, requires, opt_isModule) {
if (goog.DEPENDENCIES_ENABLED) {
var provide, require;
var path = relPath.replace(/\\/g, '/');
var deps = goog.dependencies_;
for (var i = 0; provide = provides[i]; i++) {
deps.nameToPath[provide] = path;
deps.pathIsModule[path] = !!opt_isModule;
}
for (var j = 0; require = requires[j]; j++) {
if (!(path in deps.requires)) {
deps.requires[path] = {};
}
deps.requires[path][require] = true;
}
}
};
// NOTE(nnaze): The debug DOM loader was included in base.js as an original way
// to do "debug-mode" development. The dependency system can sometimes be
// confusing, as can the debug DOM loader's asynchronous nature.
//
// With the DOM loader, a call to goog.require() is not blocking -- the script
// will not load until some point after the current script. If a namespace is
// needed at runtime, it needs to be defined in a previous script, or loaded via
// require() with its registered dependencies.
//
// User-defined namespaces may need their own deps file. For a reference on
// creating a deps file, see:
// Externally: https://developers.google.com/closure/library/docs/depswriter
//
// Because of legacy clients, the DOM loader can't be easily removed from
// base.js. Work is being done to make it disableable or replaceable for
// different environments (DOM-less JavaScript interpreters like Rhino or V8,
// for example). See bootstrap/ for more information.
/**
* @define {boolean} Whether to enable the debug loader.
*
* If enabled, a call to goog.require() will attempt to load the namespace by
* appending a script tag to the DOM (if the namespace has been registered).
*
* If disabled, goog.require() will simply assert that the namespace has been
* provided (and depend on the fact that some outside tool correctly ordered
* the script).
*/
goog.define('goog.ENABLE_DEBUG_LOADER', true);
/**
* @param {string} msg
* @private
*/
goog.logToConsole_ = function(msg) {
if (goog.global.console) {
goog.global.console['error'](msg);
}
};
/**
* Implements a system for the dynamic resolution of dependencies that works in
* parallel with the BUILD system. Note that all calls to goog.require will be
* stripped by the JSCompiler when the --process_closure_primitives option is
* used.
* @see goog.provide
* @param {string} name Namespace to include (as was given in goog.provide()) in
* the form "goog.package.part".
* @return {?} If called within a goog.module file, the associated namespace or
* module otherwise null.
*/
goog.require = function(name) {
// If the object already exists we do not need do do anything.
if (!COMPILED) {
if (goog.ENABLE_DEBUG_LOADER && goog.IS_OLD_IE_) {
goog.maybeProcessDeferredDep_(name);
}
if (goog.isProvided_(name)) {
if (goog.isInModuleLoader_()) {
return goog.module.getInternal_(name);
} else {
return null;
}
}
if (goog.ENABLE_DEBUG_LOADER) {
var path = goog.getPathFromDeps_(name);
if (path) {
goog.writeScripts_(path);
return null;
}
}
var errorMessage = 'goog.require could not find: ' + name;
goog.logToConsole_(errorMessage);
throw Error(errorMessage);
}
};
/**
* Path for included scripts.
* @type {string}
*/
goog.basePath = '';
/**
* A hook for overriding the base path.
* @type {string|undefined}
*/
goog.global.CLOSURE_BASE_PATH;
/**
* Whether to write out Closure's deps file. By default, the deps are written.
* @type {boolean|undefined}
*/
goog.global.CLOSURE_NO_DEPS;
/**
* A function to import a single script. This is meant to be overridden when
* Closure is being run in non-HTML contexts, such as web workers. It's defined
* in the global scope so that it can be set before base.js is loaded, which
* allows deps.js to be imported properly.
*
* The function is passed the script source, which is a relative URI. It should
* return true if the script was imported, false otherwise.
* @type {(function(string): boolean)|undefined}
*/
goog.global.CLOSURE_IMPORT_SCRIPT;
/**
* Null function used for default values of callbacks, etc.
* @return {void} Nothing.
*/
goog.nullFunction = function() {};
/**
* When defining a class Foo with an abstract method bar(), you can do:
* Foo.prototype.bar = goog.abstractMethod
*
* Now if a subclass of Foo fails to override bar(), an error will be thrown
* when bar() is invoked.
*
* Note: This does not take the name of the function to override as an argument
* because that would make it more difficult to obfuscate our JavaScript code.
*
* @type {!Function}
* @throws {Error} when invoked to indicate the method should be overridden.
*/
goog.abstractMethod = function() {
throw Error('unimplemented abstract method');
};
/**
* Adds a {@code getInstance} static method that always returns the same
* instance object.
* @param {!Function} ctor The constructor for the class to add the static
* method to.
*/
goog.addSingletonGetter = function(ctor) {
ctor.getInstance = function() {
if (ctor.instance_) {
return ctor.instance_;
}
if (goog.DEBUG) {
// NOTE: JSCompiler can't optimize away Array#push.
goog.instantiatedSingletons_[goog.instantiatedSingletons_.length] = ctor;
}
return ctor.instance_ = new ctor;
};
};
/**
* All singleton classes that have been instantiated, for testing. Don't read
* it directly, use the {@code goog.testing.singleton} module. The compiler
* removes this variable if unused.
* @type {!Array}
* @private
*/
goog.instantiatedSingletons_ = [];
/**
* @define {boolean} Whether to load goog.modules using {@code eval} when using
* the debug loader. This provides a better debugging experience as the
* source is unmodified and can be edited using Chrome Workspaces or similar.
* However in some environments the use of {@code eval} is banned
* so we provide an alternative.
*/
goog.define('goog.LOAD_MODULE_USING_EVAL', true);
/**
* @define {boolean} Whether the exports of goog.modules should be sealed when
* possible.
*/
goog.define('goog.SEAL_MODULE_EXPORTS', goog.DEBUG);
/**
* The registry of initialized modules:
* the module identifier to module exports map.
* @private @const {!Object}
*/
goog.loadedModules_ = {};
/**
* True if goog.dependencies_ is available.
* @const {boolean}
*/
goog.DEPENDENCIES_ENABLED = !COMPILED && goog.ENABLE_DEBUG_LOADER;
if (goog.DEPENDENCIES_ENABLED) {
/**
* This object is used to keep track of dependencies and other data that is
* used for loading scripts.
* @private
* @type {{
* pathIsModule: !Object,
* nameToPath: !Object,
* requires: !Object>,
* visited: !Object,
* written: !Object,
* deferred: !Object
* }}
*/
goog.dependencies_ = {
pathIsModule: {}, // 1 to 1
nameToPath: {}, // 1 to 1
requires: {}, // 1 to many
// Used when resolving dependencies to prevent us from visiting file twice.
visited: {},
written: {}, // Used to keep track of script files we have written.
deferred: {} // Used to track deferred module evaluations in old IEs
};
/**
* Tries to detect whether is in the context of an HTML document.
* @return {boolean} True if it looks like HTML document.
* @private
*/
goog.inHtmlDocument_ = function() {
/** @type {Document} */
var doc = goog.global.document;
return doc != null && 'write' in doc; // XULDocument misses write.
};
/**
* Tries to detect the base path of base.js script that bootstraps Closure.
* @private
*/
goog.findBasePath_ = function() {
if (goog.isDef(goog.global.CLOSURE_BASE_PATH)) {
goog.basePath = goog.global.CLOSURE_BASE_PATH;
return;
} else if (!goog.inHtmlDocument_()) {
return;
}
/** @type {Document} */
var doc = goog.global.document;
var scripts = doc.getElementsByTagName('SCRIPT');
// Search backwards since the current script is in almost all cases the one
// that has base.js.
for (var i = scripts.length - 1; i >= 0; --i) {
var script = /** @type {!HTMLScriptElement} */ (scripts[i]);
var src = script.src;
var qmark = src.lastIndexOf('?');
var l = qmark == -1 ? src.length : qmark;
if (src.substr(l - 7, 7) == 'base.js') {
goog.basePath = src.substr(0, l - 7);
return;
}
}
};
/**
* Imports a script if, and only if, that script hasn't already been imported.
* (Must be called at execution time)
* @param {string} src Script source.
* @param {string=} opt_sourceText The optionally source text to evaluate
* @private
*/
goog.importScript_ = function(src, opt_sourceText) {
var importScript = goog.global.CLOSURE_IMPORT_SCRIPT ||
goog.writeScriptTag_;
if (importScript(src, opt_sourceText)) {
goog.dependencies_.written[src] = true;
}
};
/** @const @private {boolean} */
goog.IS_OLD_IE_ = !!(!goog.global.atob && goog.global.document &&
goog.global.document.all);
/**
* Given a URL initiate retrieval and execution of the module.
* @param {string} src Script source URL.
* @private
*/
goog.importModule_ = function(src) {
// In an attempt to keep browsers from timing out loading scripts using
// synchronous XHRs, put each load in its own script block.
var bootstrap = 'goog.retrieveAndExecModule_("' + src + '");';
if (goog.importScript_('', bootstrap)) {
goog.dependencies_.written[src] = true;
}
};
/** @private {!Array} */
goog.queuedModules_ = [];
/**
* Return an appropriate module text. Suitable to insert into
* a script tag (that is unescaped).
* @param {string} srcUrl
* @param {string} scriptText
* @return {string}
* @private
*/
goog.wrapModule_ = function(srcUrl, scriptText) {
if (!goog.LOAD_MODULE_USING_EVAL || !goog.isDef(goog.global.JSON)) {
return '' +
'goog.loadModule(function(exports) {' +
'"use strict";' +
scriptText +
'\n' + // terminate any trailing single line comment.
';return exports' +
'});' +
'\n//# sourceURL=' + srcUrl + '\n';
} else {
return '' +
'goog.loadModule(' +
goog.global.JSON.stringify(
scriptText + '\n//# sourceURL=' + srcUrl + '\n') +
');';
}
};
// On IE9 and earlier, it is necessary to handle
// deferred module loads. In later browsers, the
// code to be evaluated is simply inserted as a script
// block in the correct order. To eval deferred
// code at the right time, we piggy back on goog.require to call
// goog.maybeProcessDeferredDep_.
//
// The goog.requires are used both to bootstrap
// the loading process (when no deps are available) and
// declare that they should be available.
//
// Here we eval the sources, if all the deps are available
// either already eval'd or goog.require'd. This will
// be the case when all the dependencies have already
// been loaded, and the dependent module is loaded.
//
// But this alone isn't sufficient because it is also
// necessary to handle the case where there is no root
// that is not deferred. For that there we register for an event
// and trigger goog.loadQueuedModules_ handle any remaining deferred
// evaluations.
/**
* Handle any remaining deferred goog.module evals.
* @private
*/
goog.loadQueuedModules_ = function() {
var count = goog.queuedModules_.length;
if (count > 0) {
var queue = goog.queuedModules_;
goog.queuedModules_ = [];
for (var i = 0; i < count; i++) {
var path = queue[i];
goog.maybeProcessDeferredPath_(path);
}
}
};
/**
* Eval the named module if its dependencies are
* available.
* @param {string} name The module to load.
* @private
*/
goog.maybeProcessDeferredDep_ = function(name) {
if (goog.isDeferredModule_(name) &&
goog.allDepsAreAvailable_(name)) {
var path = goog.getPathFromDeps_(name);
goog.maybeProcessDeferredPath_(goog.basePath + path);
}
};
/**
* @param {string} name The module to check.
* @return {boolean} Whether the name represents a
* module whose evaluation has been deferred.
* @private
*/
goog.isDeferredModule_ = function(name) {
var path = goog.getPathFromDeps_(name);
if (path && goog.dependencies_.pathIsModule[path]) {
var abspath = goog.basePath + path;
return (abspath) in goog.dependencies_.deferred;
}
return false;
};
/**
* @param {string} name The module to check.
* @return {boolean} Whether the name represents a
* module whose declared dependencies have all been loaded
* (eval'd or a deferred module load)
* @private
*/
goog.allDepsAreAvailable_ = function(name) {
var path = goog.getPathFromDeps_(name);
if (path && (path in goog.dependencies_.requires)) {
for (var requireName in goog.dependencies_.requires[path]) {
if (!goog.isProvided_(requireName) &&
!goog.isDeferredModule_(requireName)) {
return false;
}
}
}
return true;
};
/**
* @param {string} abspath
* @private
*/
goog.maybeProcessDeferredPath_ = function(abspath) {
if (abspath in goog.dependencies_.deferred) {
var src = goog.dependencies_.deferred[abspath];
delete goog.dependencies_.deferred[abspath];
goog.globalEval(src);
}
};
/**
* Load a goog.module from the provided URL. This is not a general purpose
* code loader and does not support late loading code, that is it should only
* be used during page load. This method exists to support unit tests and
* "debug" loaders that would otherwise have inserted script tags. Under the
* hood this needs to use a synchronous XHR and is not recommeneded for
* production code.
*
* The module's goog.requires must have already been satisified; an exception
* will be thrown if this is not the case. This assumption is that no
* "deps.js" file exists, so there is no way to discover and locate the
* module-to-be-loaded's dependencies and no attempt is made to do so.
*
* There should only be one attempt to load a module. If
* "goog.loadModuleFromUrl" is called for an already loaded module, an
* exception will be throw.
*
* @param {string} url The URL from which to attempt to load the goog.module.
*/
goog.loadModuleFromUrl = function(url) {
// Because this executes synchronously, we don't need to do any additional
// bookkeeping. When "goog.loadModule" the namespace will be marked as
// having been provided which is sufficient.
goog.retrieveAndExecModule_(url);
};
/**
* @param {function(?):?|string} moduleDef The module definition.
*/
goog.loadModule = function(moduleDef) {
// NOTE: we allow function definitions to be either in the from
// of a string to eval (which keeps the original source intact) or
// in a eval forbidden environment (CSP) we allow a function definition
// which in its body must call {@code goog.module}, and return the exports
// of the module.
var previousState = goog.moduleLoaderState_;
try {
goog.moduleLoaderState_ = {
moduleName: undefined,
declareLegacyNamespace: false
};
var exports;
if (goog.isFunction(moduleDef)) {
exports = moduleDef.call(goog.global, {});
} else if (goog.isString(moduleDef)) {
exports = goog.loadModuleFromSource_.call(goog.global, moduleDef);
} else {
throw Error('Invalid module definition');
}
var moduleName = goog.moduleLoaderState_.moduleName;
if (!goog.isString(moduleName) || !moduleName) {
throw Error('Invalid module name \"' + moduleName + '\"');
}
// Don't seal legacy namespaces as they may be uses as a parent of
// another namespace
if (goog.moduleLoaderState_.declareLegacyNamespace) {
goog.constructNamespace_(moduleName, exports);
} else if (goog.SEAL_MODULE_EXPORTS && Object.seal) {
Object.seal(exports);
}
goog.loadedModules_[moduleName] = exports;
} finally {
goog.moduleLoaderState_ = previousState;
}
};
/**
* @private @const {function(string):?}
*
* The new type inference warns because this function has no formal
* parameters, but its jsdoc says that it takes one argument.
* (The argument is used via arguments[0], but NTI does not detect this.)
* @suppress {newCheckTypes}
*/
goog.loadModuleFromSource_ = function() {
// NOTE: we avoid declaring parameters or local variables here to avoid
// masking globals or leaking values into the module definition.
'use strict';
var exports = {};
eval(arguments[0]);
return exports;
};
/**
* Writes a new script pointing to {@code src} directly into the DOM.
*
* NOTE: This method is not CSP-compliant. @see goog.appendScriptSrcNode_ for
* the fallback mechanism.
*
* @param {string} src The script URL.
* @private
*/
goog.writeScriptSrcNode_ = function(src) {
goog.global.document.write(
'