diff --git a/Makefile b/Makefile
index 2b566cbbf..caeca9b41 100644
--- a/Makefile
+++ b/Makefile
@@ -64,6 +64,7 @@ bundle: | $(BUILD_DIR)
@cd src; \
cat $(PDF_JS_FILES) > all_files.tmp; \
sed '/PDFJSSCRIPT_INCLUDE_ALL/ r all_files.tmp' pdf.js > ../$(BUILD_TARGET); \
+ sed -i '' "s/PDFJSSCRIPT_BUNDLE_VER/`git log --format="%H" -n 1`/" ../$(BUILD_TARGET); \
rm -f *.tmp; \
cd ..
diff --git a/README.md b/README.md
index deb925601..97db68d36 100644
--- a/README.md
+++ b/README.md
@@ -95,9 +95,17 @@ workings of PDF and pdf.js:
## Contributing
pdf.js is a community-driven project, so contributors are always welcome.
-Simply fork our repo and contribute away. A great place to start is our
-[open issues](https://github.com/mozilla/pdf.js/issues). For better consistency and
-long-term stability, please do look around the code and try to follow our conventions.
+Simply fork our repo and contribute away. Good starting places for picking
+a bug are the top error messages and TODOs in our corpus report:
+
++ http://people.mozilla.com/~bdahl/corpusreport/test/ref/
+
+and of course our open Github issues:
+
++ https://github.com/mozilla/pdf.js/issues
+
+For better consistency and long-term stability, please do look around the
+code and try to follow our conventions.
More information about the contributor process can be found on the
[contributor wiki page](https://github.com/mozilla/pdf.js/wiki/Contributing).
@@ -152,9 +160,9 @@ See the bot repo for details:
## Additional resources
-Our demo site is here:
+Gallery of user projects and modifications:
-+ http://mozilla.github.com/pdf.js/web/viewer.html
++ https://github.com/mozilla/pdf.js/wiki/Gallery-of-user-projects-and-modifications
You can read more about pdf.js here:
diff --git a/extensions/firefox/install.rdf b/extensions/firefox/install.rdf
index 26b2192b6..952d55fbf 100644
--- a/extensions/firefox/install.rdf
+++ b/extensions/firefox/install.rdf
@@ -12,7 +12,7 @@
{ec8030f7-c20a-464f-9b0e-13a3a9e97384}
6.0
- 10.0.*
+ 11.0.*
true
diff --git a/external/jasmine/MIT.LICENSE b/external/jasmine/MIT.LICENSE
new file mode 100644
index 000000000..7c435baae
--- /dev/null
+++ b/external/jasmine/MIT.LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2008-2011 Pivotal Labs
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/external/jasmine/jasmine-html.js b/external/jasmine/jasmine-html.js
new file mode 100644
index 000000000..3de4e8a5f
--- /dev/null
+++ b/external/jasmine/jasmine-html.js
@@ -0,0 +1,676 @@
+jasmine.HtmlReporterHelpers = {};
+
+jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
+ var el = document.createElement(type);
+
+ for (var i = 2; i < arguments.length; i++) {
+ var child = arguments[i];
+
+ if (typeof child === 'string') {
+ el.appendChild(document.createTextNode(child));
+ } else {
+ if (child) {
+ el.appendChild(child);
+ }
+ }
+ }
+
+ for (var attr in attrs) {
+ if (attr == "className") {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ }
+
+ return el;
+};
+
+jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
+ var results = child.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.skipped) {
+ status = 'skipped';
+ }
+
+ return status;
+};
+
+jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
+ var parentDiv = this.dom.summary;
+ var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
+ var parent = child[parentSuite];
+
+ if (parent) {
+ if (typeof this.views.suites[parent.id] == 'undefined') {
+ this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
+ }
+ parentDiv = this.views.suites[parent.id].element;
+ }
+
+ parentDiv.appendChild(childElement);
+};
+
+
+jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
+ for(var fn in jasmine.HtmlReporterHelpers) {
+ ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
+ }
+};
+
+jasmine.HtmlReporter = function(_doc) {
+ var self = this;
+ var doc = _doc || window.document;
+
+ var reporterView;
+
+ var dom = {};
+
+ // Jasmine Reporter Public Interface
+ self.logRunningSpecs = false;
+
+ self.reportRunnerStarting = function(runner) {
+ var specs = runner.specs() || [];
+
+ if (specs.length == 0) {
+ return;
+ }
+
+ createReporterDom(runner.env.versionString());
+ doc.body.appendChild(dom.reporter);
+
+ reporterView = new jasmine.HtmlReporter.ReporterView(dom);
+ reporterView.addSpecs(specs, self.specFilter);
+ };
+
+ self.reportRunnerResults = function(runner) {
+ reporterView.complete();
+ };
+
+ self.reportSuiteResults = function(suite) {
+ reporterView.suiteComplete(suite);
+ };
+
+ self.reportSpecStarting = function(spec) {
+ if (self.logRunningSpecs) {
+ self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
+ }
+ };
+
+ self.reportSpecResults = function(spec) {
+ reporterView.specComplete(spec);
+ };
+
+ self.log = function() {
+ var console = jasmine.getGlobal().console;
+ if (console && console.log) {
+ if (console.log.apply) {
+ console.log.apply(console, arguments);
+ } else {
+ console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
+ }
+ }
+ };
+
+ self.specFilter = function(spec) {
+ if (!focusedSpecName()) {
+ return true;
+ }
+
+ return spec.getFullName().indexOf(focusedSpecName()) === 0;
+ };
+
+ return self;
+
+ function focusedSpecName() {
+ var specName;
+
+ (function memoizeFocusedSpec() {
+ if (specName) {
+ return;
+ }
+
+ var paramMap = [];
+ var params = doc.location.search.substring(1).split('&');
+
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+ }
+
+ specName = paramMap.spec;
+ })();
+
+ return specName;
+ }
+
+ function createReporterDom(version) {
+ dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' },
+ dom.banner = self.createDom('div', { className: 'banner' },
+ self.createDom('span', { className: 'title' }, "Jasmine "),
+ self.createDom('span', { className: 'version' }, version)),
+
+ dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}),
+ dom.alert = self.createDom('div', {className: 'alert'}),
+ dom.results = self.createDom('div', {className: 'results'},
+ dom.summary = self.createDom('div', { className: 'summary' }),
+ dom.details = self.createDom('div', { id: 'details' }))
+ );
+ }
+};
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter);jasmine.HtmlReporterHelpers = {};
+
+jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) {
+ var el = document.createElement(type);
+
+ for (var i = 2; i < arguments.length; i++) {
+ var child = arguments[i];
+
+ if (typeof child === 'string') {
+ el.appendChild(document.createTextNode(child));
+ } else {
+ if (child) {
+ el.appendChild(child);
+ }
+ }
+ }
+
+ for (var attr in attrs) {
+ if (attr == "className") {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ }
+
+ return el;
+};
+
+jasmine.HtmlReporterHelpers.getSpecStatus = function(child) {
+ var results = child.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.skipped) {
+ status = 'skipped';
+ }
+
+ return status;
+};
+
+jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) {
+ var parentDiv = this.dom.summary;
+ var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite';
+ var parent = child[parentSuite];
+
+ if (parent) {
+ if (typeof this.views.suites[parent.id] == 'undefined') {
+ this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views);
+ }
+ parentDiv = this.views.suites[parent.id].element;
+ }
+
+ parentDiv.appendChild(childElement);
+};
+
+
+jasmine.HtmlReporterHelpers.addHelpers = function(ctor) {
+ for(var fn in jasmine.HtmlReporterHelpers) {
+ ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn];
+ }
+};
+
+jasmine.HtmlReporter.ReporterView = function(dom) {
+ this.startedAt = new Date();
+ this.runningSpecCount = 0;
+ this.completeSpecCount = 0;
+ this.passedCount = 0;
+ this.failedCount = 0;
+ this.skippedCount = 0;
+
+ this.createResultsMenu = function() {
+ this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'},
+ this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'),
+ ' | ',
+ this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing'));
+
+ this.summaryMenuItem.onclick = function() {
+ dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, '');
+ };
+
+ this.detailsMenuItem.onclick = function() {
+ showDetails();
+ };
+ };
+
+ this.addSpecs = function(specs, specFilter) {
+ this.totalSpecCount = specs.length;
+
+ this.views = {
+ specs: {},
+ suites: {}
+ };
+
+ for (var i = 0; i < specs.length; i++) {
+ var spec = specs[i];
+ this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views);
+ if (specFilter(spec)) {
+ this.runningSpecCount++;
+ }
+ }
+ };
+
+ this.specComplete = function(spec) {
+ this.completeSpecCount++;
+
+ if (isUndefined(this.views.specs[spec.id])) {
+ this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom);
+ }
+
+ var specView = this.views.specs[spec.id];
+
+ switch (specView.status()) {
+ case 'passed':
+ this.passedCount++;
+ break;
+
+ case 'failed':
+ this.failedCount++;
+ break;
+
+ case 'skipped':
+ this.skippedCount++;
+ break;
+ }
+
+ specView.refresh();
+ this.refresh();
+ };
+
+ this.suiteComplete = function(suite) {
+ var suiteView = this.views.suites[suite.id];
+ if (isUndefined(suiteView)) {
+ return;
+ }
+ suiteView.refresh();
+ };
+
+ this.refresh = function() {
+
+ if (isUndefined(this.resultsMenu)) {
+ this.createResultsMenu();
+ }
+
+ // currently running UI
+ if (isUndefined(this.runningAlert)) {
+ this.runningAlert = this.createDom('a', {href: "?", className: "runningAlert bar"});
+ dom.alert.appendChild(this.runningAlert);
+ }
+ this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount);
+
+ // skipped specs UI
+ if (isUndefined(this.skippedAlert)) {
+ this.skippedAlert = this.createDom('a', {href: "?", className: "skippedAlert bar"});
+ }
+
+ this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
+
+ if (this.skippedCount === 1 && isDefined(dom.alert)) {
+ dom.alert.appendChild(this.skippedAlert);
+ }
+
+ // passing specs UI
+ if (isUndefined(this.passedAlert)) {
+ this.passedAlert = this.createDom('span', {href: "?", className: "passingAlert bar"});
+ }
+ this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount);
+
+ // failing specs UI
+ if (isUndefined(this.failedAlert)) {
+ this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"});
+ }
+ this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount);
+
+ if (this.failedCount === 1 && isDefined(dom.alert)) {
+ dom.alert.appendChild(this.failedAlert);
+ dom.alert.appendChild(this.resultsMenu);
+ }
+
+ // summary info
+ this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount);
+ this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing";
+ };
+
+ this.complete = function() {
+ dom.alert.removeChild(this.runningAlert);
+
+ this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all";
+
+ if (this.failedCount === 0) {
+ dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount)));
+ } else {
+ showDetails();
+ }
+
+ dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"));
+ };
+
+ return this;
+
+ function showDetails() {
+ if (dom.reporter.className.search(/showDetails/) === -1) {
+ dom.reporter.className += " showDetails";
+ }
+ }
+
+ function isUndefined(obj) {
+ return typeof obj === 'undefined';
+ }
+
+ function isDefined(obj) {
+ return !isUndefined(obj);
+ }
+
+ function specPluralizedFor(count) {
+ var str = count + " spec";
+ if (count > 1) {
+ str += "s"
+ }
+ return str;
+ }
+
+};
+
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView);
+
+
+jasmine.HtmlReporter.SpecView = function(spec, dom, views) {
+ this.spec = spec;
+ this.dom = dom;
+ this.views = views;
+
+ this.symbol = this.createDom('li', { className: 'pending' });
+ this.dom.symbolSummary.appendChild(this.symbol);
+
+ this.summary = this.createDom('div', { className: 'specSummary' },
+ this.createDom('a', {
+ className: 'description',
+ href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
+ title: this.spec.getFullName()
+ }, this.spec.description)
+ );
+
+ this.detail = this.createDom('div', { className: 'specDetail' },
+ this.createDom('a', {
+ className: 'description',
+ href: '?spec=' + encodeURIComponent(this.spec.getFullName()),
+ title: this.spec.getFullName()
+ }, this.spec.getFullName())
+ );
+};
+
+jasmine.HtmlReporter.SpecView.prototype.status = function() {
+ return this.getSpecStatus(this.spec);
+};
+
+jasmine.HtmlReporter.SpecView.prototype.refresh = function() {
+ this.symbol.className = this.status();
+
+ switch (this.status()) {
+ case 'skipped':
+ break;
+
+ case 'passed':
+ this.appendSummaryToSuiteDiv();
+ break;
+
+ case 'failed':
+ this.appendSummaryToSuiteDiv();
+ this.appendFailureDetail();
+ break;
+ }
+};
+
+jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() {
+ this.summary.className += ' ' + this.status();
+ this.appendToSummary(this.spec, this.summary);
+};
+
+jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() {
+ this.detail.className += ' ' + this.status();
+
+ var resultItems = this.spec.results().getItems();
+ var messagesDiv = this.createDom('div', { className: 'messages' });
+
+ for (var i = 0; i < resultItems.length; i++) {
+ var result = resultItems[i];
+
+ if (result.type == 'log') {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
+ } else if (result.type == 'expect' && result.passed && !result.passed()) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
+
+ if (result.trace.stack) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
+ }
+ }
+ }
+
+ if (messagesDiv.childNodes.length > 0) {
+ this.detail.appendChild(messagesDiv);
+ this.dom.details.appendChild(this.detail);
+ }
+};
+
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) {
+ this.suite = suite;
+ this.dom = dom;
+ this.views = views;
+
+ this.element = this.createDom('div', { className: 'suite' },
+ this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(this.suite.getFullName()) }, this.suite.description)
+ );
+
+ this.appendToSummary(this.suite, this.element);
+};
+
+jasmine.HtmlReporter.SuiteView.prototype.status = function() {
+ return this.getSpecStatus(this.suite);
+};
+
+jasmine.HtmlReporter.SuiteView.prototype.refresh = function() {
+ this.element.className += " " + this.status();
+};
+
+jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView);
+
+/* @deprecated Use jasmine.HtmlReporter instead
+ */
+jasmine.TrivialReporter = function(doc) {
+ this.document = doc || document;
+ this.suiteDivs = {};
+ this.logRunningSpecs = false;
+};
+
+jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) {
+ var el = document.createElement(type);
+
+ for (var i = 2; i < arguments.length; i++) {
+ var child = arguments[i];
+
+ if (typeof child === 'string') {
+ el.appendChild(document.createTextNode(child));
+ } else {
+ if (child) { el.appendChild(child); }
+ }
+ }
+
+ for (var attr in attrs) {
+ if (attr == "className") {
+ el[attr] = attrs[attr];
+ } else {
+ el.setAttribute(attr, attrs[attr]);
+ }
+ }
+
+ return el;
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) {
+ var showPassed, showSkipped;
+
+ this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' },
+ this.createDom('div', { className: 'banner' },
+ this.createDom('div', { className: 'logo' },
+ this.createDom('span', { className: 'title' }, "Jasmine"),
+ this.createDom('span', { className: 'version' }, runner.env.versionString())),
+ this.createDom('div', { className: 'options' },
+ "Show ",
+ showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }),
+ this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "),
+ showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }),
+ this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped")
+ )
+ ),
+
+ this.runnerDiv = this.createDom('div', { className: 'runner running' },
+ this.createDom('a', { className: 'run_spec', href: '?' }, "run all"),
+ this.runnerMessageSpan = this.createDom('span', {}, "Running..."),
+ this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, ""))
+ );
+
+ this.document.body.appendChild(this.outerDiv);
+
+ var suites = runner.suites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ var suiteDiv = this.createDom('div', { className: 'suite' },
+ this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"),
+ this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description));
+ this.suiteDivs[suite.id] = suiteDiv;
+ var parentDiv = this.outerDiv;
+ if (suite.parentSuite) {
+ parentDiv = this.suiteDivs[suite.parentSuite.id];
+ }
+ parentDiv.appendChild(suiteDiv);
+ }
+
+ this.startedAt = new Date();
+
+ var self = this;
+ showPassed.onclick = function(evt) {
+ if (showPassed.checked) {
+ self.outerDiv.className += ' show-passed';
+ } else {
+ self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, '');
+ }
+ };
+
+ showSkipped.onclick = function(evt) {
+ if (showSkipped.checked) {
+ self.outerDiv.className += ' show-skipped';
+ } else {
+ self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, '');
+ }
+ };
+};
+
+jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) {
+ var results = runner.results();
+ var className = (results.failedCount > 0) ? "runner failed" : "runner passed";
+ this.runnerDiv.setAttribute("class", className);
+ //do it twice for IE
+ this.runnerDiv.setAttribute("className", className);
+ var specs = runner.specs();
+ var specCount = 0;
+ for (var i = 0; i < specs.length; i++) {
+ if (this.specFilter(specs[i])) {
+ specCount++;
+ }
+ }
+ var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s");
+ message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s";
+ this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild);
+
+ this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString()));
+};
+
+jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) {
+ var results = suite.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.totalCount === 0) { // todo: change this to check results.skipped
+ status = 'skipped';
+ }
+ this.suiteDivs[suite.id].className += " " + status;
+};
+
+jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) {
+ if (this.logRunningSpecs) {
+ this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...');
+ }
+};
+
+jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) {
+ var results = spec.results();
+ var status = results.passed() ? 'passed' : 'failed';
+ if (results.skipped) {
+ status = 'skipped';
+ }
+ var specDiv = this.createDom('div', { className: 'spec ' + status },
+ this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"),
+ this.createDom('a', {
+ className: 'description',
+ href: '?spec=' + encodeURIComponent(spec.getFullName()),
+ title: spec.getFullName()
+ }, spec.description));
+
+
+ var resultItems = results.getItems();
+ var messagesDiv = this.createDom('div', { className: 'messages' });
+ for (var i = 0; i < resultItems.length; i++) {
+ var result = resultItems[i];
+
+ if (result.type == 'log') {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString()));
+ } else if (result.type == 'expect' && result.passed && !result.passed()) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message));
+
+ if (result.trace.stack) {
+ messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack));
+ }
+ }
+ }
+
+ if (messagesDiv.childNodes.length > 0) {
+ specDiv.appendChild(messagesDiv);
+ }
+
+ this.suiteDivs[spec.suite.id].appendChild(specDiv);
+};
+
+jasmine.TrivialReporter.prototype.log = function() {
+ var console = jasmine.getGlobal().console;
+ if (console && console.log) {
+ if (console.log.apply) {
+ console.log.apply(console, arguments);
+ } else {
+ console.log(arguments); // ie fix: console.log.apply doesn't exist on ie
+ }
+ }
+};
+
+jasmine.TrivialReporter.prototype.getLocation = function() {
+ return this.document.location;
+};
+
+jasmine.TrivialReporter.prototype.specFilter = function(spec) {
+ var paramMap = {};
+ var params = this.getLocation().search.substring(1).split('&');
+ for (var i = 0; i < params.length; i++) {
+ var p = params[i].split('=');
+ paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]);
+ }
+
+ if (!paramMap.spec) {
+ return true;
+ }
+ return spec.getFullName().indexOf(paramMap.spec) === 0;
+};
diff --git a/external/jasmine/jasmine.css b/external/jasmine/jasmine.css
new file mode 100644
index 000000000..826e57531
--- /dev/null
+++ b/external/jasmine/jasmine.css
@@ -0,0 +1,81 @@
+body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; }
+
+#HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
+#HTMLReporter a { text-decoration: none; }
+#HTMLReporter a:hover { text-decoration: underline; }
+#HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; }
+#HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; }
+#HTMLReporter #jasmine_content { position: fixed; right: 100%; }
+#HTMLReporter .version { color: #aaaaaa; }
+#HTMLReporter .banner { margin-top: 14px; }
+#HTMLReporter .duration { color: #aaaaaa; float: right; }
+#HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; }
+#HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; }
+#HTMLReporter .symbolSummary li.passed { font-size: 14px; }
+#HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; }
+#HTMLReporter .symbolSummary li.failed { line-height: 9px; }
+#HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; }
+#HTMLReporter .symbolSummary li.skipped { font-size: 14px; }
+#HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; }
+#HTMLReporter .symbolSummary li.pending { line-height: 11px; }
+#HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; }
+#HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
+#HTMLReporter .runningAlert { background-color: #666666; }
+#HTMLReporter .skippedAlert { background-color: #aaaaaa; }
+#HTMLReporter .skippedAlert:first-child { background-color: #333333; }
+#HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; }
+#HTMLReporter .passingAlert { background-color: #a6b779; }
+#HTMLReporter .passingAlert:first-child { background-color: #5e7d00; }
+#HTMLReporter .failingAlert { background-color: #cf867e; }
+#HTMLReporter .failingAlert:first-child { background-color: #b03911; }
+#HTMLReporter .results { margin-top: 14px; }
+#HTMLReporter #details { display: none; }
+#HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; }
+#HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
+#HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
+#HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
+#HTMLReporter.showDetails .summary { display: none; }
+#HTMLReporter.showDetails #details { display: block; }
+#HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
+#HTMLReporter .summary { margin-top: 14px; }
+#HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; }
+#HTMLReporter .summary .specSummary.passed a { color: #5e7d00; }
+#HTMLReporter .summary .specSummary.failed a { color: #b03911; }
+#HTMLReporter .description + .suite { margin-top: 0; }
+#HTMLReporter .suite { margin-top: 14px; }
+#HTMLReporter .suite a { color: #333333; }
+#HTMLReporter #details .specDetail { margin-bottom: 28px; }
+#HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; }
+#HTMLReporter .resultMessage { padding-top: 14px; color: #333333; }
+#HTMLReporter .resultMessage span.result { display: block; }
+#HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
+
+#TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ }
+#TrivialReporter a:visited, #TrivialReporter a { color: #303; }
+#TrivialReporter a:hover, #TrivialReporter a:active { color: blue; }
+#TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; }
+#TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; }
+#TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; }
+#TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; }
+#TrivialReporter .runner.running { background-color: yellow; }
+#TrivialReporter .options { text-align: right; font-size: .8em; }
+#TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; }
+#TrivialReporter .suite .suite { margin: 5px; }
+#TrivialReporter .suite.passed { background-color: #dfd; }
+#TrivialReporter .suite.failed { background-color: #fdd; }
+#TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; }
+#TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; }
+#TrivialReporter .spec.failed { background-color: #fbb; border-color: red; }
+#TrivialReporter .spec.passed { background-color: #bfb; border-color: green; }
+#TrivialReporter .spec.skipped { background-color: #bbb; }
+#TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; }
+#TrivialReporter .passed { background-color: #cfc; display: none; }
+#TrivialReporter .failed { background-color: #fbb; }
+#TrivialReporter .skipped { color: #777; background-color: #eee; display: none; }
+#TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; }
+#TrivialReporter .resultMessage .mismatch { color: black; }
+#TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; }
+#TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; }
+#TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; }
+#TrivialReporter #jasmine_content { position: fixed; right: 100%; }
+#TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; }
diff --git a/external/jasmine/jasmine.js b/external/jasmine/jasmine.js
new file mode 100644
index 000000000..8bba9262d
--- /dev/null
+++ b/external/jasmine/jasmine.js
@@ -0,0 +1,2476 @@
+var isCommonJS = typeof window == "undefined";
+
+/**
+ * Top level namespace for Jasmine, a lightweight JavaScript BDD/spec/testing framework.
+ *
+ * @namespace
+ */
+var jasmine = {};
+if (isCommonJS) exports.jasmine = jasmine;
+/**
+ * @private
+ */
+jasmine.unimplementedMethod_ = function() {
+ throw new Error("unimplemented method");
+};
+
+/**
+ * Use jasmine.undefined
instead of undefined
, since undefined
is just
+ * a plain old variable and may be redefined by somebody else.
+ *
+ * @private
+ */
+jasmine.undefined = jasmine.___undefined___;
+
+/**
+ * Show diagnostic messages in the console if set to true
+ *
+ */
+jasmine.VERBOSE = false;
+
+/**
+ * Default interval in milliseconds for event loop yields (e.g. to allow network activity or to refresh the screen with the HTML-based runner). Small values here may result in slow test running. Zero means no updates until all tests have completed.
+ *
+ */
+jasmine.DEFAULT_UPDATE_INTERVAL = 250;
+
+/**
+ * Default timeout interval in milliseconds for waitsFor() blocks.
+ */
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 5000;
+
+jasmine.getGlobal = function() {
+ function getGlobal() {
+ return this;
+ }
+
+ return getGlobal();
+};
+
+/**
+ * Allows for bound functions to be compared. Internal use only.
+ *
+ * @ignore
+ * @private
+ * @param base {Object} bound 'this' for the function
+ * @param name {Function} function to find
+ */
+jasmine.bindOriginal_ = function(base, name) {
+ var original = base[name];
+ if (original.apply) {
+ return function() {
+ return original.apply(base, arguments);
+ };
+ } else {
+ // IE support
+ return jasmine.getGlobal()[name];
+ }
+};
+
+jasmine.setTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'setTimeout');
+jasmine.clearTimeout = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearTimeout');
+jasmine.setInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'setInterval');
+jasmine.clearInterval = jasmine.bindOriginal_(jasmine.getGlobal(), 'clearInterval');
+
+jasmine.MessageResult = function(values) {
+ this.type = 'log';
+ this.values = values;
+ this.trace = new Error(); // todo: test better
+};
+
+jasmine.MessageResult.prototype.toString = function() {
+ var text = "";
+ for (var i = 0; i < this.values.length; i++) {
+ if (i > 0) text += " ";
+ if (jasmine.isString_(this.values[i])) {
+ text += this.values[i];
+ } else {
+ text += jasmine.pp(this.values[i]);
+ }
+ }
+ return text;
+};
+
+jasmine.ExpectationResult = function(params) {
+ this.type = 'expect';
+ this.matcherName = params.matcherName;
+ this.passed_ = params.passed;
+ this.expected = params.expected;
+ this.actual = params.actual;
+ this.message = this.passed_ ? 'Passed.' : params.message;
+
+ var trace = (params.trace || new Error(this.message));
+ this.trace = this.passed_ ? '' : trace;
+};
+
+jasmine.ExpectationResult.prototype.toString = function () {
+ return this.message;
+};
+
+jasmine.ExpectationResult.prototype.passed = function () {
+ return this.passed_;
+};
+
+/**
+ * Getter for the Jasmine environment. Ensures one gets created
+ */
+jasmine.getEnv = function() {
+ var env = jasmine.currentEnv_ = jasmine.currentEnv_ || new jasmine.Env();
+ return env;
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isArray_ = function(value) {
+ return jasmine.isA_("Array", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isString_ = function(value) {
+ return jasmine.isA_("String", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isNumber_ = function(value) {
+ return jasmine.isA_("Number", value);
+};
+
+/**
+ * @ignore
+ * @private
+ * @param {String} typeName
+ * @param value
+ * @returns {Boolean}
+ */
+jasmine.isA_ = function(typeName, value) {
+ return Object.prototype.toString.apply(value) === '[object ' + typeName + ']';
+};
+
+/**
+ * Pretty printer for expecations. Takes any object and turns it into a human-readable string.
+ *
+ * @param value {Object} an object to be outputted
+ * @returns {String}
+ */
+jasmine.pp = function(value) {
+ var stringPrettyPrinter = new jasmine.StringPrettyPrinter();
+ stringPrettyPrinter.format(value);
+ return stringPrettyPrinter.string;
+};
+
+/**
+ * Returns true if the object is a DOM Node.
+ *
+ * @param {Object} obj object to check
+ * @returns {Boolean}
+ */
+jasmine.isDomNode = function(obj) {
+ return obj.nodeType > 0;
+};
+
+/**
+ * Returns a matchable 'generic' object of the class type. For use in expecations of type when values don't matter.
+ *
+ * @example
+ * // don't care about which function is passed in, as long as it's a function
+ * expect(mySpy).toHaveBeenCalledWith(jasmine.any(Function));
+ *
+ * @param {Class} clazz
+ * @returns matchable object of the type clazz
+ */
+jasmine.any = function(clazz) {
+ return new jasmine.Matchers.Any(clazz);
+};
+
+/**
+ * Jasmine Spies are test doubles that can act as stubs, spies, fakes or when used in an expecation, mocks.
+ *
+ * Spies should be created in test setup, before expectations. They can then be checked, using the standard Jasmine
+ * expectation syntax. Spies can be checked if they were called or not and what the calling params were.
+ *
+ * A Spy has the following fields: wasCalled, callCount, mostRecentCall, and argsForCall (see docs).
+ *
+ * Spies are torn down at the end of every spec.
+ *
+ * Note: Do not call new jasmine.Spy() directly - a spy must be created using spyOn, jasmine.createSpy or jasmine.createSpyObj.
+ *
+ * @example
+ * // a stub
+ * var myStub = jasmine.createSpy('myStub'); // can be used anywhere
+ *
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // actual foo.not will not be called, execution stops
+ * spyOn(foo, 'not');
+
+ // foo.not spied upon, execution will continue to implementation
+ * spyOn(foo, 'not').andCallThrough();
+ *
+ * // fake example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ *
+ * // foo.not(val) will return val
+ * spyOn(foo, 'not').andCallFake(function(value) {return value;});
+ *
+ * // mock example
+ * foo.not(7 == 7);
+ * expect(foo.not).toHaveBeenCalled();
+ * expect(foo.not).toHaveBeenCalledWith(true);
+ *
+ * @constructor
+ * @see spyOn, jasmine.createSpy, jasmine.createSpyObj
+ * @param {String} name
+ */
+jasmine.Spy = function(name) {
+ /**
+ * The name of the spy, if provided.
+ */
+ this.identity = name || 'unknown';
+ /**
+ * Is this Object a spy?
+ */
+ this.isSpy = true;
+ /**
+ * The actual function this spy stubs.
+ */
+ this.plan = function() {
+ };
+ /**
+ * Tracking of the most recent call to the spy.
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy.mostRecentCall.args = [1, 2];
+ */
+ this.mostRecentCall = {};
+
+ /**
+ * Holds arguments for each call to the spy, indexed by call count
+ * @example
+ * var mySpy = jasmine.createSpy('foo');
+ * mySpy(1, 2);
+ * mySpy(7, 8);
+ * mySpy.mostRecentCall.args = [7, 8];
+ * mySpy.argsForCall[0] = [1, 2];
+ * mySpy.argsForCall[1] = [7, 8];
+ */
+ this.argsForCall = [];
+ this.calls = [];
+};
+
+/**
+ * Tells a spy to call through to the actual implemenatation.
+ *
+ * @example
+ * var foo = {
+ * bar: function() { // do some stuff }
+ * }
+ *
+ * // defining a spy on an existing property: foo.bar
+ * spyOn(foo, 'bar').andCallThrough();
+ */
+jasmine.Spy.prototype.andCallThrough = function() {
+ this.plan = this.originalValue;
+ return this;
+};
+
+/**
+ * For setting the return value of a spy.
+ *
+ * @example
+ * // defining a spy from scratch: foo() returns 'baz'
+ * var foo = jasmine.createSpy('spy on foo').andReturn('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() returns 'baz'
+ * spyOn(foo, 'bar').andReturn('baz');
+ *
+ * @param {Object} value
+ */
+jasmine.Spy.prototype.andReturn = function(value) {
+ this.plan = function() {
+ return value;
+ };
+ return this;
+};
+
+/**
+ * For throwing an exception when a spy is called.
+ *
+ * @example
+ * // defining a spy from scratch: foo() throws an exception w/ message 'ouch'
+ * var foo = jasmine.createSpy('spy on foo').andThrow('baz');
+ *
+ * // defining a spy on an existing property: foo.bar() throws an exception w/ message 'ouch'
+ * spyOn(foo, 'bar').andThrow('baz');
+ *
+ * @param {String} exceptionMsg
+ */
+jasmine.Spy.prototype.andThrow = function(exceptionMsg) {
+ this.plan = function() {
+ throw exceptionMsg;
+ };
+ return this;
+};
+
+/**
+ * Calls an alternate implementation when a spy is called.
+ *
+ * @example
+ * var baz = function() {
+ * // do some stuff, return something
+ * }
+ * // defining a spy from scratch: foo() calls the function baz
+ * var foo = jasmine.createSpy('spy on foo').andCall(baz);
+ *
+ * // defining a spy on an existing property: foo.bar() calls an anonymnous function
+ * spyOn(foo, 'bar').andCall(function() { return 'baz';} );
+ *
+ * @param {Function} fakeFunc
+ */
+jasmine.Spy.prototype.andCallFake = function(fakeFunc) {
+ this.plan = fakeFunc;
+ return this;
+};
+
+/**
+ * Resets all of a spy's the tracking variables so that it can be used again.
+ *
+ * @example
+ * spyOn(foo, 'bar');
+ *
+ * foo.bar();
+ *
+ * expect(foo.bar.callCount).toEqual(1);
+ *
+ * foo.bar.reset();
+ *
+ * expect(foo.bar.callCount).toEqual(0);
+ */
+jasmine.Spy.prototype.reset = function() {
+ this.wasCalled = false;
+ this.callCount = 0;
+ this.argsForCall = [];
+ this.calls = [];
+ this.mostRecentCall = {};
+};
+
+jasmine.createSpy = function(name) {
+
+ var spyObj = function() {
+ spyObj.wasCalled = true;
+ spyObj.callCount++;
+ var args = jasmine.util.argsToArray(arguments);
+ spyObj.mostRecentCall.object = this;
+ spyObj.mostRecentCall.args = args;
+ spyObj.argsForCall.push(args);
+ spyObj.calls.push({object: this, args: args});
+ return spyObj.plan.apply(this, arguments);
+ };
+
+ var spy = new jasmine.Spy(name);
+
+ for (var prop in spy) {
+ spyObj[prop] = spy[prop];
+ }
+
+ spyObj.reset();
+
+ return spyObj;
+};
+
+/**
+ * Determines whether an object is a spy.
+ *
+ * @param {jasmine.Spy|Object} putativeSpy
+ * @returns {Boolean}
+ */
+jasmine.isSpy = function(putativeSpy) {
+ return putativeSpy && putativeSpy.isSpy;
+};
+
+/**
+ * Creates a more complicated spy: an Object that has every property a function that is a spy. Used for stubbing something
+ * large in one call.
+ *
+ * @param {String} baseName name of spy class
+ * @param {Array} methodNames array of names of methods to make spies
+ */
+jasmine.createSpyObj = function(baseName, methodNames) {
+ if (!jasmine.isArray_(methodNames) || methodNames.length === 0) {
+ throw new Error('createSpyObj requires a non-empty array of method names to create spies for');
+ }
+ var obj = {};
+ for (var i = 0; i < methodNames.length; i++) {
+ obj[methodNames[i]] = jasmine.createSpy(baseName + '.' + methodNames[i]);
+ }
+ return obj;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the current spec's output.
+ *
+ * Be careful not to leave calls to jasmine.log
in production code.
+ */
+jasmine.log = function() {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.log.apply(spec, arguments);
+};
+
+/**
+ * Function that installs a spy on an existing object's method name. Used within a Spec to create a spy.
+ *
+ * @example
+ * // spy example
+ * var foo = {
+ * not: function(bool) { return !bool; }
+ * }
+ * spyOn(foo, 'not'); // actual foo.not will not be called, execution stops
+ *
+ * @see jasmine.createSpy
+ * @param obj
+ * @param methodName
+ * @returns a Jasmine spy that can be chained with all spy methods
+ */
+var spyOn = function(obj, methodName) {
+ return jasmine.getEnv().currentSpec.spyOn(obj, methodName);
+};
+if (isCommonJS) exports.spyOn = spyOn;
+
+/**
+ * Creates a Jasmine spec that will be added to the current suite.
+ *
+ * // TODO: pending tests
+ *
+ * @example
+ * it('should be true', function() {
+ * expect(true).toEqual(true);
+ * });
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var it = function(desc, func) {
+ return jasmine.getEnv().it(desc, func);
+};
+if (isCommonJS) exports.it = it;
+
+/**
+ * Creates a disabled Jasmine spec.
+ *
+ * A convenience method that allows existing specs to be disabled temporarily during development.
+ *
+ * @param {String} desc description of this specification
+ * @param {Function} func defines the preconditions and expectations of the spec
+ */
+var xit = function(desc, func) {
+ return jasmine.getEnv().xit(desc, func);
+};
+if (isCommonJS) exports.xit = xit;
+
+/**
+ * Starts a chain for a Jasmine expectation.
+ *
+ * It is passed an Object that is the actual value and should chain to one of the many
+ * jasmine.Matchers functions.
+ *
+ * @param {Object} actual Actual value to test against and expected value
+ */
+var expect = function(actual) {
+ return jasmine.getEnv().currentSpec.expect(actual);
+};
+if (isCommonJS) exports.expect = expect;
+
+/**
+ * Defines part of a jasmine spec. Used in cominbination with waits or waitsFor in asynchrnous specs.
+ *
+ * @param {Function} func Function that defines part of a jasmine spec.
+ */
+var runs = function(func) {
+ jasmine.getEnv().currentSpec.runs(func);
+};
+if (isCommonJS) exports.runs = runs;
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+var waits = function(timeout) {
+ jasmine.getEnv().currentSpec.waits(timeout);
+};
+if (isCommonJS) exports.waits = waits;
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+var waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+ jasmine.getEnv().currentSpec.waitsFor.apply(jasmine.getEnv().currentSpec, arguments);
+};
+if (isCommonJS) exports.waitsFor = waitsFor;
+
+/**
+ * A function that is called before each spec in a suite.
+ *
+ * Used for spec setup, including validating assumptions.
+ *
+ * @param {Function} beforeEachFunction
+ */
+var beforeEach = function(beforeEachFunction) {
+ jasmine.getEnv().beforeEach(beforeEachFunction);
+};
+if (isCommonJS) exports.beforeEach = beforeEach;
+
+/**
+ * A function that is called after each spec in a suite.
+ *
+ * Used for restoring any state that is hijacked during spec execution.
+ *
+ * @param {Function} afterEachFunction
+ */
+var afterEach = function(afterEachFunction) {
+ jasmine.getEnv().afterEach(afterEachFunction);
+};
+if (isCommonJS) exports.afterEach = afterEach;
+
+/**
+ * Defines a suite of specifications.
+ *
+ * Stores the description and all defined specs in the Jasmine environment as one suite of specs. Variables declared
+ * are accessible by calls to beforeEach, it, and afterEach. Describe blocks can be nested, allowing for specialization
+ * of setup in some tests.
+ *
+ * @example
+ * // TODO: a simple suite
+ *
+ * // TODO: a simple suite with a nested describe block
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var describe = function(description, specDefinitions) {
+ return jasmine.getEnv().describe(description, specDefinitions);
+};
+if (isCommonJS) exports.describe = describe;
+
+/**
+ * Disables a suite of specifications. Used to disable some suites in a file, or files, temporarily during development.
+ *
+ * @param {String} description A string, usually the class under test.
+ * @param {Function} specDefinitions function that defines several specs.
+ */
+var xdescribe = function(description, specDefinitions) {
+ return jasmine.getEnv().xdescribe(description, specDefinitions);
+};
+if (isCommonJS) exports.xdescribe = xdescribe;
+
+
+// Provide the XMLHttpRequest class for IE 5.x-6.x:
+jasmine.XmlHttpRequest = (typeof XMLHttpRequest == "undefined") ? function() {
+ function tryIt(f) {
+ try {
+ return f();
+ } catch(e) {
+ }
+ return null;
+ }
+
+ var xhr = tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP.6.0");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP.3.0");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Msxml2.XMLHTTP");
+ }) ||
+ tryIt(function() {
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ });
+
+ if (!xhr) throw new Error("This browser does not support XMLHttpRequest.");
+
+ return xhr;
+} : XMLHttpRequest;
+/**
+ * @namespace
+ */
+jasmine.util = {};
+
+/**
+ * Declare that a child class inherit it's prototype from the parent class.
+ *
+ * @private
+ * @param {Function} childClass
+ * @param {Function} parentClass
+ */
+jasmine.util.inherit = function(childClass, parentClass) {
+ /**
+ * @private
+ */
+ var subclass = function() {
+ };
+ subclass.prototype = parentClass.prototype;
+ childClass.prototype = new subclass();
+};
+
+jasmine.util.formatException = function(e) {
+ var lineNumber;
+ if (e.line) {
+ lineNumber = e.line;
+ }
+ else if (e.lineNumber) {
+ lineNumber = e.lineNumber;
+ }
+
+ var file;
+
+ if (e.sourceURL) {
+ file = e.sourceURL;
+ }
+ else if (e.fileName) {
+ file = e.fileName;
+ }
+
+ var message = (e.name && e.message) ? (e.name + ': ' + e.message) : e.toString();
+
+ if (file && lineNumber) {
+ message += ' in ' + file + ' (line ' + lineNumber + ')';
+ }
+
+ return message;
+};
+
+jasmine.util.htmlEscape = function(str) {
+ if (!str) return str;
+ return str.replace(/&/g, '&')
+ .replace(//g, '>');
+};
+
+jasmine.util.argsToArray = function(args) {
+ var arrayOfArgs = [];
+ for (var i = 0; i < args.length; i++) arrayOfArgs.push(args[i]);
+ return arrayOfArgs;
+};
+
+jasmine.util.extend = function(destination, source) {
+ for (var property in source) destination[property] = source[property];
+ return destination;
+};
+
+/**
+ * Environment for Jasmine
+ *
+ * @constructor
+ */
+jasmine.Env = function() {
+ this.currentSpec = null;
+ this.currentSuite = null;
+ this.currentRunner_ = new jasmine.Runner(this);
+
+ this.reporter = new jasmine.MultiReporter();
+
+ this.updateInterval = jasmine.DEFAULT_UPDATE_INTERVAL;
+ this.defaultTimeoutInterval = jasmine.DEFAULT_TIMEOUT_INTERVAL;
+ this.lastUpdate = 0;
+ this.specFilter = function() {
+ return true;
+ };
+
+ this.nextSpecId_ = 0;
+ this.nextSuiteId_ = 0;
+ this.equalityTesters_ = [];
+
+ // wrap matchers
+ this.matchersClass = function() {
+ jasmine.Matchers.apply(this, arguments);
+ };
+ jasmine.util.inherit(this.matchersClass, jasmine.Matchers);
+
+ jasmine.Matchers.wrapInto_(jasmine.Matchers.prototype, this.matchersClass);
+};
+
+
+jasmine.Env.prototype.setTimeout = jasmine.setTimeout;
+jasmine.Env.prototype.clearTimeout = jasmine.clearTimeout;
+jasmine.Env.prototype.setInterval = jasmine.setInterval;
+jasmine.Env.prototype.clearInterval = jasmine.clearInterval;
+
+/**
+ * @returns an object containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.version = function () {
+ if (jasmine.version_) {
+ return jasmine.version_;
+ } else {
+ throw new Error('Version not set');
+ }
+};
+
+/**
+ * @returns string containing jasmine version build info, if set.
+ */
+jasmine.Env.prototype.versionString = function() {
+ if (!jasmine.version_) {
+ return "version unknown";
+ }
+
+ var version = this.version();
+ var versionString = version.major + "." + version.minor + "." + version.build;
+ if (version.release_candidate) {
+ versionString += ".rc" + version.release_candidate;
+ }
+ versionString += " revision " + version.revision;
+ return versionString;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSpecId = function () {
+ return this.nextSpecId_++;
+};
+
+/**
+ * @returns a sequential integer starting at 0
+ */
+jasmine.Env.prototype.nextSuiteId = function () {
+ return this.nextSuiteId_++;
+};
+
+/**
+ * Register a reporter to receive status updates from Jasmine.
+ * @param {jasmine.Reporter} reporter An object which will receive status updates.
+ */
+jasmine.Env.prototype.addReporter = function(reporter) {
+ this.reporter.addReporter(reporter);
+};
+
+jasmine.Env.prototype.execute = function() {
+ this.currentRunner_.execute();
+};
+
+jasmine.Env.prototype.describe = function(description, specDefinitions) {
+ var suite = new jasmine.Suite(this, description, specDefinitions, this.currentSuite);
+
+ var parentSuite = this.currentSuite;
+ if (parentSuite) {
+ parentSuite.add(suite);
+ } else {
+ this.currentRunner_.add(suite);
+ }
+
+ this.currentSuite = suite;
+
+ var declarationError = null;
+ try {
+ specDefinitions.call(suite);
+ } catch(e) {
+ declarationError = e;
+ }
+
+ if (declarationError) {
+ this.it("encountered a declaration exception", function() {
+ throw declarationError;
+ });
+ }
+
+ this.currentSuite = parentSuite;
+
+ return suite;
+};
+
+jasmine.Env.prototype.beforeEach = function(beforeEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.beforeEach(beforeEachFunction);
+ } else {
+ this.currentRunner_.beforeEach(beforeEachFunction);
+ }
+};
+
+jasmine.Env.prototype.currentRunner = function () {
+ return this.currentRunner_;
+};
+
+jasmine.Env.prototype.afterEach = function(afterEachFunction) {
+ if (this.currentSuite) {
+ this.currentSuite.afterEach(afterEachFunction);
+ } else {
+ this.currentRunner_.afterEach(afterEachFunction);
+ }
+
+};
+
+jasmine.Env.prototype.xdescribe = function(desc, specDefinitions) {
+ return {
+ execute: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.it = function(description, func) {
+ var spec = new jasmine.Spec(this, this.currentSuite, description);
+ this.currentSuite.add(spec);
+ this.currentSpec = spec;
+
+ if (func) {
+ spec.runs(func);
+ }
+
+ return spec;
+};
+
+jasmine.Env.prototype.xit = function(desc, func) {
+ return {
+ id: this.nextSpecId(),
+ runs: function() {
+ }
+ };
+};
+
+jasmine.Env.prototype.compareObjects_ = function(a, b, mismatchKeys, mismatchValues) {
+ if (a.__Jasmine_been_here_before__ === b && b.__Jasmine_been_here_before__ === a) {
+ return true;
+ }
+
+ a.__Jasmine_been_here_before__ = b;
+ b.__Jasmine_been_here_before__ = a;
+
+ var hasKey = function(obj, keyName) {
+ return obj !== null && obj[keyName] !== jasmine.undefined;
+ };
+
+ for (var property in b) {
+ if (!hasKey(a, property) && hasKey(b, property)) {
+ mismatchKeys.push("expected has key '" + property + "', but missing from actual.");
+ }
+ }
+ for (property in a) {
+ if (!hasKey(b, property) && hasKey(a, property)) {
+ mismatchKeys.push("expected missing key '" + property + "', but present in actual.");
+ }
+ }
+ for (property in b) {
+ if (property == '__Jasmine_been_here_before__') continue;
+ if (!this.equals_(a[property], b[property], mismatchKeys, mismatchValues)) {
+ mismatchValues.push("'" + property + "' was '" + (b[property] ? jasmine.util.htmlEscape(b[property].toString()) : b[property]) + "' in expected, but was '" + (a[property] ? jasmine.util.htmlEscape(a[property].toString()) : a[property]) + "' in actual.");
+ }
+ }
+
+ if (jasmine.isArray_(a) && jasmine.isArray_(b) && a.length != b.length) {
+ mismatchValues.push("arrays were not the same length");
+ }
+
+ delete a.__Jasmine_been_here_before__;
+ delete b.__Jasmine_been_here_before__;
+ return (mismatchKeys.length === 0 && mismatchValues.length === 0);
+};
+
+jasmine.Env.prototype.equals_ = function(a, b, mismatchKeys, mismatchValues) {
+ mismatchKeys = mismatchKeys || [];
+ mismatchValues = mismatchValues || [];
+
+ for (var i = 0; i < this.equalityTesters_.length; i++) {
+ var equalityTester = this.equalityTesters_[i];
+ var result = equalityTester(a, b, this, mismatchKeys, mismatchValues);
+ if (result !== jasmine.undefined) return result;
+ }
+
+ if (a === b) return true;
+
+ if (a === jasmine.undefined || a === null || b === jasmine.undefined || b === null) {
+ return (a == jasmine.undefined && b == jasmine.undefined);
+ }
+
+ if (jasmine.isDomNode(a) && jasmine.isDomNode(b)) {
+ return a === b;
+ }
+
+ if (a instanceof Date && b instanceof Date) {
+ return a.getTime() == b.getTime();
+ }
+
+ if (a instanceof jasmine.Matchers.Any) {
+ return a.matches(b);
+ }
+
+ if (b instanceof jasmine.Matchers.Any) {
+ return b.matches(a);
+ }
+
+ if (jasmine.isString_(a) && jasmine.isString_(b)) {
+ return (a == b);
+ }
+
+ if (jasmine.isNumber_(a) && jasmine.isNumber_(b)) {
+ return (a == b);
+ }
+
+ if (typeof a === "object" && typeof b === "object") {
+ return this.compareObjects_(a, b, mismatchKeys, mismatchValues);
+ }
+
+ //Straight check
+ return (a === b);
+};
+
+jasmine.Env.prototype.contains_ = function(haystack, needle) {
+ if (jasmine.isArray_(haystack)) {
+ for (var i = 0; i < haystack.length; i++) {
+ if (this.equals_(haystack[i], needle)) return true;
+ }
+ return false;
+ }
+ return haystack.indexOf(needle) >= 0;
+};
+
+jasmine.Env.prototype.addEqualityTester = function(equalityTester) {
+ this.equalityTesters_.push(equalityTester);
+};
+/** No-op base class for Jasmine reporters.
+ *
+ * @constructor
+ */
+jasmine.Reporter = function() {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerStarting = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportRunnerResults = function(runner) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecStarting = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.reportSpecResults = function(spec) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.Reporter.prototype.log = function(str) {
+};
+
+/**
+ * Blocks are functions with executable code that make up a spec.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {Function} func
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Block = function(env, func, spec) {
+ this.env = env;
+ this.func = func;
+ this.spec = spec;
+};
+
+jasmine.Block.prototype.execute = function(onComplete) {
+ try {
+ this.func.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ }
+ onComplete();
+};
+/** JavaScript API reporter.
+ *
+ * @constructor
+ */
+jasmine.JsApiReporter = function() {
+ this.started = false;
+ this.finished = false;
+ this.suites_ = [];
+ this.results_ = {};
+};
+
+jasmine.JsApiReporter.prototype.reportRunnerStarting = function(runner) {
+ this.started = true;
+ var suites = runner.topLevelSuites();
+ for (var i = 0; i < suites.length; i++) {
+ var suite = suites[i];
+ this.suites_.push(this.summarize_(suite));
+ }
+};
+
+jasmine.JsApiReporter.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.JsApiReporter.prototype.summarize_ = function(suiteOrSpec) {
+ var isSuite = suiteOrSpec instanceof jasmine.Suite;
+ var summary = {
+ id: suiteOrSpec.id,
+ name: suiteOrSpec.description,
+ type: isSuite ? 'suite' : 'spec',
+ children: []
+ };
+
+ if (isSuite) {
+ var children = suiteOrSpec.children();
+ for (var i = 0; i < children.length; i++) {
+ summary.children.push(this.summarize_(children[i]));
+ }
+ }
+ return summary;
+};
+
+jasmine.JsApiReporter.prototype.results = function() {
+ return this.results_;
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpec = function(specId) {
+ return this.results_[specId];
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportRunnerResults = function(runner) {
+ this.finished = true;
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSuiteResults = function(suite) {
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.reportSpecResults = function(spec) {
+ this.results_[spec.id] = {
+ messages: spec.results().getItems(),
+ result: spec.results().failedCount > 0 ? "failed" : "passed"
+ };
+};
+
+//noinspection JSUnusedLocalSymbols
+jasmine.JsApiReporter.prototype.log = function(str) {
+};
+
+jasmine.JsApiReporter.prototype.resultsForSpecs = function(specIds){
+ var results = {};
+ for (var i = 0; i < specIds.length; i++) {
+ var specId = specIds[i];
+ results[specId] = this.summarizeResult_(this.results_[specId]);
+ }
+ return results;
+};
+
+jasmine.JsApiReporter.prototype.summarizeResult_ = function(result){
+ var summaryMessages = [];
+ var messagesLength = result.messages.length;
+ for (var messageIndex = 0; messageIndex < messagesLength; messageIndex++) {
+ var resultMessage = result.messages[messageIndex];
+ summaryMessages.push({
+ text: resultMessage.type == 'log' ? resultMessage.toString() : jasmine.undefined,
+ passed: resultMessage.passed ? resultMessage.passed() : true,
+ type: resultMessage.type,
+ message: resultMessage.message,
+ trace: {
+ stack: resultMessage.passed && !resultMessage.passed() ? resultMessage.trace.stack : jasmine.undefined
+ }
+ });
+ }
+
+ return {
+ result : result.result,
+ messages : summaryMessages
+ };
+};
+
+/**
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param actual
+ * @param {jasmine.Spec} spec
+ */
+jasmine.Matchers = function(env, actual, spec, opt_isNot) {
+ this.env = env;
+ this.actual = actual;
+ this.spec = spec;
+ this.isNot = opt_isNot || false;
+ this.reportWasCalled_ = false;
+};
+
+// todo: @deprecated as of Jasmine 0.11, remove soon [xw]
+jasmine.Matchers.pp = function(str) {
+ throw new Error("jasmine.Matchers.pp() is no longer supported, please use jasmine.pp() instead!");
+};
+
+// todo: @deprecated Deprecated as of Jasmine 0.10. Rewrite your custom matchers to return true or false. [xw]
+jasmine.Matchers.prototype.report = function(result, failing_message, details) {
+ throw new Error("As of jasmine 0.11, custom matchers must be implemented differently -- please see jasmine docs");
+};
+
+jasmine.Matchers.wrapInto_ = function(prototype, matchersClass) {
+ for (var methodName in prototype) {
+ if (methodName == 'report') continue;
+ var orig = prototype[methodName];
+ matchersClass.prototype[methodName] = jasmine.Matchers.matcherFn_(methodName, orig);
+ }
+};
+
+jasmine.Matchers.matcherFn_ = function(matcherName, matcherFunction) {
+ return function() {
+ var matcherArgs = jasmine.util.argsToArray(arguments);
+ var result = matcherFunction.apply(this, arguments);
+
+ if (this.isNot) {
+ result = !result;
+ }
+
+ if (this.reportWasCalled_) return result;
+
+ var message;
+ if (!result) {
+ if (this.message) {
+ message = this.message.apply(this, arguments);
+ if (jasmine.isArray_(message)) {
+ message = message[this.isNot ? 1 : 0];
+ }
+ } else {
+ var englishyPredicate = matcherName.replace(/[A-Z]/g, function(s) { return ' ' + s.toLowerCase(); });
+ message = "Expected " + jasmine.pp(this.actual) + (this.isNot ? " not " : " ") + englishyPredicate;
+ if (matcherArgs.length > 0) {
+ for (var i = 0; i < matcherArgs.length; i++) {
+ if (i > 0) message += ",";
+ message += " " + jasmine.pp(matcherArgs[i]);
+ }
+ }
+ message += ".";
+ }
+ }
+ var expectationResult = new jasmine.ExpectationResult({
+ matcherName: matcherName,
+ passed: result,
+ expected: matcherArgs.length > 1 ? matcherArgs : matcherArgs[0],
+ actual: this.actual,
+ message: message
+ });
+ this.spec.addMatcherResult(expectationResult);
+ return jasmine.undefined;
+ };
+};
+
+
+
+
+/**
+ * toBe: compares the actual to the expected using ===
+ * @param expected
+ */
+jasmine.Matchers.prototype.toBe = function(expected) {
+ return this.actual === expected;
+};
+
+/**
+ * toNotBe: compares the actual to the expected using !==
+ * @param expected
+ * @deprecated as of 1.0. Use not.toBe() instead.
+ */
+jasmine.Matchers.prototype.toNotBe = function(expected) {
+ return this.actual !== expected;
+};
+
+/**
+ * toEqual: compares the actual to the expected using common sense equality. Handles Objects, Arrays, etc.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toEqual = function(expected) {
+ return this.env.equals_(this.actual, expected);
+};
+
+/**
+ * toNotEqual: compares the actual to the expected using the ! of jasmine.Matchers.toEqual
+ * @param expected
+ * @deprecated as of 1.0. Use not.toNotEqual() instead.
+ */
+jasmine.Matchers.prototype.toNotEqual = function(expected) {
+ return !this.env.equals_(this.actual, expected);
+};
+
+/**
+ * Matcher that compares the actual to the expected using a regular expression. Constructs a RegExp, so takes
+ * a pattern or a String.
+ *
+ * @param expected
+ */
+jasmine.Matchers.prototype.toMatch = function(expected) {
+ return new RegExp(expected).test(this.actual);
+};
+
+/**
+ * Matcher that compares the actual to the expected using the boolean inverse of jasmine.Matchers.toMatch
+ * @param expected
+ * @deprecated as of 1.0. Use not.toMatch() instead.
+ */
+jasmine.Matchers.prototype.toNotMatch = function(expected) {
+ return !(new RegExp(expected).test(this.actual));
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeDefined = function() {
+ return (this.actual !== jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to jasmine.undefined.
+ */
+jasmine.Matchers.prototype.toBeUndefined = function() {
+ return (this.actual === jasmine.undefined);
+};
+
+/**
+ * Matcher that compares the actual to null.
+ */
+jasmine.Matchers.prototype.toBeNull = function() {
+ return (this.actual === null);
+};
+
+/**
+ * Matcher that boolean not-nots the actual.
+ */
+jasmine.Matchers.prototype.toBeTruthy = function() {
+ return !!this.actual;
+};
+
+
+/**
+ * Matcher that boolean nots the actual.
+ */
+jasmine.Matchers.prototype.toBeFalsy = function() {
+ return !this.actual;
+};
+
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called.
+ */
+jasmine.Matchers.prototype.toHaveBeenCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('toHaveBeenCalled does not take arguments, use toHaveBeenCalledWith');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy " + this.actual.identity + " to have been called.",
+ "Expected spy " + this.actual.identity + " not to have been called."
+ ];
+ };
+
+ return this.actual.wasCalled;
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalled() instead */
+jasmine.Matchers.prototype.wasCalled = jasmine.Matchers.prototype.toHaveBeenCalled;
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was not called.
+ *
+ * @deprecated Use expect(xxx).not.toHaveBeenCalled() instead
+ */
+jasmine.Matchers.prototype.wasNotCalled = function() {
+ if (arguments.length > 0) {
+ throw new Error('wasNotCalled does not take arguments');
+ }
+
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy " + this.actual.identity + " to not have been called.",
+ "Expected spy " + this.actual.identity + " to have been called."
+ ];
+ };
+
+ return !this.actual.wasCalled;
+};
+
+/**
+ * Matcher that checks to see if the actual, a Jasmine spy, was called with a set of parameters.
+ *
+ * @example
+ *
+ */
+jasmine.Matchers.prototype.toHaveBeenCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+ this.message = function() {
+ if (this.actual.callCount === 0) {
+ // todo: what should the failure message for .not.toHaveBeenCalledWith() be? is this right? test better. [xw]
+ return [
+ "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but it was never called.",
+ "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but it was."
+ ];
+ } else {
+ return [
+ "Expected spy " + this.actual.identity + " to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall),
+ "Expected spy " + this.actual.identity + " not to have been called with " + jasmine.pp(expectedArgs) + " but was called with " + jasmine.pp(this.actual.argsForCall)
+ ];
+ }
+ };
+
+ return this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/** @deprecated Use expect(xxx).toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasCalledWith = jasmine.Matchers.prototype.toHaveBeenCalledWith;
+
+/** @deprecated Use expect(xxx).not.toHaveBeenCalledWith() instead */
+jasmine.Matchers.prototype.wasNotCalledWith = function() {
+ var expectedArgs = jasmine.util.argsToArray(arguments);
+ if (!jasmine.isSpy(this.actual)) {
+ throw new Error('Expected a spy, but got ' + jasmine.pp(this.actual) + '.');
+ }
+
+ this.message = function() {
+ return [
+ "Expected spy not to have been called with " + jasmine.pp(expectedArgs) + " but it was",
+ "Expected spy to have been called with " + jasmine.pp(expectedArgs) + " but it was"
+ ];
+ };
+
+ return !this.env.contains_(this.actual.argsForCall, expectedArgs);
+};
+
+/**
+ * Matcher that checks that the expected item is an element in the actual Array.
+ *
+ * @param {Object} expected
+ */
+jasmine.Matchers.prototype.toContain = function(expected) {
+ return this.env.contains_(this.actual, expected);
+};
+
+/**
+ * Matcher that checks that the expected item is NOT an element in the actual Array.
+ *
+ * @param {Object} expected
+ * @deprecated as of 1.0. Use not.toNotContain() instead.
+ */
+jasmine.Matchers.prototype.toNotContain = function(expected) {
+ return !this.env.contains_(this.actual, expected);
+};
+
+jasmine.Matchers.prototype.toBeLessThan = function(expected) {
+ return this.actual < expected;
+};
+
+jasmine.Matchers.prototype.toBeGreaterThan = function(expected) {
+ return this.actual > expected;
+};
+
+/**
+ * Matcher that checks that the expected item is equal to the actual item
+ * up to a given level of decimal precision (default 2).
+ *
+ * @param {Number} expected
+ * @param {Number} precision
+ */
+jasmine.Matchers.prototype.toBeCloseTo = function(expected, precision) {
+ if (!(precision === 0)) {
+ precision = precision || 2;
+ }
+ var multiplier = Math.pow(10, precision);
+ var actual = Math.round(this.actual * multiplier);
+ expected = Math.round(expected * multiplier);
+ return expected == actual;
+};
+
+/**
+ * Matcher that checks that the expected exception was thrown by the actual.
+ *
+ * @param {String} expected
+ */
+jasmine.Matchers.prototype.toThrow = function(expected) {
+ var result = false;
+ var exception;
+ if (typeof this.actual != 'function') {
+ throw new Error('Actual is not a function');
+ }
+ try {
+ this.actual();
+ } catch (e) {
+ exception = e;
+ }
+ if (exception) {
+ result = (expected === jasmine.undefined || this.env.equals_(exception.message || exception, expected.message || expected));
+ }
+
+ var not = this.isNot ? "not " : "";
+
+ this.message = function() {
+ if (exception && (expected === jasmine.undefined || !this.env.equals_(exception.message || exception, expected.message || expected))) {
+ return ["Expected function " + not + "to throw", expected ? expected.message || expected : "an exception", ", but it threw", exception.message || exception].join(' ');
+ } else {
+ return "Expected function to throw an exception.";
+ }
+ };
+
+ return result;
+};
+
+jasmine.Matchers.Any = function(expectedClass) {
+ this.expectedClass = expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.matches = function(other) {
+ if (this.expectedClass == String) {
+ return typeof other == 'string' || other instanceof String;
+ }
+
+ if (this.expectedClass == Number) {
+ return typeof other == 'number' || other instanceof Number;
+ }
+
+ if (this.expectedClass == Function) {
+ return typeof other == 'function' || other instanceof Function;
+ }
+
+ if (this.expectedClass == Object) {
+ return typeof other == 'object';
+ }
+
+ return other instanceof this.expectedClass;
+};
+
+jasmine.Matchers.Any.prototype.toString = function() {
+ return '';
+};
+
+/**
+ * @constructor
+ */
+jasmine.MultiReporter = function() {
+ this.subReporters_ = [];
+};
+jasmine.util.inherit(jasmine.MultiReporter, jasmine.Reporter);
+
+jasmine.MultiReporter.prototype.addReporter = function(reporter) {
+ this.subReporters_.push(reporter);
+};
+
+(function() {
+ var functionNames = [
+ "reportRunnerStarting",
+ "reportRunnerResults",
+ "reportSuiteResults",
+ "reportSpecStarting",
+ "reportSpecResults",
+ "log"
+ ];
+ for (var i = 0; i < functionNames.length; i++) {
+ var functionName = functionNames[i];
+ jasmine.MultiReporter.prototype[functionName] = (function(functionName) {
+ return function() {
+ for (var j = 0; j < this.subReporters_.length; j++) {
+ var subReporter = this.subReporters_[j];
+ if (subReporter[functionName]) {
+ subReporter[functionName].apply(subReporter, arguments);
+ }
+ }
+ };
+ })(functionName);
+ }
+})();
+/**
+ * Holds results for a set of Jasmine spec. Allows for the results array to hold another jasmine.NestedResults
+ *
+ * @constructor
+ */
+jasmine.NestedResults = function() {
+ /**
+ * The total count of results
+ */
+ this.totalCount = 0;
+ /**
+ * Number of passed results
+ */
+ this.passedCount = 0;
+ /**
+ * Number of failed results
+ */
+ this.failedCount = 0;
+ /**
+ * Was this suite/spec skipped?
+ */
+ this.skipped = false;
+ /**
+ * @ignore
+ */
+ this.items_ = [];
+};
+
+/**
+ * Roll up the result counts.
+ *
+ * @param result
+ */
+jasmine.NestedResults.prototype.rollupCounts = function(result) {
+ this.totalCount += result.totalCount;
+ this.passedCount += result.passedCount;
+ this.failedCount += result.failedCount;
+};
+
+/**
+ * Adds a log message.
+ * @param values Array of message parts which will be concatenated later.
+ */
+jasmine.NestedResults.prototype.log = function(values) {
+ this.items_.push(new jasmine.MessageResult(values));
+};
+
+/**
+ * Getter for the results: message & results.
+ */
+jasmine.NestedResults.prototype.getItems = function() {
+ return this.items_;
+};
+
+/**
+ * Adds a result, tracking counts (total, passed, & failed)
+ * @param {jasmine.ExpectationResult|jasmine.NestedResults} result
+ */
+jasmine.NestedResults.prototype.addResult = function(result) {
+ if (result.type != 'log') {
+ if (result.items_) {
+ this.rollupCounts(result);
+ } else {
+ this.totalCount++;
+ if (result.passed()) {
+ this.passedCount++;
+ } else {
+ this.failedCount++;
+ }
+ }
+ }
+ this.items_.push(result);
+};
+
+/**
+ * @returns {Boolean} True if everything below passed
+ */
+jasmine.NestedResults.prototype.passed = function() {
+ return this.passedCount === this.totalCount;
+};
+/**
+ * Base class for pretty printing for expectation results.
+ */
+jasmine.PrettyPrinter = function() {
+ this.ppNestLevel_ = 0;
+};
+
+/**
+ * Formats a value in a nice, human-readable string.
+ *
+ * @param value
+ */
+jasmine.PrettyPrinter.prototype.format = function(value) {
+ if (this.ppNestLevel_ > 40) {
+ throw new Error('jasmine.PrettyPrinter: format() nested too deeply!');
+ }
+
+ this.ppNestLevel_++;
+ try {
+ if (value === jasmine.undefined) {
+ this.emitScalar('undefined');
+ } else if (value === null) {
+ this.emitScalar('null');
+ } else if (value === jasmine.getGlobal()) {
+ this.emitScalar('');
+ } else if (value instanceof jasmine.Matchers.Any) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'string') {
+ this.emitString(value);
+ } else if (jasmine.isSpy(value)) {
+ this.emitScalar("spy on " + value.identity);
+ } else if (value instanceof RegExp) {
+ this.emitScalar(value.toString());
+ } else if (typeof value === 'function') {
+ this.emitScalar('Function');
+ } else if (typeof value.nodeType === 'number') {
+ this.emitScalar('HTMLNode');
+ } else if (value instanceof Date) {
+ this.emitScalar('Date(' + value + ')');
+ } else if (value.__Jasmine_been_here_before__) {
+ this.emitScalar('');
+ } else if (jasmine.isArray_(value) || typeof value == 'object') {
+ value.__Jasmine_been_here_before__ = true;
+ if (jasmine.isArray_(value)) {
+ this.emitArray(value);
+ } else {
+ this.emitObject(value);
+ }
+ delete value.__Jasmine_been_here_before__;
+ } else {
+ this.emitScalar(value.toString());
+ }
+ } finally {
+ this.ppNestLevel_--;
+ }
+};
+
+jasmine.PrettyPrinter.prototype.iterateObject = function(obj, fn) {
+ for (var property in obj) {
+ if (property == '__Jasmine_been_here_before__') continue;
+ fn(property, obj.__lookupGetter__ ? (obj.__lookupGetter__(property) !== jasmine.undefined &&
+ obj.__lookupGetter__(property) !== null) : false);
+ }
+};
+
+jasmine.PrettyPrinter.prototype.emitArray = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitObject = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitScalar = jasmine.unimplementedMethod_;
+jasmine.PrettyPrinter.prototype.emitString = jasmine.unimplementedMethod_;
+
+jasmine.StringPrettyPrinter = function() {
+ jasmine.PrettyPrinter.call(this);
+
+ this.string = '';
+};
+jasmine.util.inherit(jasmine.StringPrettyPrinter, jasmine.PrettyPrinter);
+
+jasmine.StringPrettyPrinter.prototype.emitScalar = function(value) {
+ this.append(value);
+};
+
+jasmine.StringPrettyPrinter.prototype.emitString = function(value) {
+ this.append("'" + value + "'");
+};
+
+jasmine.StringPrettyPrinter.prototype.emitArray = function(array) {
+ this.append('[ ');
+ for (var i = 0; i < array.length; i++) {
+ if (i > 0) {
+ this.append(', ');
+ }
+ this.format(array[i]);
+ }
+ this.append(' ]');
+};
+
+jasmine.StringPrettyPrinter.prototype.emitObject = function(obj) {
+ var self = this;
+ this.append('{ ');
+ var first = true;
+
+ this.iterateObject(obj, function(property, isGetter) {
+ if (first) {
+ first = false;
+ } else {
+ self.append(', ');
+ }
+
+ self.append(property);
+ self.append(' : ');
+ if (isGetter) {
+ self.append('');
+ } else {
+ self.format(obj[property]);
+ }
+ });
+
+ this.append(' }');
+};
+
+jasmine.StringPrettyPrinter.prototype.append = function(value) {
+ this.string += value;
+};
+jasmine.Queue = function(env) {
+ this.env = env;
+ this.blocks = [];
+ this.running = false;
+ this.index = 0;
+ this.offset = 0;
+ this.abort = false;
+};
+
+jasmine.Queue.prototype.addBefore = function(block) {
+ this.blocks.unshift(block);
+};
+
+jasmine.Queue.prototype.add = function(block) {
+ this.blocks.push(block);
+};
+
+jasmine.Queue.prototype.insertNext = function(block) {
+ this.blocks.splice((this.index + this.offset + 1), 0, block);
+ this.offset++;
+};
+
+jasmine.Queue.prototype.start = function(onComplete) {
+ this.running = true;
+ this.onComplete = onComplete;
+ this.next_();
+};
+
+jasmine.Queue.prototype.isRunning = function() {
+ return this.running;
+};
+
+jasmine.Queue.LOOP_DONT_RECURSE = true;
+
+jasmine.Queue.prototype.next_ = function() {
+ var self = this;
+ var goAgain = true;
+
+ while (goAgain) {
+ goAgain = false;
+
+ if (self.index < self.blocks.length && !this.abort) {
+ var calledSynchronously = true;
+ var completedSynchronously = false;
+
+ var onComplete = function () {
+ if (jasmine.Queue.LOOP_DONT_RECURSE && calledSynchronously) {
+ completedSynchronously = true;
+ return;
+ }
+
+ if (self.blocks[self.index].abort) {
+ self.abort = true;
+ }
+
+ self.offset = 0;
+ self.index++;
+
+ var now = new Date().getTime();
+ if (self.env.updateInterval && now - self.env.lastUpdate > self.env.updateInterval) {
+ self.env.lastUpdate = now;
+ self.env.setTimeout(function() {
+ self.next_();
+ }, 0);
+ } else {
+ if (jasmine.Queue.LOOP_DONT_RECURSE && completedSynchronously) {
+ goAgain = true;
+ } else {
+ self.next_();
+ }
+ }
+ };
+ self.blocks[self.index].execute(onComplete);
+
+ calledSynchronously = false;
+ if (completedSynchronously) {
+ onComplete();
+ }
+
+ } else {
+ self.running = false;
+ if (self.onComplete) {
+ self.onComplete();
+ }
+ }
+ }
+};
+
+jasmine.Queue.prototype.results = function() {
+ var results = new jasmine.NestedResults();
+ for (var i = 0; i < this.blocks.length; i++) {
+ if (this.blocks[i].results) {
+ results.addResult(this.blocks[i].results());
+ }
+ }
+ return results;
+};
+
+
+/**
+ * Runner
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ */
+jasmine.Runner = function(env) {
+ var self = this;
+ self.env = env;
+ self.queue = new jasmine.Queue(env);
+ self.before_ = [];
+ self.after_ = [];
+ self.suites_ = [];
+};
+
+jasmine.Runner.prototype.execute = function() {
+ var self = this;
+ if (self.env.reporter.reportRunnerStarting) {
+ self.env.reporter.reportRunnerStarting(this);
+ }
+ self.queue.start(function () {
+ self.finishCallback();
+ });
+};
+
+jasmine.Runner.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.splice(0,0,beforeEachFunction);
+};
+
+jasmine.Runner.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.splice(0,0,afterEachFunction);
+};
+
+
+jasmine.Runner.prototype.finishCallback = function() {
+ this.env.reporter.reportRunnerResults(this);
+};
+
+jasmine.Runner.prototype.addSuite = function(suite) {
+ this.suites_.push(suite);
+};
+
+jasmine.Runner.prototype.add = function(block) {
+ if (block instanceof jasmine.Suite) {
+ this.addSuite(block);
+ }
+ this.queue.add(block);
+};
+
+jasmine.Runner.prototype.specs = function () {
+ var suites = this.suites();
+ var specs = [];
+ for (var i = 0; i < suites.length; i++) {
+ specs = specs.concat(suites[i].specs());
+ }
+ return specs;
+};
+
+jasmine.Runner.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.Runner.prototype.topLevelSuites = function() {
+ var topLevelSuites = [];
+ for (var i = 0; i < this.suites_.length; i++) {
+ if (!this.suites_[i].parentSuite) {
+ topLevelSuites.push(this.suites_[i]);
+ }
+ }
+ return topLevelSuites;
+};
+
+jasmine.Runner.prototype.results = function() {
+ return this.queue.results();
+};
+/**
+ * Internal representation of a Jasmine specification, or test.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {jasmine.Suite} suite
+ * @param {String} description
+ */
+jasmine.Spec = function(env, suite, description) {
+ if (!env) {
+ throw new Error('jasmine.Env() required');
+ }
+ if (!suite) {
+ throw new Error('jasmine.Suite() required');
+ }
+ var spec = this;
+ spec.id = env.nextSpecId ? env.nextSpecId() : null;
+ spec.env = env;
+ spec.suite = suite;
+ spec.description = description;
+ spec.queue = new jasmine.Queue(env);
+
+ spec.afterCallbacks = [];
+ spec.spies_ = [];
+
+ spec.results_ = new jasmine.NestedResults();
+ spec.results_.description = description;
+ spec.matchersClass = null;
+};
+
+jasmine.Spec.prototype.getFullName = function() {
+ return this.suite.getFullName() + ' ' + this.description + '.';
+};
+
+
+jasmine.Spec.prototype.results = function() {
+ return this.results_;
+};
+
+/**
+ * All parameters are pretty-printed and concatenated together, then written to the spec's output.
+ *
+ * Be careful not to leave calls to jasmine.log
in production code.
+ */
+jasmine.Spec.prototype.log = function() {
+ return this.results_.log(arguments);
+};
+
+jasmine.Spec.prototype.runs = function (func) {
+ var block = new jasmine.Block(this.env, func, this);
+ this.addToQueue(block);
+ return this;
+};
+
+jasmine.Spec.prototype.addToQueue = function (block) {
+ if (this.queue.isRunning()) {
+ this.queue.insertNext(block);
+ } else {
+ this.queue.add(block);
+ }
+};
+
+/**
+ * @param {jasmine.ExpectationResult} result
+ */
+jasmine.Spec.prototype.addMatcherResult = function(result) {
+ this.results_.addResult(result);
+};
+
+jasmine.Spec.prototype.expect = function(actual) {
+ var positive = new (this.getMatchersClass_())(this.env, actual, this);
+ positive.not = new (this.getMatchersClass_())(this.env, actual, this, true);
+ return positive;
+};
+
+/**
+ * Waits a fixed time period before moving to the next block.
+ *
+ * @deprecated Use waitsFor() instead
+ * @param {Number} timeout milliseconds to wait
+ */
+jasmine.Spec.prototype.waits = function(timeout) {
+ var waitsFunc = new jasmine.WaitsBlock(this.env, timeout, this);
+ this.addToQueue(waitsFunc);
+ return this;
+};
+
+/**
+ * Waits for the latchFunction to return true before proceeding to the next block.
+ *
+ * @param {Function} latchFunction
+ * @param {String} optional_timeoutMessage
+ * @param {Number} optional_timeout
+ */
+jasmine.Spec.prototype.waitsFor = function(latchFunction, optional_timeoutMessage, optional_timeout) {
+ var latchFunction_ = null;
+ var optional_timeoutMessage_ = null;
+ var optional_timeout_ = null;
+
+ for (var i = 0; i < arguments.length; i++) {
+ var arg = arguments[i];
+ switch (typeof arg) {
+ case 'function':
+ latchFunction_ = arg;
+ break;
+ case 'string':
+ optional_timeoutMessage_ = arg;
+ break;
+ case 'number':
+ optional_timeout_ = arg;
+ break;
+ }
+ }
+
+ var waitsForFunc = new jasmine.WaitsForBlock(this.env, optional_timeout_, latchFunction_, optional_timeoutMessage_, this);
+ this.addToQueue(waitsForFunc);
+ return this;
+};
+
+jasmine.Spec.prototype.fail = function (e) {
+ var expectationResult = new jasmine.ExpectationResult({
+ passed: false,
+ message: e ? jasmine.util.formatException(e) : 'Exception',
+ trace: { stack: e.stack }
+ });
+ this.results_.addResult(expectationResult);
+};
+
+jasmine.Spec.prototype.getMatchersClass_ = function() {
+ return this.matchersClass || this.env.matchersClass;
+};
+
+jasmine.Spec.prototype.addMatchers = function(matchersPrototype) {
+ var parent = this.getMatchersClass_();
+ var newMatchersClass = function() {
+ parent.apply(this, arguments);
+ };
+ jasmine.util.inherit(newMatchersClass, parent);
+ jasmine.Matchers.wrapInto_(matchersPrototype, newMatchersClass);
+ this.matchersClass = newMatchersClass;
+};
+
+jasmine.Spec.prototype.finishCallback = function() {
+ this.env.reporter.reportSpecResults(this);
+};
+
+jasmine.Spec.prototype.finish = function(onComplete) {
+ this.removeAllSpies();
+ this.finishCallback();
+ if (onComplete) {
+ onComplete();
+ }
+};
+
+jasmine.Spec.prototype.after = function(doAfter) {
+ if (this.queue.isRunning()) {
+ this.queue.add(new jasmine.Block(this.env, doAfter, this));
+ } else {
+ this.afterCallbacks.unshift(doAfter);
+ }
+};
+
+jasmine.Spec.prototype.execute = function(onComplete) {
+ var spec = this;
+ if (!spec.env.specFilter(spec)) {
+ spec.results_.skipped = true;
+ spec.finish(onComplete);
+ return;
+ }
+
+ this.env.reporter.reportSpecStarting(this);
+
+ spec.env.currentSpec = spec;
+
+ spec.addBeforesAndAftersToQueue();
+
+ spec.queue.start(function () {
+ spec.finish(onComplete);
+ });
+};
+
+jasmine.Spec.prototype.addBeforesAndAftersToQueue = function() {
+ var runner = this.env.currentRunner();
+ var i;
+
+ for (var suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, suite.before_[i], this));
+ }
+ }
+ for (i = 0; i < runner.before_.length; i++) {
+ this.queue.addBefore(new jasmine.Block(this.env, runner.before_[i], this));
+ }
+ for (i = 0; i < this.afterCallbacks.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, this.afterCallbacks[i], this));
+ }
+ for (suite = this.suite; suite; suite = suite.parentSuite) {
+ for (i = 0; i < suite.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, suite.after_[i], this));
+ }
+ }
+ for (i = 0; i < runner.after_.length; i++) {
+ this.queue.add(new jasmine.Block(this.env, runner.after_[i], this));
+ }
+};
+
+jasmine.Spec.prototype.explodes = function() {
+ throw 'explodes function should not have been called';
+};
+
+jasmine.Spec.prototype.spyOn = function(obj, methodName, ignoreMethodDoesntExist) {
+ if (obj == jasmine.undefined) {
+ throw "spyOn could not find an object to spy upon for " + methodName + "()";
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] === jasmine.undefined) {
+ throw methodName + '() method does not exist';
+ }
+
+ if (!ignoreMethodDoesntExist && obj[methodName] && obj[methodName].isSpy) {
+ throw new Error(methodName + ' has already been spied upon');
+ }
+
+ var spyObj = jasmine.createSpy(methodName);
+
+ this.spies_.push(spyObj);
+ spyObj.baseObj = obj;
+ spyObj.methodName = methodName;
+ spyObj.originalValue = obj[methodName];
+
+ obj[methodName] = spyObj;
+
+ return spyObj;
+};
+
+jasmine.Spec.prototype.removeAllSpies = function() {
+ for (var i = 0; i < this.spies_.length; i++) {
+ var spy = this.spies_[i];
+ spy.baseObj[spy.methodName] = spy.originalValue;
+ }
+ this.spies_ = [];
+};
+
+/**
+ * Internal representation of a Jasmine suite.
+ *
+ * @constructor
+ * @param {jasmine.Env} env
+ * @param {String} description
+ * @param {Function} specDefinitions
+ * @param {jasmine.Suite} parentSuite
+ */
+jasmine.Suite = function(env, description, specDefinitions, parentSuite) {
+ var self = this;
+ self.id = env.nextSuiteId ? env.nextSuiteId() : null;
+ self.description = description;
+ self.queue = new jasmine.Queue(env);
+ self.parentSuite = parentSuite;
+ self.env = env;
+ self.before_ = [];
+ self.after_ = [];
+ self.children_ = [];
+ self.suites_ = [];
+ self.specs_ = [];
+};
+
+jasmine.Suite.prototype.getFullName = function() {
+ var fullName = this.description;
+ for (var parentSuite = this.parentSuite; parentSuite; parentSuite = parentSuite.parentSuite) {
+ fullName = parentSuite.description + ' ' + fullName;
+ }
+ return fullName;
+};
+
+jasmine.Suite.prototype.finish = function(onComplete) {
+ this.env.reporter.reportSuiteResults(this);
+ this.finished = true;
+ if (typeof(onComplete) == 'function') {
+ onComplete();
+ }
+};
+
+jasmine.Suite.prototype.beforeEach = function(beforeEachFunction) {
+ beforeEachFunction.typeName = 'beforeEach';
+ this.before_.unshift(beforeEachFunction);
+};
+
+jasmine.Suite.prototype.afterEach = function(afterEachFunction) {
+ afterEachFunction.typeName = 'afterEach';
+ this.after_.unshift(afterEachFunction);
+};
+
+jasmine.Suite.prototype.results = function() {
+ return this.queue.results();
+};
+
+jasmine.Suite.prototype.add = function(suiteOrSpec) {
+ this.children_.push(suiteOrSpec);
+ if (suiteOrSpec instanceof jasmine.Suite) {
+ this.suites_.push(suiteOrSpec);
+ this.env.currentRunner().addSuite(suiteOrSpec);
+ } else {
+ this.specs_.push(suiteOrSpec);
+ }
+ this.queue.add(suiteOrSpec);
+};
+
+jasmine.Suite.prototype.specs = function() {
+ return this.specs_;
+};
+
+jasmine.Suite.prototype.suites = function() {
+ return this.suites_;
+};
+
+jasmine.Suite.prototype.children = function() {
+ return this.children_;
+};
+
+jasmine.Suite.prototype.execute = function(onComplete) {
+ var self = this;
+ this.queue.start(function () {
+ self.finish(onComplete);
+ });
+};
+jasmine.WaitsBlock = function(env, timeout, spec) {
+ this.timeout = timeout;
+ jasmine.Block.call(this, env, null, spec);
+};
+
+jasmine.util.inherit(jasmine.WaitsBlock, jasmine.Block);
+
+jasmine.WaitsBlock.prototype.execute = function (onComplete) {
+ if (jasmine.VERBOSE) {
+ this.env.reporter.log('>> Jasmine waiting for ' + this.timeout + ' ms...');
+ }
+ this.env.setTimeout(function () {
+ onComplete();
+ }, this.timeout);
+};
+/**
+ * A block which waits for some condition to become true, with timeout.
+ *
+ * @constructor
+ * @extends jasmine.Block
+ * @param {jasmine.Env} env The Jasmine environment.
+ * @param {Number} timeout The maximum time in milliseconds to wait for the condition to become true.
+ * @param {Function} latchFunction A function which returns true when the desired condition has been met.
+ * @param {String} message The message to display if the desired condition hasn't been met within the given time period.
+ * @param {jasmine.Spec} spec The Jasmine spec.
+ */
+jasmine.WaitsForBlock = function(env, timeout, latchFunction, message, spec) {
+ this.timeout = timeout || env.defaultTimeoutInterval;
+ this.latchFunction = latchFunction;
+ this.message = message;
+ this.totalTimeSpentWaitingForLatch = 0;
+ jasmine.Block.call(this, env, null, spec);
+};
+jasmine.util.inherit(jasmine.WaitsForBlock, jasmine.Block);
+
+jasmine.WaitsForBlock.TIMEOUT_INCREMENT = 10;
+
+jasmine.WaitsForBlock.prototype.execute = function(onComplete) {
+ if (jasmine.VERBOSE) {
+ this.env.reporter.log('>> Jasmine waiting for ' + (this.message || 'something to happen'));
+ }
+ var latchFunctionResult;
+ try {
+ latchFunctionResult = this.latchFunction.apply(this.spec);
+ } catch (e) {
+ this.spec.fail(e);
+ onComplete();
+ return;
+ }
+
+ if (latchFunctionResult) {
+ onComplete();
+ } else if (this.totalTimeSpentWaitingForLatch >= this.timeout) {
+ var message = 'timed out after ' + this.timeout + ' msec waiting for ' + (this.message || 'something to happen');
+ this.spec.fail({
+ name: 'timeout',
+ message: message
+ });
+
+ this.abort = true;
+ onComplete();
+ } else {
+ this.totalTimeSpentWaitingForLatch += jasmine.WaitsForBlock.TIMEOUT_INCREMENT;
+ var self = this;
+ this.env.setTimeout(function() {
+ self.execute(onComplete);
+ }, jasmine.WaitsForBlock.TIMEOUT_INCREMENT);
+ }
+};
+// Mock setTimeout, clearTimeout
+// Contributed by Pivotal Computer Systems, www.pivotalsf.com
+
+jasmine.FakeTimer = function() {
+ this.reset();
+
+ var self = this;
+ self.setTimeout = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, false);
+ return self.timeoutsMade;
+ };
+
+ self.setInterval = function(funcToCall, millis) {
+ self.timeoutsMade++;
+ self.scheduleFunction(self.timeoutsMade, funcToCall, millis, true);
+ return self.timeoutsMade;
+ };
+
+ self.clearTimeout = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+ self.clearInterval = function(timeoutKey) {
+ self.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ };
+
+};
+
+jasmine.FakeTimer.prototype.reset = function() {
+ this.timeoutsMade = 0;
+ this.scheduledFunctions = {};
+ this.nowMillis = 0;
+};
+
+jasmine.FakeTimer.prototype.tick = function(millis) {
+ var oldMillis = this.nowMillis;
+ var newMillis = oldMillis + millis;
+ this.runFunctionsWithinRange(oldMillis, newMillis);
+ this.nowMillis = newMillis;
+};
+
+jasmine.FakeTimer.prototype.runFunctionsWithinRange = function(oldMillis, nowMillis) {
+ var scheduledFunc;
+ var funcsToRun = [];
+ for (var timeoutKey in this.scheduledFunctions) {
+ scheduledFunc = this.scheduledFunctions[timeoutKey];
+ if (scheduledFunc != jasmine.undefined &&
+ scheduledFunc.runAtMillis >= oldMillis &&
+ scheduledFunc.runAtMillis <= nowMillis) {
+ funcsToRun.push(scheduledFunc);
+ this.scheduledFunctions[timeoutKey] = jasmine.undefined;
+ }
+ }
+
+ if (funcsToRun.length > 0) {
+ funcsToRun.sort(function(a, b) {
+ return a.runAtMillis - b.runAtMillis;
+ });
+ for (var i = 0; i < funcsToRun.length; ++i) {
+ try {
+ var funcToRun = funcsToRun[i];
+ this.nowMillis = funcToRun.runAtMillis;
+ funcToRun.funcToCall();
+ if (funcToRun.recurring) {
+ this.scheduleFunction(funcToRun.timeoutKey,
+ funcToRun.funcToCall,
+ funcToRun.millis,
+ true);
+ }
+ } catch(e) {
+ }
+ }
+ this.runFunctionsWithinRange(oldMillis, nowMillis);
+ }
+};
+
+jasmine.FakeTimer.prototype.scheduleFunction = function(timeoutKey, funcToCall, millis, recurring) {
+ this.scheduledFunctions[timeoutKey] = {
+ runAtMillis: this.nowMillis + millis,
+ funcToCall: funcToCall,
+ recurring: recurring,
+ timeoutKey: timeoutKey,
+ millis: millis
+ };
+};
+
+/**
+ * @namespace
+ */
+jasmine.Clock = {
+ defaultFakeTimer: new jasmine.FakeTimer(),
+
+ reset: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.reset();
+ },
+
+ tick: function(millis) {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.defaultFakeTimer.tick(millis);
+ },
+
+ runFunctionsWithinRange: function(oldMillis, nowMillis) {
+ jasmine.Clock.defaultFakeTimer.runFunctionsWithinRange(oldMillis, nowMillis);
+ },
+
+ scheduleFunction: function(timeoutKey, funcToCall, millis, recurring) {
+ jasmine.Clock.defaultFakeTimer.scheduleFunction(timeoutKey, funcToCall, millis, recurring);
+ },
+
+ useMock: function() {
+ if (!jasmine.Clock.isInstalled()) {
+ var spec = jasmine.getEnv().currentSpec;
+ spec.after(jasmine.Clock.uninstallMock);
+
+ jasmine.Clock.installMock();
+ }
+ },
+
+ installMock: function() {
+ jasmine.Clock.installed = jasmine.Clock.defaultFakeTimer;
+ },
+
+ uninstallMock: function() {
+ jasmine.Clock.assertInstalled();
+ jasmine.Clock.installed = jasmine.Clock.real;
+ },
+
+ real: {
+ setTimeout: jasmine.getGlobal().setTimeout,
+ clearTimeout: jasmine.getGlobal().clearTimeout,
+ setInterval: jasmine.getGlobal().setInterval,
+ clearInterval: jasmine.getGlobal().clearInterval
+ },
+
+ assertInstalled: function() {
+ if (!jasmine.Clock.isInstalled()) {
+ throw new Error("Mock clock is not installed, use jasmine.Clock.useMock()");
+ }
+ },
+
+ isInstalled: function() {
+ return jasmine.Clock.installed == jasmine.Clock.defaultFakeTimer;
+ },
+
+ installed: null
+};
+jasmine.Clock.installed = jasmine.Clock.real;
+
+//else for IE support
+jasmine.getGlobal().setTimeout = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setTimeout.apply) {
+ return jasmine.Clock.installed.setTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setTimeout(funcToCall, millis);
+ }
+};
+
+jasmine.getGlobal().setInterval = function(funcToCall, millis) {
+ if (jasmine.Clock.installed.setInterval.apply) {
+ return jasmine.Clock.installed.setInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.setInterval(funcToCall, millis);
+ }
+};
+
+jasmine.getGlobal().clearTimeout = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearTimeout.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearTimeout(timeoutKey);
+ }
+};
+
+jasmine.getGlobal().clearInterval = function(timeoutKey) {
+ if (jasmine.Clock.installed.clearTimeout.apply) {
+ return jasmine.Clock.installed.clearInterval.apply(this, arguments);
+ } else {
+ return jasmine.Clock.installed.clearInterval(timeoutKey);
+ }
+};
+
+jasmine.version_= {
+ "major": 1,
+ "minor": 1,
+ "build": 0,
+ "revision": 1320442951
+};
diff --git a/external/jasmine/jasmine_favicon.png b/external/jasmine/jasmine_favicon.png
new file mode 100644
index 000000000..218f3b437
Binary files /dev/null and b/external/jasmine/jasmine_favicon.png differ
diff --git a/src/canvas.js b/src/canvas.js
index 44e73c6af..9b3ed0ba9 100644
--- a/src/canvas.js
+++ b/src/canvas.js
@@ -445,7 +445,7 @@ var CanvasGraphics = (function canvasGraphics() {
this.save();
ctx.scale(fontSize, fontSize);
ctx.transform.apply(ctx, fontMatrix);
- this.executeIRQueue(glyph.IRQueue);
+ this.executeIRQueue(glyph.codeIRQueue);
this.restore();
var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
@@ -546,7 +546,9 @@ var CanvasGraphics = (function canvasGraphics() {
setStrokeColor: function canvasGraphicsSetStrokeColor(/*...*/) {
var cs = this.current.strokeColorSpace;
var color = cs.getRgb(arguments);
- this.setStrokeRGBColor.apply(this, color);
+ var color = Util.makeCssRgb.apply(null, cs.getRgb(arguments));
+ this.ctx.strokeStyle = color;
+ this.current.strokeColor = color;
},
getColorN_IR_Pattern: function canvasGraphicsGetColorN_IR_Pattern(IR, cs) {
if (IR[0] == 'TilingPattern') {
@@ -581,8 +583,9 @@ var CanvasGraphics = (function canvasGraphics() {
},
setFillColor: function canvasGraphicsSetFillColor(/*...*/) {
var cs = this.current.fillColorSpace;
- var color = cs.getRgb(arguments);
- this.setFillRGBColor.apply(this, color);
+ var color = Util.makeCssRgb.apply(null, cs.getRgb(arguments));
+ this.ctx.fillStyle = color;
+ this.current.fillColor = color;
},
setFillColorN_IR: function canvasGraphicsSetFillColorN(/*...*/) {
var cs = this.current.fillColorSpace;
@@ -594,27 +597,49 @@ var CanvasGraphics = (function canvasGraphics() {
}
},
setStrokeGray: function canvasGraphicsSetStrokeGray(gray) {
- this.setStrokeRGBColor(gray, gray, gray);
+ if (!(this.current.strokeColorSpace instanceof DeviceGrayCS))
+ this.current.strokeColorSpace = new DeviceGrayCS();
+
+ var color = Util.makeCssRgb(gray, gray, gray);
+ this.ctx.strokeStyle = color;
+ this.current.strokeColor = color;
},
setFillGray: function canvasGraphicsSetFillGray(gray) {
- this.setFillRGBColor(gray, gray, gray);
+ if (!(this.current.fillColorSpace instanceof DeviceGrayCS))
+ this.current.fillColorSpace = new DeviceGrayCS();
+
+ var color = Util.makeCssRgb(gray, gray, gray);
+ this.ctx.fillStyle = color;
+ this.current.fillColor = color;
},
setStrokeRGBColor: function canvasGraphicsSetStrokeRGBColor(r, g, b) {
+ if (!(this.current.strokeColorSpace instanceof DeviceRgbCS))
+ this.current.strokeColorSpace = new DeviceRgbCS();
+
var color = Util.makeCssRgb(r, g, b);
this.ctx.strokeStyle = color;
this.current.strokeColor = color;
},
setFillRGBColor: function canvasGraphicsSetFillRGBColor(r, g, b) {
+ if (!(this.current.fillColorSpace instanceof DeviceRgbCS))
+ this.current.fillColorSpace = new DeviceRgbCS();
+
var color = Util.makeCssRgb(r, g, b);
this.ctx.fillStyle = color;
this.current.fillColor = color;
},
setStrokeCMYKColor: function canvasGraphicsSetStrokeCMYKColor(c, m, y, k) {
+ if (!(this.current.strokeColorSpace instanceof DeviceCmykCS))
+ this.current.strokeColorSpace = new DeviceCmykCS();
+
var color = Util.makeCssCmyk(c, m, y, k);
this.ctx.strokeStyle = color;
this.current.strokeColor = color;
},
setFillCMYKColor: function canvasGraphicsSetFillCMYKColor(c, m, y, k) {
+ if (!(this.current.fillColorSpace instanceof DeviceCmykCS))
+ this.current.fillColorSpace = new DeviceCmykCS();
+
var color = Util.makeCssCmyk(c, m, y, k);
this.ctx.fillStyle = color;
this.current.fillColor = color;
diff --git a/src/colorspace.js b/src/colorspace.js
index 946a1bdf4..b369d0f88 100644
--- a/src/colorspace.js
+++ b/src/colorspace.js
@@ -24,7 +24,7 @@ var ColorSpace = (function colorSpaceColorSpace() {
constructor.parse = function colorSpaceParse(cs, xref, res) {
var IR = constructor.parseToIR(cs, xref, res);
- if (IR instanceof SeparationCS)
+ if (IR instanceof AlternateCS)
return IR;
return constructor.fromIR(IR);
@@ -50,11 +50,12 @@ var ColorSpace = (function colorSpaceColorSpace() {
var hiVal = IR[2];
var lookup = IR[3];
return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup);
- case 'SeparationCS':
- var alt = IR[1];
- var tintFnIR = IR[2];
+ case 'AlternateCS':
+ var numComps = IR[1];
+ var alt = IR[2];
+ var tintFnIR = IR[3];
- return new SeparationCS(ColorSpace.fromIR(alt),
+ return new AlternateCS(numComps, ColorSpace.fromIR(alt),
PDFFunction.fromIR(tintFnIR));
default:
error('Unkown name ' + name);
@@ -134,11 +135,17 @@ var ColorSpace = (function colorSpaceColorSpace() {
var lookup = xref.fetchIfRef(cs[3]);
return ['IndexedCS', baseIndexedCS, hiVal, lookup];
case 'Separation':
+ case 'DeviceN':
+ var name = cs[1];
+ var numComps = 1;
+ if (isName(name))
+ numComps = 1;
+ else if (isArray(name))
+ numComps = name.length;
var alt = ColorSpace.parseToIR(cs[2], xref, res);
var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3]));
- return ['SeparationCS', alt, tintFnIR];
+ return ['AlternateCS', numComps, alt, tintFnIR];
case 'Lab':
- case 'DeviceN':
default:
error('unimplemented color space object "' + mode + '"');
}
@@ -151,33 +158,45 @@ var ColorSpace = (function colorSpaceColorSpace() {
return constructor;
})();
-var SeparationCS = (function separationCS() {
- function constructor(base, tintFn) {
- this.name = 'Separation';
- this.numComps = 1;
- this.defaultColor = [1];
+/**
+ * Alternate color space handles both Separation and DeviceN color spaces. A
+ * Separation color space is actually just a DeviceN with one color component.
+ * Both color spaces use a tinting function to convert colors to a base color
+ * space.
+ */
+var AlternateCS = (function alternateCS() {
+ function constructor(numComps, base, tintFn) {
+ this.name = 'Alternate';
+ this.numComps = numComps;
+ this.defaultColor = [];
+ for (var i = 0; i < numComps; ++i)
+ this.defaultColor.push(1);
this.base = base;
this.tintFn = tintFn;
}
constructor.prototype = {
- getRgb: function sepcs_getRgb(color) {
+ getRgb: function altcs_getRgb(color) {
var tinted = this.tintFn(color);
return this.base.getRgb(tinted);
},
- getRgbBuffer: function sepcs_getRgbBuffer(input, bits) {
+ getRgbBuffer: function altcs_getRgbBuffer(input, bits) {
var tintFn = this.tintFn;
var base = this.base;
var scale = 1 / ((1 << bits) - 1);
var length = input.length;
var pos = 0;
- var numComps = base.numComps;
- var baseBuf = new Uint8Array(numComps * length);
+ var baseNumComps = base.numComps;
+ var baseBuf = new Uint8Array(baseNumComps * length);
+ var numComps = this.numComps;
+ var scaled = new Array(numComps);
- for (var i = 0; i < length; ++i) {
- var scaled = input[i] * scale;
- var tinted = tintFn([scaled]);
- for (var j = 0; j < numComps; ++j)
+ for (var i = 0; i < length; i += numComps) {
+ for (var z = 0; z < numComps; ++z)
+ scaled[z] = input[i + z] * scale;
+
+ var tinted = tintFn(scaled);
+ for (var j = 0; j < baseNumComps; ++j)
baseBuf[pos++] = 255 * tinted[j];
}
return base.getRgbBuffer(baseBuf, 8);
diff --git a/src/core.js b/src/core.js
index 6ff34dfbe..3549eb906 100644
--- a/src/core.js
+++ b/src/core.js
@@ -15,10 +15,6 @@ if (!globalScope.PDFJS) {
globalScope.PDFJS = {};
}
-// Temporarily disabling workers until 'localhost' FF bugfix lands:
-// https://bugzilla.mozilla.org/show_bug.cgi?id=683280
-globalScope.PDFJS.disableWorker = true;
-
// getPdf()
// Convenience function to perform binary Ajax GET
// Usage: getPdf('http://...', callback)
@@ -471,6 +467,7 @@ var PDFDoc = (function pdfDoc() {
this.objs = new PDFObjects();
this.pageCache = [];
+ this.fontsLoading = {};
this.workerReadyPromise = new Promise('workerReady');
// If worker support isn't disabled explicit and the browser has worker
@@ -484,7 +481,16 @@ var PDFDoc = (function pdfDoc() {
throw 'No PDFJS.workerSrc specified';
}
- var worker = new Worker(workerSrc);
+ var worker;
+ try {
+ worker = new Worker(workerSrc);
+ } catch (e) {
+ // Some versions of FF can't create a worker on localhost, see:
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
+ globalScope.PDFJS.disableWorker = true;
+ this.setupFakeWorker();
+ return;
+ }
var messageHandler = new MessageHandler('main', worker);
@@ -505,8 +511,6 @@ var PDFDoc = (function pdfDoc() {
} else {
this.setupFakeWorker();
}
-
- this.fontsLoading = {};
}
constructor.prototype = {
diff --git a/src/evaluator.js b/src/evaluator.js
index 3b33519d8..a863a531e 100644
--- a/src/evaluator.js
+++ b/src/evaluator.js
@@ -459,18 +459,183 @@ var PartialEvaluator = (function partialEvaluator() {
};
},
- extractEncoding: function partialEvaluatorExtractEncoding(dict,
- xref,
- properties) {
- var type = properties.type, encoding;
- if (properties.composite) {
- var defaultWidth = xref.fetchIfRef(dict.get('DW')) || 1000;
- properties.defaultWidth = defaultWidth;
+ extractDataStructures: function
+ partialEvaluatorExtractDataStructures(dict, baseDict,
+ xref, properties) {
+ // 9.10.2
+ var toUnicode = dict.get('ToUnicode') ||
+ baseDict.get('ToUnicode');
+ if (toUnicode)
+ properties.toUnicode = this.readToUnicode(toUnicode, xref);
+
+ if (properties.composite) {
+ // CIDSystemInfo helps to match CID to glyphs
+ var cidSystemInfo = xref.fetchIfRef(dict.get('CIDSystemInfo'));
+ if (isDict(cidSystemInfo)) {
+ properties.cidSystemInfo = {
+ registry: cidSystemInfo.get('Registry'),
+ ordering: cidSystemInfo.get('Ordering'),
+ supplement: cidSystemInfo.get('Supplement')
+ };
+ }
+
+ var cidToGidMap = xref.fetchIfRef(dict.get('CIDToGIDMap'));
+ if (isStream(cidToGidMap))
+ properties.cidToGidMap = this.readCidToGidMap(cidToGidMap);
+ }
+
+ var differences = [];
+ var baseEncoding = Encodings.StandardEncoding;
+ var hasEncoding = dict.has('Encoding');
+ if (hasEncoding) {
+ var encoding = xref.fetchIfRef(dict.get('Encoding'));
+ if (isDict(encoding)) {
+ var baseName = encoding.get('BaseEncoding');
+ if (baseName)
+ baseEncoding = Encodings[baseName.name];
+
+ // Load the differences between the base and original
+ if (encoding.has('Differences')) {
+ var diffEncoding = encoding.get('Differences');
+ var index = 0;
+ for (var j = 0, jj = diffEncoding.length; j < jj; j++) {
+ var data = diffEncoding[j];
+ if (isNum(data))
+ index = data;
+ else
+ differences[index++] = data.name;
+ }
+ }
+ } else if (isName(encoding)) {
+ baseEncoding = Encodings[encoding.name];
+ } else {
+ error('Encoding is not a Name nor a Dict');
+ }
+ }
+ properties.differences = differences;
+ properties.baseEncoding = baseEncoding;
+ properties.hasEncoding = hasEncoding;
+ },
+
+ readToUnicode:
+ function partialEvaluatorReadToUnicode(toUnicode, xref) {
+ var cmapObj = xref.fetchIfRef(toUnicode);
+ var charToUnicode = [];
+ if (isName(cmapObj)) {
+ var isIdentityMap = cmapObj.name.substr(0, 9) == 'Identity-';
+ if (!isIdentityMap)
+ error('ToUnicode file cmap translation not implemented');
+ } else if (isStream(cmapObj)) {
+ var tokens = [];
+ var token = '';
+ var beginArrayToken = {};
+
+ var cmap = cmapObj.getBytes(cmapObj.length);
+ for (var i = 0, ii = cmap.length; i < ii; i++) {
+ var byte = cmap[i];
+ if (byte == 0x20 || byte == 0x0D || byte == 0x0A ||
+ byte == 0x3C || byte == 0x5B || byte == 0x5D) {
+ switch (token) {
+ case 'usecmap':
+ error('usecmap is not implemented');
+ break;
+
+ case 'beginbfchar':
+ case 'beginbfrange':
+ case 'begincidchar':
+ case 'begincidrange':
+ token = '';
+ tokens = [];
+ break;
+
+ case 'endcidrange':
+ case 'endbfrange':
+ for (var j = 0, jj = tokens.length; j < jj; j += 3) {
+ var startRange = tokens[j];
+ var endRange = tokens[j + 1];
+ var code = tokens[j + 2];
+ while (startRange <= endRange) {
+ charToUnicode[startRange] = code++;
+ ++startRange;
+ }
+ }
+ break;
+
+ case 'endcidchar':
+ case 'endbfchar':
+ for (var j = 0, jj = tokens.length; j < jj; j += 2) {
+ var index = tokens[j];
+ var code = tokens[j + 1];
+ charToUnicode[index] = code;
+ }
+ break;
+
+ case '':
+ break;
+
+ default:
+ if (token[0] >= '0' && token[0] <= '9')
+ token = parseInt(token, 10); // a number
+ tokens.push(token);
+ token = '';
+ }
+ switch (byte) {
+ case 0x5B:
+ // begin list parsing
+ tokens.push(beginArrayToken);
+ break;
+ case 0x5D:
+ // collect array items
+ var items = [], item;
+ while (tokens.length &&
+ (item = tokens.pop()) != beginArrayToken)
+ items.unshift(item);
+ tokens.push(items);
+ break;
+ }
+ } else if (byte == 0x3E) {
+ if (token.length) {
+ // parsing hex number
+ tokens.push(parseInt(token, 16));
+ token = '';
+ }
+ } else {
+ token += String.fromCharCode(byte);
+ }
+ }
+ }
+ return charToUnicode;
+ },
+ readCidToGidMap:
+ function partialEvaluatorReadCidToGidMap(cidToGidStream) {
+ // Extract the encoding from the CIDToGIDMap
+ var glyphsData = cidToGidStream.getBytes();
+
+ // Set encoding 0 to later verify the font has an encoding
+ var result = [];
+ for (var j = 0, jj = glyphsData.length; j < jj; j++) {
+ var glyphID = (glyphsData[j++] << 8) | glyphsData[j];
+ if (glyphID == 0)
+ continue;
+
+ var code = j >> 1;
+ result[code] = glyphID;
+ }
+ return result;
+ },
+
+ extractWidths: function partialEvaluatorWidths(dict,
+ xref,
+ descriptor,
+ properties) {
+ var glyphsWidths = [];
+ var defaultWidth = 0;
+ if (properties.composite) {
+ defaultWidth = xref.fetchIfRef(dict.get('DW')) || 1000;
- var glyphsWidths = {};
var widths = xref.fetchIfRef(dict.get('W'));
if (widths) {
- var start = 0;
+ var start = 0, end = 0;
for (var i = 0, ii = widths.length; i < ii; i++) {
var code = widths[i];
if (isArray(code)) {
@@ -487,247 +652,42 @@ var PartialEvaluator = (function partialEvaluator() {
}
}
}
- properties.widths = glyphsWidths;
-
- // Glyph ids are big-endian 2-byte values
- encoding = properties.encoding;
-
- // CIDSystemInfo might help to match width and glyphs
- var cidSystemInfo = dict.get('CIDSystemInfo');
- if (isDict(cidSystemInfo)) {
- properties.cidSystemInfo = {
- registry: cidSystemInfo.get('Registry'),
- ordering: cidSystemInfo.get('Ordering'),
- supplement: cidSystemInfo.get('Supplement')
- };
- }
-
- var cidToGidMap = dict.get('CIDToGIDMap');
- if (!cidToGidMap || !isRef(cidToGidMap)) {
-
-
- return Object.create(GlyphsUnicode);
- }
-
- // Extract the encoding from the CIDToGIDMap
- var glyphsStream = xref.fetchIfRef(cidToGidMap);
- var glyphsData = glyphsStream.getBytes(0);
-
- // Set encoding 0 to later verify the font has an encoding
- encoding[0] = { unicode: 0, width: 0 };
- for (var j = 0, jj = glyphsData.length; j < jj; j++) {
- var glyphID = (glyphsData[j++] << 8) | glyphsData[j];
- if (glyphID == 0)
- continue;
-
- var code = j >> 1;
- var width = glyphsWidths[code];
- encoding[code] = {
- unicode: glyphID,
- width: isNum(width) ? width : defaultWidth
- };
- }
-
- return Object.create(GlyphsUnicode);
- }
-
- var differences = properties.differences;
- var map = properties.encoding;
- var baseEncoding = null;
- if (dict.has('Encoding')) {
- encoding = xref.fetchIfRef(dict.get('Encoding'));
- if (isDict(encoding)) {
- var baseName = encoding.get('BaseEncoding');
- if (baseName)
- baseEncoding = Encodings[baseName.name].slice();
-
- // Load the differences between the base and original
- if (encoding.has('Differences')) {
- var diffEncoding = encoding.get('Differences');
- var index = 0;
- for (var j = 0, jj = diffEncoding.length; j < jj; j++) {
- var data = diffEncoding[j];
- if (isNum(data))
- index = data;
- else
- differences[index++] = data.name;
- }
- }
- } else if (isName(encoding)) {
- baseEncoding = Encodings[encoding.name].slice();
+ } else {
+ var firstChar = properties.firstChar;
+ var widths = xref.fetchIfRef(dict.get('Widths'));
+ if (widths) {
+ var j = firstChar;
+ for (var i = 0, ii = widths.length; i < ii; i++)
+ glyphsWidths[j++] = widths[i];
+ defaultWidth = parseFloat(descriptor.get('MissingWidth')) || 0;
} else {
- error('Encoding is not a Name nor a Dict');
- }
- }
+ // Trying get the BaseFont metrics (see comment above).
+ var baseFontName = dict.get('BaseFont');
+ if (isName(baseFontName)) {
+ var metrics = this.getBaseFontMetrics(baseFontName.name);
- if (!baseEncoding) {
- switch (type) {
- case 'TrueType':
- baseEncoding = Encodings.WinAnsiEncoding.slice();
- break;
- case 'Type1':
- case 'Type3':
- baseEncoding = Encodings.StandardEncoding.slice();
- break;
- default:
- warn('Unknown type of font: ' + type);
- baseEncoding = [];
- break;
- }
- }
-
- // merge in the differences
- var firstChar = properties.firstChar;
- var lastChar = properties.lastChar;
- var widths = properties.widths || [];
- var glyphs = {};
- for (var i = firstChar; i <= lastChar; i++) {
- var glyph = differences[i];
- var replaceGlyph = true;
- if (!glyph) {
- glyph = baseEncoding[i] || i;
- replaceGlyph = false;
- }
- var index = GlyphsUnicode[glyph] || i;
- var width = widths[i] || widths[glyph];
- map[i] = {
- unicode: index,
- width: isNum(width) ? width : properties.defaultWidth
- };
-
- if (replaceGlyph || !glyphs[glyph])
- glyphs[glyph] = map[i];
- if (replaceGlyph || !glyphs[index])
- glyphs[index] = map[i];
-
- // If there is no file, the character mapping can't be modified
- // but this is unlikely that there is any standard encoding with
- // chars below 0x1f, so that's fine.
- if (!properties.file)
- continue;
-
- if (index <= 0x1f || (index >= 127 && index <= 255))
- map[i].unicode += kCmapGlyphOffset;
- }
-
- if (type == 'TrueType' && dict.has('ToUnicode') && differences) {
- var cmapObj = dict.get('ToUnicode');
- if (isRef(cmapObj)) {
- cmapObj = xref.fetch(cmapObj);
- }
- if (isName(cmapObj)) {
- error('ToUnicode file cmap translation not implemented');
- } else if (isStream(cmapObj)) {
- var tokens = [];
- var token = '';
- var beginArrayToken = {};
-
- var cmap = cmapObj.getBytes(cmapObj.length);
- for (var i = 0, ii = cmap.length; i < ii; i++) {
- var byte = cmap[i];
- if (byte == 0x20 || byte == 0x0D || byte == 0x0A ||
- byte == 0x3C || byte == 0x5B || byte == 0x5D) {
- switch (token) {
- case 'usecmap':
- error('usecmap is not implemented');
- break;
-
- case 'beginbfchar':
- case 'beginbfrange':
- case 'begincidchar':
- case 'begincidrange':
- token = '';
- tokens = [];
- break;
-
- case 'endcidrange':
- case 'endbfrange':
- for (var j = 0, jj = tokens.length; j < jj; j += 3) {
- var startRange = tokens[j];
- var endRange = tokens[j + 1];
- var code = tokens[j + 2];
- while (startRange < endRange) {
- var mapping = map[startRange] || {};
- mapping.unicode = code++;
- map[startRange] = mapping;
- ++startRange;
- }
- }
- break;
-
- case 'endcidchar':
- case 'endbfchar':
- for (var j = 0, jj = tokens.length; j < jj; j += 2) {
- var index = tokens[j];
- var code = tokens[j + 1];
- var mapping = map[index] || {};
- mapping.unicode = code;
- map[index] = mapping;
- }
- break;
-
- case '':
- break;
-
- default:
- if (token[0] >= '0' && token[0] <= '9')
- token = parseInt(token, 10); // a number
- tokens.push(token);
- token = '';
- }
- switch (byte) {
- case 0x5B:
- // begin list parsing
- tokens.push(beginArrayToken);
- break;
- case 0x5D:
- // collect array items
- var items = [], item;
- while (tokens.length &&
- (item = tokens.pop()) != beginArrayToken)
- items.unshift(item);
- tokens.push(items);
- break;
- }
- } else if (byte == 0x3E) {
- if (token.length) {
- // parsing hex number
- tokens.push(parseInt(token, 16));
- token = '';
- }
- } else {
- token += String.fromCharCode(byte);
- }
+ glyphsWidths = metrics.widths;
+ defaultWidth = metrics.defaultWidth;
}
}
}
- return glyphs;
+
+ properties.defaultWidth = defaultWidth;
+ properties.widths = glyphsWidths;
},
- getBaseFontMetricsAndMap: function getBaseFontMetricsAndMap(name) {
- var map = {};
- if (/^Symbol(-?(Bold|Italic))*$/.test(name)) {
- // special case for symbols
- var encoding = Encodings.symbolsEncoding.slice();
- for (var i = 0, n = encoding.length, j; i < n; i++) {
- j = encoding[i];
- if (!j)
- continue;
- map[i] = GlyphsUnicode[j] || 0;
- }
- }
-
- var defaultWidth = 0;
- var widths = Metrics[stdFontMap[name] || name];
- if (isNum(widths)) {
- defaultWidth = widths;
- widths = null;
+ getBaseFontMetrics: function getBaseFontMetrics(name) {
+ var defaultWidth = 0, widths = [];
+ var glyphWidths = Metrics[stdFontMap[name] || name];
+ if (isNum(glyphWidths)) {
+ defaultWidth = glyphWidths;
+ } else {
+ widths = glyphWidths;
}
return {
defaultWidth: defaultWidth,
- widths: widths || [],
- map: map
+ widths: widths
};
},
@@ -756,6 +716,7 @@ var PartialEvaluator = (function partialEvaluator() {
assertWellFormed(isName(type), 'invalid font Subtype');
composite = true;
}
+ var maxCharIndex = composite ? 0xFFFF : 0xFF;
var descriptor = xref.fetchIfRef(dict.get('FontDescriptor'));
if (!descriptor) {
@@ -774,18 +735,16 @@ var PartialEvaluator = (function partialEvaluator() {
// Using base font name as a font name.
baseFontName = baseFontName.name.replace(/[,_]/g, '-');
- var metricsAndMap = this.getBaseFontMetricsAndMap(baseFontName);
+ var metrics = this.getBaseFontMetrics(baseFontName);
var properties = {
type: type.name,
- encoding: metricsAndMap.map,
- differences: [],
- widths: metricsAndMap.widths,
- defaultWidth: metricsAndMap.defaultWidth,
+ widths: metrics.widths,
+ defaultWidth: metrics.defaultWidth,
firstChar: 0,
- lastChar: 256
+ lastChar: maxCharIndex
};
- this.extractEncoding(dict, xref, properties);
+ this.extractDataStructures(dict, dict, xref, properties);
return {
name: baseFontName,
@@ -802,27 +761,7 @@ var PartialEvaluator = (function partialEvaluator() {
// TODO Fill the width array depending on which of the base font this is
// a variant.
var firstChar = xref.fetchIfRef(dict.get('FirstChar')) || 0;
- var lastChar = xref.fetchIfRef(dict.get('LastChar')) || 256;
- var defaultWidth = 0;
- var glyphWidths = {};
- var encoding = {};
- var widths = xref.fetchIfRef(dict.get('Widths'));
- if (widths) {
- for (var i = 0, j = firstChar, ii = widths.length; i < ii; i++, j++)
- glyphWidths[j] = widths[i];
- defaultWidth = parseFloat(descriptor.get('MissingWidth')) || 0;
- } else {
- // Trying get the BaseFont metrics (see comment above).
- var baseFontName = dict.get('BaseFont');
- if (isName(baseFontName)) {
- var metricsAndMap = this.getBaseFontMetricsAndMap(baseFontName.name);
-
- glyphWidths = metricsAndMap.widths;
- defaultWidth = metricsAndMap.defaultWidth;
- encoding = metricsAndMap.map;
- }
- }
-
+ var lastChar = xref.fetchIfRef(dict.get('LastChar')) || maxCharIndex;
var fontName = xref.fetchIfRef(descriptor.get('FontName'));
assertWellFormed(isName(fontName), 'invalid font name');
@@ -854,34 +793,30 @@ var PartialEvaluator = (function partialEvaluator() {
fixedPitch: false,
fontMatrix: dict.get('FontMatrix') || IDENTITY_MATRIX,
firstChar: firstChar || 0,
- lastChar: lastChar || 256,
+ lastChar: lastChar || maxCharIndex,
bbox: descriptor.get('FontBBox'),
ascent: descriptor.get('Ascent'),
descent: descriptor.get('Descent'),
xHeight: descriptor.get('XHeight'),
capHeight: descriptor.get('CapHeight'),
- defaultWidth: defaultWidth,
flags: descriptor.get('Flags'),
italicAngle: descriptor.get('ItalicAngle'),
- differences: [],
- widths: glyphWidths,
- encoding: encoding,
coded: false
};
- properties.glyphs = this.extractEncoding(dict, xref, properties);
+ this.extractWidths(dict, xref, descriptor, properties);
+ this.extractDataStructures(dict, baseDict, xref, properties);
if (type.name === 'Type3') {
properties.coded = true;
var charProcs = xref.fetchIfRef(dict.get('CharProcs'));
var fontResources = xref.fetchIfRef(dict.get('Resources')) || resources;
properties.resources = fontResources;
+ properties.charProcIRQueues = {};
for (var key in charProcs.map) {
var glyphStream = xref.fetchIfRef(charProcs.map[key]);
var queueObj = {};
- properties.glyphs[key].IRQueue = this.getIRQueue(glyphStream,
- fontResources,
- queueObj,
- dependency);
+ properties.charProcIRQueues[key] =
+ this.getIRQueue(glyphStream, fontResources, queueObj, dependency);
}
}
diff --git a/src/fonts.js b/src/fonts.js
index ca02bb020..116bb4dfc 100644
--- a/src/fonts.js
+++ b/src/fonts.js
@@ -672,6 +672,44 @@ var UnicodeRanges = [
{ 'begin': 0x1F030, 'end': 0x1F09F } // Domino Tiles
];
+var MacStandardGlyphOrdering = [
+ '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl',
+ 'numbersign', 'dollar', 'percent', 'ampersand', 'quotesingle', 'parenleft',
+ 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash',
+ 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
+ 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question', 'at',
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
+ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft',
+ 'backslash', 'bracketright', 'asciicircum', 'underscore', 'grave', 'a', 'b',
+ 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q',
+ 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright',
+ 'asciitilde', 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde',
+ 'Odieresis', 'Udieresis', 'aacute', 'agrave', 'acircumflex', 'adieresis',
+ 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis',
+ 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve',
+ 'ocircumflex', 'odieresis', 'otilde', 'uacute', 'ugrave', 'ucircumflex',
+ 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', 'bullet',
+ 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute',
+ 'dieresis', 'notequal', 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal',
+ 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', 'product', 'pi',
+ 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash',
+ 'questiondown', 'exclamdown', 'logicalnot', 'radical', 'florin',
+ 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', 'ellipsis',
+ 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash',
+ 'emdash', 'quotedblleft', 'quotedblright', 'quoteleft', 'quoteright',
+ 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', 'currency',
+ 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered',
+ 'quotesinglbase', 'quotedblbase', 'perthousand', 'Acircumflex',
+ 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex',
+ 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute',
+ 'Ucircumflex', 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron',
+ 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', 'ogonek', 'caron',
+ 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar',
+ 'Eth', 'eth', 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply',
+ 'onesuperior', 'twosuperior', 'threesuperior', 'onehalf', 'onequarter',
+ 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla',
+ 'scedilla', 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat'];
+
function getUnicodeRangeFor(value) {
for (var i = 0, ii = UnicodeRanges.length; i < ii; i++) {
var range = UnicodeRanges[i];
@@ -681,6 +719,22 @@ function getUnicodeRangeFor(value) {
return -1;
}
+function adaptUnicode(unicode) {
+ return (unicode <= 0x1F || (unicode >= 127 && unicode < kSizeOfGlyphArea)) ?
+ unicode + kCmapGlyphOffset : unicode;
+}
+
+function isAdaptedUnicode(unicode) {
+ return unicode >= kCmapGlyphOffset &&
+ unicode < kCmapGlyphOffset + kSizeOfGlyphArea;
+}
+
+function isSpecialUnicode(unicode) {
+ return (unicode <= 0x1F || (unicode >= 127 && unicode < kSizeOfGlyphArea)) ||
+ unicode >= kCmapGlyphOffset &&
+ unicode < kCmapGlyphOffset + kSizeOfGlyphArea;
+}
+
/**
* 'Font' is the class the outside world should use, it encapsulate all the font
* decoding logics whatever type it is (assuming the font type is supported).
@@ -692,8 +746,8 @@ function getUnicodeRangeFor(value) {
var Font = (function Font() {
var constructor = function font_constructor(name, file, properties) {
this.name = name;
- this.encoding = properties.encoding;
this.coded = properties.coded;
+ this.charProcIRQueues = properties.charProcIRQueues;
this.resources = properties.resources;
this.sizes = [];
@@ -702,6 +756,9 @@ var Font = (function Font() {
names = names.split(/[-,_]/g)[0];
this.serif = serifFonts[names] || (name.search(/serif/gi) != -1);
+ var type = properties.type;
+ this.type = type;
+
// If the font is to be ignored, register it like an already loaded font
// to avoid the cost of waiting for it be be loaded by the platform.
if (properties.ignore) {
@@ -709,12 +766,20 @@ var Font = (function Font() {
this.loading = false;
return;
}
+
+ this.differences = properties.differences;
+ this.widths = properties.widths;
+ this.defaultWidth = properties.defaultWidth;
+ this.composite = properties.composite;
+ this.toUnicode = properties.toUnicode;
+ this.hasEncoding = properties.hasEncoding;
+
this.fontMatrix = properties.fontMatrix;
if (properties.type == 'Type3')
return;
- // Trying to fix encoding using glyph widths and CIDSystemInfo.
- this.fixWidths(properties);
+ // Trying to fix encoding using glyph CIDSystemInfo.
+ this.loadCidToUnicode(properties);
if (!file) {
// The file data is not specified. Trying to fix the font name
@@ -730,15 +795,14 @@ var Font = (function Font() {
// name ArialBlack for example will be replaced by Helvetica.
this.black = (name.search(/Black/g) != -1);
- this.defaultWidth = properties.defaultWidth;
+ this.encoding = properties.baseEncoding;
+ this.noUnicodeAdaptation = true;
this.loadedName = fontName.split('-')[0];
- this.composite = properties.composite;
this.loading = false;
return;
}
var data;
- var type = properties.type;
switch (type) {
case 'Type1':
case 'CIDFontType0':
@@ -767,11 +831,10 @@ var Font = (function Font() {
}
this.data = data;
- this.type = type;
this.fontMatrix = properties.fontMatrix;
- this.defaultWidth = properties.defaultWidth;
+ this.encoding = properties.baseEncoding;
+ this.hasShortCmap = properties.hasShortCmap;
this.loadedName = getUniqueName();
- this.composite = properties.composite;
this.loading = true;
};
@@ -987,7 +1050,7 @@ var Font = (function Font() {
format314);
};
- function createOS2Table(properties, override) {
+ function createOS2Table(properties, charstrings, override) {
override = override || {
unitsPerEm: 0,
yMax: 0,
@@ -1004,26 +1067,31 @@ var Font = (function Font() {
var firstCharIndex = null;
var lastCharIndex = 0;
- var encoding = properties.encoding;
- for (var index in encoding) {
- var code = encoding[index].unicode;
- if (firstCharIndex > code || !firstCharIndex)
- firstCharIndex = code;
- if (lastCharIndex < code)
- lastCharIndex = code;
+ if (charstrings) {
+ for (var i = 0; i < charstrings.length; ++i) {
+ var code = charstrings[i].unicode;
+ if (firstCharIndex > code || !firstCharIndex)
+ firstCharIndex = code;
+ if (lastCharIndex < code)
+ lastCharIndex = code;
- var position = getUnicodeRangeFor(code);
- if (position < 32) {
- ulUnicodeRange1 |= 1 << position;
- } else if (position < 64) {
- ulUnicodeRange2 |= 1 << position - 32;
- } else if (position < 96) {
- ulUnicodeRange3 |= 1 << position - 64;
- } else if (position < 123) {
- ulUnicodeRange4 |= 1 << position - 96;
- } else {
- error('Unicode ranges Bits > 123 are reserved for internal usage');
+ var position = getUnicodeRangeFor(code);
+ if (position < 32) {
+ ulUnicodeRange1 |= 1 << position;
+ } else if (position < 64) {
+ ulUnicodeRange2 |= 1 << position - 32;
+ } else if (position < 96) {
+ ulUnicodeRange3 |= 1 << position - 64;
+ } else if (position < 123) {
+ ulUnicodeRange4 |= 1 << position - 96;
+ } else {
+ error('Unicode ranges Bits > 123 are reserved for internal usage');
+ }
}
+ } else {
+ // TODO
+ firstCharIndex = 0;
+ lastCharIndex = 255;
}
var unitsPerEm = override.unitsPerEm || kPDFGlyphSpaceUnits;
@@ -1208,6 +1276,29 @@ var Font = (function Font() {
};
};
+ function createGlyphNameMap(glyphs, ids, properties) {
+ var glyphNames = properties.glyphNames;
+ if (!glyphNames) {
+ properties.glyphNameMap = {};
+ return;
+ }
+ var glyphsLength = glyphs.length;
+ var glyphNameMap = {};
+ var encoding = [];
+ for (var i = 0; i < glyphsLength; ++i) {
+ var glyphName = glyphNames[ids[i]];
+ if (!glyphName)
+ continue;
+ var unicode = glyphs[i].unicode;
+ glyphNameMap[glyphName] = unicode;
+ var code = glyphs[i].code;
+ encoding[code] = glyphName;
+ }
+ properties.glyphNameMap = glyphNameMap;
+ if (!properties.hasEncoding)
+ properties.baseEncoding = encoding;
+ }
+
function replaceCMapTable(cmap, font, properties) {
var start = (font.start ? font.start : 0) + cmap.offset;
font.pos = start;
@@ -1262,7 +1353,6 @@ var Font = (function Font() {
cmap.data[i] = data.charCodeAt(i);
}
- var encoding = properties.encoding;
for (var i = 0; i < numRecords; i++) {
var table = tables[i];
font.pos = start + table.offset;
@@ -1271,29 +1361,88 @@ var Font = (function Font() {
var length = int16(font.getBytes(2));
var language = int16(font.getBytes(2));
- if (format == 4) {
- return cmap.data;
- } else if (format == 0) {
+ if (format == 0) {
// Characters below 0x20 are controls characters that are hardcoded
// into the platform so if some characters in the font are assigned
// under this limit they will not be displayed so let's rewrite the
// CMap.
var glyphs = [];
- var deltas = [];
+ var ids = [];
for (var j = 0; j < 256; j++) {
var index = font.getByte();
if (index) {
- deltas.push(index);
-
- var unicode = j + kCmapGlyphOffset;
- var mapping = encoding[j] || {};
- mapping.unicode = unicode;
- encoding[j] = mapping;
- glyphs.push({ unicode: unicode });
+ var unicode = adaptUnicode(j);
+ glyphs.push({ unicode: unicode, code: j });
+ ids.push(index);
}
}
- return cmap.data = createCMapTable(glyphs, deltas);
+ properties.hasShortCmap = true;
+
+ createGlyphNameMap(glyphs, ids, properties);
+ return cmap.data = createCMapTable(glyphs, ids);
+ } else if (format == 4) {
+ // re-creating the table in format 4 since the encoding
+ // might be changed
+ var segCount = (int16(font.getBytes(2)) >> 1);
+ font.getBytes(6); // skipping range fields
+ var segIndex, segments = [];
+ for (segIndex = 0; segIndex < segCount; segIndex++) {
+ segments.push({ end: int16(font.getBytes(2)) });
+ }
+ font.getBytes(2);
+ for (segIndex = 0; segIndex < segCount; segIndex++) {
+ segments[segIndex].start = int16(font.getBytes(2));
+ }
+
+ for (segIndex = 0; segIndex < segCount; segIndex++) {
+ segments[segIndex].delta = int16(font.getBytes(2));
+ }
+
+ var offsetsCount = 0;
+ for (segIndex = 0; segIndex < segCount; segIndex++) {
+ var segment = segments[segIndex];
+ var rangeOffset = int16(font.getBytes(2));
+ if (!rangeOffset) {
+ segment.offsetIndex = -1;
+ continue;
+ }
+
+ var offsetIndex = (rangeOffset >> 1) - (segCount - segIndex);
+ segment.offsetIndex = offsetIndex;
+ offsetsCount = Math.max(offsetsCount, offsetIndex +
+ segment.end - segment.start + 1);
+ }
+
+ var offsets = [];
+ for (var j = 0; j < offsetsCount; j++)
+ offsets.push(int16(font.getBytes(2)));
+
+ var glyphs = [], ids = [];
+
+ for (segIndex = 0; segIndex < segCount; segIndex++) {
+ var segment = segments[segIndex];
+ var start = segment.start, end = segment.end;
+ var delta = segment.delta, offsetIndex = segment.offsetIndex;
+
+ for (var j = start; j <= end; j++) {
+ if (j == 0xFFFF)
+ continue;
+
+ var glyphCode = offsetIndex < 0 ? j :
+ offsets[offsetIndex + j - start];
+ glyphCode = (glyphCode + delta) & 0xFFFF;
+ if (glyphCode == 0 || isAdaptedUnicode(j))
+ continue;
+
+ var unicode = adaptUnicode(j);
+ glyphs.push({ unicode: unicode, code: j });
+ ids.push(glyphCode);
+ }
+ }
+
+ createGlyphNameMap(glyphs, ids, properties);
+ return cmap.data = createCMapTable(glyphs, ids);
} else if (format == 6) {
// Format 6 is a 2-bytes dense mapping, which means the font data
// lives glue together even if they are pretty far in the unicode
@@ -1305,15 +1454,18 @@ var Font = (function Font() {
var glyphs = [];
var ids = [];
- for (var j = 0; j < firstCode + entryCount; j++) {
- var code = (j >= firstCode) ? int16(font.getBytes(2)) : j;
- glyphs.push({ unicode: j + kCmapGlyphOffset });
- ids.push(code);
+ for (var j = 0; j < entryCount; j++) {
+ var glyphCode = int16(font.getBytes(2));
+ var code = firstCode + j;
+ if (isAdaptedUnicode(glyphCode))
+ continue;
- var mapping = encoding[j] || {};
- mapping.unicode = glyphs[j].unicode;
- encoding[j] = mapping;
+ var unicode = adaptUnicode(code);
+ glyphs.push({ unicode: unicode, code: code });
+ ids.push(glyphCode);
}
+
+ createGlyphNameMap(glyphs, ids, properties);
return cmap.data = createCMapTable(glyphs, ids);
}
}
@@ -1396,6 +1548,52 @@ var Font = (function Font() {
}
}
+ function readGlyphNameMap(post, properties) {
+ var start = (font.start ? font.start : 0) + post.offset;
+ font.pos = start;
+
+ var length = post.length, end = start + length;
+ var version = int32(font.getBytes(4));
+ // skip rest to the tables
+ font.getBytes(28);
+
+ var glyphNames;
+ switch (version) {
+ case 0x00010000:
+ glyphNames = MacStandardGlyphOrdering;
+ break;
+ case 0x00020000:
+ var numGlyphs = int16(font.getBytes(2));
+ var glyphNameIndexes = [];
+ for (var i = 0; i < numGlyphs; ++i)
+ glyphNameIndexes.push(int16(font.getBytes(2)));
+ var customNames = [];
+ while (font.pos < end) {
+ var stringLength = font.getByte();
+ var string = '';
+ for (var i = 0; i < stringLength; ++i)
+ string += font.getChar();
+ customNames.push(string);
+ }
+ glyphNames = [];
+ for (var i = 0; i < numGlyphs; ++i) {
+ var j = glyphNameIndexes[i];
+ if (j < 258) {
+ glyphNames.push(MacStandardGlyphOrdering[j]);
+ continue;
+ }
+ glyphNames.push(customNames[j - 258]);
+ }
+ break;
+ case 0x00030000:
+ break;
+ default:
+ warn('Unknown/unsupported post table version ' + version);
+ break;
+ }
+ properties.glyphNames = glyphNames;
+ }
+
// Check that required tables are present
var requiredTables = ['OS/2', 'cmap', 'head', 'hhea',
'hmtx', 'maxp', 'name', 'post'];
@@ -1403,7 +1601,7 @@ var Font = (function Font() {
var header = readOpenTypeHeader(font);
var numTables = header.numTables;
- var cmap, maxp, hhea, hmtx, vhea, vmtx, head, loca, glyf;
+ var cmap, post, maxp, hhea, hmtx, vhea, vmtx, head, loca, glyf;
var tables = [];
for (var i = 0; i < numTables; i++) {
var table = readTableEntry(font);
@@ -1411,6 +1609,8 @@ var Font = (function Font() {
if (index != -1) {
if (table.tag == 'cmap')
cmap = table;
+ else if (table.tag == 'post')
+ post = table;
else if (table.tag == 'maxp')
maxp = table;
else if (table.tag == 'hhea')
@@ -1461,7 +1661,7 @@ var Font = (function Font() {
tables.push({
tag: 'OS/2',
- data: stringToArray(createOS2Table(properties, override))
+ data: stringToArray(createOS2Table(properties, null, override))
});
}
@@ -1486,6 +1686,11 @@ var Font = (function Font() {
hhea.data[11] = 0xFF;
}
+ // The 'post' table has glyphs names.
+ if (post) {
+ readGlyphNameMap(post, properties);
+ }
+
// Replace the old CMAP table with a shiny new one
if (properties.type == 'CIDFontType2') {
// Type2 composite fonts map characters directly to glyphs so the cmap
@@ -1503,28 +1708,17 @@ var Font = (function Font() {
tables.push(cmap);
}
- var encoding = properties.encoding, i;
-
- // offsetting glyphs to avoid problematic unicode ranges
- for (i in encoding) {
- if (encoding.hasOwnProperty(i)) {
- var unicode = encoding[i].unicode;
- if (unicode <= 0x1f ||
- (unicode >= 127 && unicode < kSizeOfGlyphArea))
- encoding[i].unicode += kCmapGlyphOffset;
- }
- }
-
var glyphs = [];
for (i = 1; i < numGlyphs; i++) {
- glyphs.push({
- unicode: i <= 0x1f || (i >= 127 && i < kSizeOfGlyphArea) ?
- i + kCmapGlyphOffset : i
- });
+ if (isAdaptedUnicode(i))
+ continue;
+
+ glyphs.push({ unicode: adaptUnicode(i) });
}
cmap.data = createCMapTable(glyphs);
} else {
replaceCMapTable(cmap, font, properties);
+ this.glyphNameMap = properties.glyphNameMap;
}
// Rewrite the 'post' table if needed
@@ -1598,12 +1792,29 @@ var Font = (function Font() {
var charstrings = font.charstrings;
properties.fixedPitch = isFixedPitch(charstrings);
+ var glyphNameMap = {};
+ for (var i = 0; i < charstrings.length; ++i) {
+ var charstring = charstrings[i];
+ glyphNameMap[charstring.glyph] = charstring.unicode;
+ }
+ this.glyphNameMap = glyphNameMap;
+
+ if (!properties.hasEncoding && (properties.subtype == 'Type1C' ||
+ properties.subtype == 'CIDFontType0C')) {
+ var encoding = [];
+ for (var i = 0; i < charstrings.length; ++i) {
+ var charstring = charstrings[i];
+ encoding[charstring.code] = charstring.glyph;
+ }
+ properties.baseEncoding = encoding;
+ }
+
var fields = {
// PostScript Font Program
'CFF ': font.data,
// OS/2 and Windows Specific metrics
- 'OS/2': stringToArray(createOS2Table(properties)),
+ 'OS/2': stringToArray(createOS2Table(properties, charstrings)),
// Character to glyphs mapping
'cmap': createCMapTable(charstrings.slice(),
@@ -1657,9 +1868,8 @@ var Font = (function Font() {
// Horizontal metrics
'hmtx': (function fontFieldsHmtx() {
var hmtx = '\x00\x00\x00\x00'; // Fake .notdef
- for (var i = 0, ii = charstrings.length; i < ii; i++) {
+ for (var i = 0, ii = charstrings.length; i < ii; i++)
hmtx += string16(charstrings[i].width) + string16(0);
- }
return stringToArray(hmtx);
})(),
@@ -1688,82 +1898,48 @@ var Font = (function Font() {
return stringToArray(otf.file);
},
- fixWidths: function font_fixWidths(properties) {
- if (properties.type !== 'CIDFontType0' &&
- properties.type !== 'CIDFontType2')
- return;
-
- var encoding = properties.encoding;
- if (encoding[0])
+ loadCidToUnicode: function font_loadCidToUnicode(properties) {
+ if (properties.cidToGidMap) {
+ this.cidToUnicode = properties.cidToGidMap;
return;
- var glyphsWidths = properties.widths;
- if (!glyphsWidths)
+ }
+
+ if (!properties.cidSystemInfo)
return;
- var defaultWidth = properties.defaultWidth;
+ var cidToUnicodeMap = [];
+ this.cidToUnicode = cidToUnicodeMap;
+
var cidSystemInfo = properties.cidSystemInfo;
var cidToUnicode;
if (cidSystemInfo) {
cidToUnicode = CIDToUnicodeMaps[
cidSystemInfo.registry + '-' + cidSystemInfo.ordering];
}
- if (!cidToUnicode) {
- // the font is directly characters to glyphs with no encoding
- // so create an identity encoding
- for (i = 0; i < 0xD800; i++) {
- var width = glyphsWidths[i];
- encoding[i] = {
- unicode: i,
- width: isNum(width) ? width : defaultWidth
- };
- }
- // skipping surrogates + 256-user defined
- for (i = 0xE100; i <= 0xFFFF; i++) {
- var width = glyphsWidths[i];
- encoding[i] = {
- unicode: i,
- width: isNum(width) ? width : defaultWidth
- };
- }
- return;
- }
- encoding[0] = { unicode: 0, width: 0 };
- var glyph = 1, i, j, k, cidLength, ii;
+ if (!cidToUnicode)
+ return; // identity encoding
+
+ var glyph = 1, i, j, k, ii;
for (i = 0, ii = cidToUnicode.length; i < ii; ++i) {
var unicode = cidToUnicode[i];
- var width;
if (isArray(unicode)) {
var length = unicode.length;
- width = glyphsWidths[glyph];
- for (j = 0; j < length; j++) {
- k = unicode[j];
- encoding[k] = {
- unicode: k,
- width: isNum(width) ? width : defaultWidth
- };
- }
+ for (j = 0; j < length; j++)
+ cidToUnicodeMap[unicode[j]] = glyph;
glyph++;
} else if (typeof unicode === 'object') {
var fillLength = unicode.f;
if (fillLength) {
k = unicode.c;
for (j = 0; j < fillLength; ++j) {
- width = glyphsWidths[glyph++];
- encoding[k] = {
- unicode: k,
- width: isNum(width) ? width : defaultWidth
- };
+ cidToUnicodeMap[k] = glyph++;
k++;
}
} else
glyph += unicode.s;
} else if (unicode) {
- width = glyphsWidths[glyph++];
- encoding[unicode] = {
- unicode: unicode,
- width: isNum(width) ? width : defaultWidth
- };
+ cidToUnicodeMap[unicode] = glyph++;
} else
glyph++;
}
@@ -1797,6 +1973,79 @@ var Font = (function Font() {
return rule;
},
+ charToGlyph: function fonts_charToGlyph(charcode) {
+ var unicode, width, codeIRQueue;
+
+ var width = this.widths[charcode];
+
+ switch (this.type) {
+ case 'CIDFontType0':
+ if (this.noUnicodeAdaptation) {
+ width = this.widths[this.cidToUnicode[charcode]];
+ unicode = charcode;
+ break;
+ }
+ unicode = adaptUnicode(this.cidToUnicode[charcode] || charcode);
+ break;
+ case 'CIDFontType2':
+ if (this.noUnicodeAdaptation) {
+ width = this.widths[this.cidToUnicode[charcode]];
+ unicode = charcode;
+ break;
+ }
+ unicode = adaptUnicode(this.cidToUnicode[charcode] || charcode);
+ break;
+ case 'Type1':
+ var glyphName = this.differences[charcode] || this.encoding[charcode];
+ if (this.noUnicodeAdaptation) {
+ if (!isNum(width))
+ width = this.widths[glyphName];
+ unicode = GlyphsUnicode[glyphName] || charcode;
+ break;
+ }
+ unicode = this.glyphNameMap[glyphName] ||
+ adaptUnicode(GlyphsUnicode[glyphName] || charcode);
+ break;
+ case 'Type3':
+ var glyphName = this.differences[charcode] || this.encoding[charcode];
+ codeIRQueue = this.charProcIRQueues[glyphName];
+ unicode = charcode;
+ break;
+ case 'TrueType':
+ var glyphName = this.differences[charcode] || this.encoding[charcode];
+ if (!glyphName)
+ glyphName = Encodings.StandardEncoding[charcode];
+ if (!isNum(width))
+ width = this.widths[glyphName];
+ if (this.noUnicodeAdaptation) {
+ unicode = GlyphsUnicode[glyphName] || charcode;
+ break;
+ }
+ if (!this.hasEncoding) {
+ unicode = adaptUnicode(charcode);
+ break;
+ }
+ if (this.hasShortCmap) {
+ var j = Encodings.MacRomanEncoding.indexOf(glyphName);
+ unicode = j >= 0 && !isSpecialUnicode(j) ? j :
+ this.glyphNameMap[glyphName];
+ } else {
+ unicode = glyphName in GlyphsUnicode ?
+ adaptUnicode(GlyphsUnicode[glyphName]) :
+ this.glyphNameMap[glyphName];
+ }
+ break;
+ default:
+ warn('Unsupported font type: ' + this.type);
+ break;
+ }
+ return {
+ unicode: unicode,
+ width: isNum(width) ? width : this.defaultWidth,
+ codeIRQueue: codeIRQueue
+ };
+ },
+
charsToGlyphs: function fonts_chars2Glyphs(chars) {
var charsCache = this.charsCache;
var glyphs;
@@ -1812,11 +2061,6 @@ var Font = (function Font() {
if (!charsCache)
charsCache = this.charsCache = Object.create(null);
- // translate the string using the font's encoding
- var encoding = this.encoding;
- if (!encoding)
- return chars;
-
glyphs = [];
if (this.composite) {
@@ -1828,14 +2072,7 @@ var Font = (function Font() {
// loop should never end on the last byte
for (var i = 0; i < length; i++) {
var charcode = int16([chars.charCodeAt(i++), chars.charCodeAt(i)]);
- var glyph = encoding[charcode];
- if ('undefined' == typeof(glyph)) {
- warn('Unencoded charcode ' + charcode);
- glyph = {
- unicode: charcode,
- width: this.defaultWidth
- };
- }
+ var glyph = this.charToGlyph(charcode);
glyphs.push(glyph);
// placing null after each word break charcode (ASCII SPACE)
if (charcode == 0x20)
@@ -1845,14 +2082,7 @@ var Font = (function Font() {
else {
for (var i = 0, ii = chars.length; i < ii; ++i) {
var charcode = chars.charCodeAt(i);
- var glyph = encoding[charcode];
- if ('undefined' == typeof(glyph)) {
- warn('Unencoded charcode ' + charcode);
- glyph = {
- unicode: charcode,
- width: this.defaultWidth
- };
- }
+ var glyph = this.charToGlyph(charcode);
glyphs.push(glyph);
if (charcode == 0x20)
glyphs.push(null);
@@ -2106,6 +2336,17 @@ var Type1Parser = function type1Parser() {
warn('Support for Type1 command ' + value +
' (' + escape + ') is not implemented in charstring: ' +
charstring);
+ if (value == 12) {
+ // we know how to ignore only some the Type1 commands
+ switch (escape) {
+ case 7:
+ charstring.push('drop', 'drop', 'drop', 'drop');
+ continue;
+ case 8:
+ charstring.push('drop');
+ continue;
+ }
+ }
}
value = command;
@@ -2326,24 +2567,30 @@ var Type1Parser = function type1Parser() {
properties.fontMatrix = matrix;
break;
case '/Encoding':
- var size = parseInt(getToken(), 10);
- getToken(); // read in 'array'
+ var encodingArg = getToken();
+ var encoding;
+ if (!/^\d+$/.test(encodingArg)) {
+ // encoding name is specified
+ encoding = Encodings[encodingArg];
+ } else {
+ encoding = [];
+ var size = parseInt(encodingArg, 10);
+ getToken(); // read in 'array'
- for (var j = 0; j < size; j++) {
- var token = getToken();
- if (token == 'dup') {
- var index = parseInt(getToken(), 10);
- var glyph = getToken();
-
- if ('undefined' == typeof(properties.differences[index])) {
- var mapping = properties.encoding[index] || {};
- mapping.unicode = GlyphsUnicode[glyph] || index;
- properties.glyphs[glyph] = properties.encoding[index] =
- mapping;
+ for (var j = 0; j < size; j++) {
+ var token = getToken();
+ if (token == 'dup') {
+ var index = parseInt(getToken(), 10);
+ var glyph = getToken();
+ encoding[index] = glyph;
+ getToken(); // read the in 'put'
}
- getToken(); // read the in 'put'
}
}
+ if (!properties.hasEncoding && encoding) {
+ properties.baseEncoding = encoding;
+ break;
+ }
break;
}
token = '';
@@ -2486,45 +2733,51 @@ CFF.prototype = {
},
encodeNumber: function cff_encodeNumber(value) {
+ // some of the fonts has ouf-of-range values
+ // they are just arithmetic overflows
+ // make sanitizer happy
+ value |= 0;
if (value >= -32768 && value <= 32767) {
return '\x1c' +
String.fromCharCode((value >> 8) & 0xFF) +
String.fromCharCode(value & 0xFF);
- } else if (value >= (-2147483648) && value <= 2147483647) {
+ } else {
return '\x1d' +
String.fromCharCode((value >> 24) & 0xFF) +
String.fromCharCode((value >> 16) & 0xFF) +
String.fromCharCode((value >> 8) & 0xFF) +
String.fromCharCode(value & 0xFF);
}
- error('Value: ' + value + ' is not allowed');
- return null;
},
getOrderedCharStrings: function cff_getOrderedCharStrings(glyphs,
properties) {
var charstrings = [];
- var missings = [];
-
- for (var i = 0, ii = glyphs.length; i < ii; i++) {
- var glyph = glyphs[i];
- var mapping = properties.glyphs[glyph.glyph];
- if (!mapping) {
- if (glyph.glyph != '.notdef')
- missings.push(glyph.glyph);
- } else {
- charstrings.push({
- glyph: glyph.glyph,
- unicode: mapping.unicode,
- charstring: glyph.data,
- width: glyph.width,
- lsb: glyph.lsb
- });
- }
+ var reverseMapping = {};
+ var encoding = properties.baseEncoding;
+ var i, length, glyphName;
+ for (i = 0, length = encoding.length; i < length; ++i) {
+ glyphName = encoding[i];
+ if (!glyphName || isSpecialUnicode(i))
+ continue;
+ reverseMapping[glyphName] = i;
+ }
+ reverseMapping['.notdef'] = 0;
+ var unusedUnicode = kCmapGlyphOffset;
+ for (i = 0, length = glyphs.length; i < length; i++) {
+ var item = glyphs[i];
+ var glyphName = item.glyph;
+ var unicode = glyphName in reverseMapping ?
+ reverseMapping[glyphName] : unusedUnicode++;
+ charstrings.push({
+ glyph: glyphName,
+ unicode: unicode,
+ gid: i,
+ charstring: item.data,
+ width: item.width,
+ lsb: item.lsb
+ });
}
-
- if (missings.length)
- warn(missings + ' does not have unicode in the glyphs dictionary');
charstrings.sort(function charstrings_sort(a, b) {
return a.unicode - b.unicode;
@@ -2807,6 +3060,20 @@ var Type2CFF = (function type2CFF() {
var encoding = this.parseEncoding(topDict.Encoding, properties,
strings, charset);
+ var charset, encoding;
+ var isCIDFont = properties.subtype == 'CIDFontType0C';
+ if (isCIDFont) {
+ charset = [];
+ charset.length = charStrings.length;
+ encoding = this.parseCidMap(topDict.charset,
+ charStrings.length);
+ } else {
+ charset = this.parseCharsets(topDict.charset,
+ charStrings.length, strings);
+ encoding = this.parseEncoding(topDict.Encoding, properties,
+ strings, charset);
+ }
+
// The font sanitizer does not support CFF encoding with a
// supplement, since the encoding is not really use to map
// between gid to glyph, let's overwrite what is declared in
@@ -2863,80 +3130,46 @@ var Type2CFF = (function type2CFF() {
getCharStrings: function cff_charstrings(charsets, encoding,
privateDict, properties) {
- var defaultWidth = privateDict['defaultWidthX'];
var charstrings = [];
- var firstChar = properties.firstChar;
- var glyphMap = {};
+ var unicodeUsed = [];
+ var unassignedUnicodeItems = [];
for (var i = 0, ii = charsets.length; i < ii; i++) {
var glyph = charsets[i];
+ var encodingFound = false;
for (var charcode in encoding) {
- if (encoding[charcode] == i)
- glyphMap[glyph] = charcode | 0;
+ if (encoding[charcode] == i) {
+ var code = charcode | 0;
+ charstrings.push({
+ unicode: adaptUnicode(code),
+ code: code,
+ gid: i,
+ glyph: glyph
+ });
+ unicodeUsed[code] = true;
+ encodingFound = true;
+ break;
+ }
+ }
+ if (!encodingFound) {
+ unassignedUnicodeItems.push(i);
}
}
- var differences = properties.differences;
- for (var i = 0, ii = differences.length; i < ii; ++i) {
- var glyph = differences[i];
- if (!glyph)
- continue;
- var oldGlyph = charsets[i];
- if (oldGlyph)
- delete glyphMap[oldGlyph];
- glyphMap[differences[i]] = i;
- }
-
- var glyphs = properties.glyphs;
- for (var i = 1, ii = charsets.length; i < ii; i++) {
- var glyph = charsets[i];
- var code = glyphMap[glyph] || 0;
-
- var mapping = glyphs[code] || glyphs[glyph] || { width: defaultWidth };
- var unicode = mapping.unicode;
-
- if (unicode <= 0x1f || (unicode >= 127 && unicode <= 255))
- unicode += kCmapGlyphOffset;
-
- var width = (mapping.hasOwnProperty('width') && isNum(mapping.width)) ?
- mapping.width : defaultWidth;
- properties.encoding[code] = {
- unicode: unicode,
- width: width
- };
-
+ var nextUnusedUnicode = 0x21;
+ for (var j = 0, jj = unassignedUnicodeItems.length; j < jj; ++j) {
+ var i = unassignedUnicodeItems[j];
+ // giving unicode value anyway
+ while (unicodeUsed[nextUnusedUnicode])
+ nextUnusedUnicode++;
+ var code = nextUnusedUnicode++;
charstrings.push({
- unicode: unicode,
- width: width,
+ unicode: adaptUnicode(code),
code: code,
- gid: i
+ gid: i,
+ glyph: charsets[i]
});
}
- // sort the array by the unicode value
- charstrings.sort(function type2CFFGetCharStringsSort(a, b) {
- return a.unicode - b.unicode;
- });
-
- // remove duplicates -- they might appear during selection:
- // properties.glyphs[code] || properties.glyphs[glyph]
- var nextUnusedUnicode = kCmapGlyphOffset + 0x0020;
- var lastUnicode = charstrings[0].unicode, wasModified = false;
- for (var i = 1, ii = charstrings.length; i < ii; ++i) {
- if (lastUnicode != charstrings[i].unicode) {
- lastUnicode = charstrings[i].unicode;
- continue;
- }
- // duplicate found -- keeping the item that has
- // different code and unicode, that one created
- // as result of modification of the base encoding
- var duplicateIndex =
- charstrings[i].unicode == charstrings[i].code ? i : i - 1;
- charstrings[duplicateIndex].unicode = nextUnusedUnicode++;
- wasModified = true;
- }
- if (!wasModified)
- return charstrings;
-
// sort the array by the unicode value (again)
charstrings.sort(function type2CFFGetCharStringsSort(a, b) {
return a.unicode - b.unicode;
@@ -2964,8 +3197,8 @@ var Type2CFF = (function type2CFF() {
if (pos == 0 || pos == 1) {
var gid = 1;
- var baseEncoding = pos ? Encodings.ExpertEncoding.slice() :
- Encodings.StandardEncoding.slice();
+ var baseEncoding = pos ? Encodings.ExpertEncoding :
+ Encodings.StandardEncoding;
for (var i = 0, ii = charset.length; i < ii; i++) {
var index = baseEncoding.indexOf(charset[i]);
if (index != -1)
@@ -2985,8 +3218,8 @@ var Type2CFF = (function type2CFF() {
var gid = 1;
for (var i = 0; i < rangesCount; i++) {
var start = bytes[pos++];
- var count = bytes[pos++];
- for (var j = start; j <= start + count; j++)
+ var left = bytes[pos++];
+ for (var j = start; j <= start + left; j++)
encoding[j] = gid++;
}
break;
@@ -3047,6 +3280,46 @@ var Type2CFF = (function type2CFF() {
}
return charset;
},
+
+ parseCidMap: function cff_parsecharsets(pos, length) {
+ var bytes = this.bytes;
+ var format = bytes[pos++];
+
+ var encoding = {};
+ var map = {encoding: encoding};
+
+ encoding[0] = 0;
+
+ var gid = 1;
+ switch (format) {
+ case 0:
+ while (gid < length) {
+ var cid = (bytes[pos++] << 8) | bytes[pos++];
+ encoding[cid] = gid++;
+ }
+ break;
+ case 1:
+ while (gid < length) {
+ var cid = (bytes[pos++] << 8) | bytes[pos++];
+ var count = bytes[pos++];
+ for (var i = 0; i <= count; i++)
+ encoding[cid++] = gid++;
+ }
+ break;
+ case 2:
+ while (gid < length) {
+ var cid = (bytes[pos++] << 8) | bytes[pos++];
+ var count = (bytes[pos++] << 8) | bytes[pos++];
+ for (var i = 0; i <= count; i++)
+ encoding[cid++] = gid++;
+ }
+ break;
+ default:
+ error('Unknown charset format');
+ }
+ return map;
+ },
+
getPrivDict: function cff_getprivdict(baseDict, strings) {
var dict = {};
@@ -3108,6 +3381,17 @@ var Type2CFF = (function type2CFF() {
case 18:
dict['Private'] = value;
break;
+ case 3102:
+ case 3103:
+ case 3104:
+ case 3105:
+ case 3106:
+ case 3107:
+ case 3108:
+ case 3109:
+ case 3110:
+ dict['cidOperatorPresent'] = true;
+ break;
default:
TODO('interpret top dict key');
}
@@ -3220,6 +3504,15 @@ var Type2CFF = (function type2CFF() {
}
var b = (b << 8) | op;
}
+ if (!operands.length && b == 8 &&
+ dict[pos + 1] == 9) {
+ // no operands for FamilyBlues, removing the key
+ // and next one is FamilyOtherBlues - skipping them
+ // also replacing FamilyBlues to pass sanitizer
+ dict[pos] = 139;
+ pos += 2;
+ continue;
+ }
entries.push([b, operands]);
operands = [];
++pos;
diff --git a/src/function.js b/src/function.js
index 80c5a5460..ef24736c1 100644
--- a/src/function.js
+++ b/src/function.js
@@ -20,6 +20,8 @@ var PDFFunction = (function pdfFunction() {
var array = [];
var codeSize = 0;
var codeBuf = 0;
+ // 32 is a valid bps so shifting won't work
+ var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1);
var strBytes = str.getBytes((length * bps + 7) / 8);
var strIdx = 0;
@@ -30,7 +32,7 @@ var PDFFunction = (function pdfFunction() {
codeSize += 8;
}
codeSize -= bps;
- array.push(codeBuf >> codeSize);
+ array.push((codeBuf >> codeSize) * sampleMul);
codeBuf &= (1 << codeSize) - 1;
}
return array;
@@ -76,6 +78,17 @@ var PDFFunction = (function pdfFunction() {
},
constructSampled: function pdfFunctionConstructSampled(str, dict) {
+ function toMultiArray(arr) {
+ var inputLength = arr.length;
+ var outputLength = arr.length / 2;
+ var out = new Array(outputLength);
+ var index = 0;
+ for (var i = 0; i < inputLength; i += 2) {
+ out[index] = [arr[i], arr[i + 1]];
+ ++index;
+ }
+ return out;
+ }
var domain = dict.get('Domain');
var range = dict.get('Range');
@@ -85,9 +98,8 @@ var PDFFunction = (function pdfFunction() {
var inputSize = domain.length / 2;
var outputSize = range.length / 2;
- if (inputSize != 1)
- error('No support for multi-variable inputs to functions: ' +
- inputSize);
+ domain = toMultiArray(domain);
+ range = toMultiArray(range);
var size = dict.get('Size');
var bps = dict.get('BitsPerSample');
@@ -105,15 +117,36 @@ var PDFFunction = (function pdfFunction() {
encode.push(size[i] - 1);
}
}
+ encode = toMultiArray(encode);
+
var decode = dict.get('Decode');
if (!decode)
decode = range;
+ else
+ decode = toMultiArray(decode);
+
+ // Precalc the multipliers
+ var inputMul = new Float64Array(inputSize);
+ for (var i = 0; i < inputSize; ++i) {
+ inputMul[i] = (encode[i][1] - encode[i][0]) /
+ (domain[i][1] - domain[i][0]);
+ }
+
+ var idxMul = new Int32Array(inputSize);
+ idxMul[0] = outputSize;
+ for (i = 1; i < inputSize; ++i) {
+ idxMul[i] = idxMul[i - 1] * size[i - 1];
+ }
+
+ var nSamples = outputSize;
+ for (i = 0; i < inputSize; ++i)
+ nSamples *= size[i];
var samples = this.getSampleArray(size, outputSize, bps, str);
return [
CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size,
- outputSize, bps, range
+ outputSize, bps, range, inputMul, idxMul, nSamples
];
},
@@ -127,64 +160,74 @@ var PDFFunction = (function pdfFunction() {
var outputSize = IR[7];
var bps = IR[8];
var range = IR[9];
+ var inputMul = IR[10];
+ var idxMul = IR[11];
+ var nSamples = IR[12];
return function constructSampledFromIRResult(args) {
- var clip = function constructSampledFromIRClip(v, min, max) {
- if (v > max)
- v = max;
- else if (v < min)
- v = min;
- return v;
- };
-
if (inputSize != args.length)
error('Incorrect number of arguments: ' + inputSize + ' != ' +
args.length);
+ // Most of the below is a port of Poppler's implementation.
+ // TODO: There's a few other ways to do multilinear interpolation such
+ // as piecewise, which is much faster but an approximation.
+ var out = new Float64Array(outputSize);
+ var x;
+ var e = new Array(inputSize);
+ var efrac0 = new Float64Array(inputSize);
+ var efrac1 = new Float64Array(inputSize);
+ var sBuf = new Float64Array(1 << inputSize);
+ var i, j, k, idx, t;
- for (var i = 0; i < inputSize; i++) {
- var i2 = i * 2;
-
- // clip to the domain
- var v = clip(args[i], domain[i2], domain[i2 + 1]);
-
- // encode
- v = encode[i2] + ((v - domain[i2]) *
- (encode[i2 + 1] - encode[i2]) /
- (domain[i2 + 1] - domain[i2]));
-
- // clip to the size
- args[i] = clip(v, 0, size[i] - 1);
+ // map input values into sample array
+ for (i = 0; i < inputSize; ++i) {
+ x = (args[i] - domain[i][0]) * inputMul[i] + encode[i][0];
+ if (x < 0) {
+ x = 0;
+ } else if (x > size[i] - 1) {
+ x = size[i] - 1;
+ }
+ e[i] = [Math.floor(x), 0];
+ if ((e[i][1] = e[i][0] + 1) >= size[i]) {
+ // this happens if in[i] = domain[i][1]
+ e[i][1] = e[i][0];
+ }
+ efrac1[i] = x - e[i][0];
+ efrac0[i] = 1 - efrac1[i];
}
- // interpolate to table
- TODO('Multi-dimensional interpolation');
- var floor = Math.floor(args[0]);
- var ceil = Math.ceil(args[0]);
- var scale = args[0] - floor;
+ // for each output, do m-linear interpolation
+ for (i = 0; i < outputSize; ++i) {
- floor *= outputSize;
- ceil *= outputSize;
-
- var output = [], v = 0;
- for (var i = 0; i < outputSize; ++i) {
- if (ceil == floor) {
- v = samples[ceil + i];
- } else {
- var low = samples[floor + i];
- var high = samples[ceil + i];
- v = low * scale + high * (1 - scale);
+ // pull 2^m values out of the sample array
+ for (j = 0; j < (1 << inputSize); ++j) {
+ idx = i;
+ for (k = 0, t = j; k < inputSize; ++k, t >>= 1) {
+ idx += idxMul[k] * (e[k][t & 1]);
+ }
+ if (idx >= 0 && idx < nSamples) {
+ sBuf[j] = samples[idx];
+ } else {
+ sBuf[j] = 0; // TODO Investigate if this is what Adobe does
+ }
}
- var i2 = i * 2;
- // decode
- v = decode[i2] + (v * (decode[i2 + 1] - decode[i2]) /
- ((1 << bps) - 1));
+ // do m sets of interpolations
+ for (j = 0, t = (1 << inputSize); j < inputSize; ++j, t >>= 1) {
+ for (k = 0; k < t; k += 2) {
+ sBuf[k >> 1] = efrac0[j] * sBuf[k] + efrac1[j] * sBuf[k + 1];
+ }
+ }
- // clip to the domain
- output.push(clip(v, range[i2], range[i2 + 1]));
+ // map output value to range
+ out[i] = (sBuf[0] * (decode[i][1] - decode[i][0]) + decode[i][0]);
+ if (out[i] < range[i][0]) {
+ out[i] = range[i][0];
+ } else if (out[i] > range[i][1]) {
+ out[i] = range[i][1];
+ }
}
-
- return output;
+ return out;
}
},
diff --git a/src/pattern.js b/src/pattern.js
index 2a31fec4a..72d13d896 100644
--- a/src/pattern.js
+++ b/src/pattern.js
@@ -19,10 +19,10 @@ var Pattern = (function patternPattern() {
constructor.shadingFromIR = function pattern_shadingFromIR(ctx, raw) {
return Shadings[raw[0]].fromIR(ctx, raw);
- }
+ };
- constructor.parseShading = function pattern_shading(shading, matrix,
- xref, res, ctx) {
+ constructor.parseShading = function pattern_shading(shading, matrix, xref,
+ res, ctx) {
var dict = isStream(shading) ? shading.dict : shading;
var type = dict.get('ShadingType');
@@ -116,17 +116,18 @@ Shadings.RadialAxial = (function radialAxialShading() {
p1 = Util.applyTransform(p1, userMatrix);
}
+ var grad;
if (type == 2)
- var grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]);
+ grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]);
else if (type == 3)
- var grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1);
+ grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1);
for (var i = 0, ii = colorStops.length; i < ii; ++i) {
var c = colorStops[i];
grad.addColorStop(c[0], c[1]);
}
return grad;
- }
+ };
constructor.prototype = {
getIR: function radialAxialShadingGetIR() {
@@ -166,7 +167,7 @@ Shadings.Dummy = (function dummyShading() {
constructor.fromIR = function dummyShadingFromIR() {
return 'hotpink';
- }
+ };
constructor.prototype = {
getIR: function dummyShadingGetIR() {
@@ -242,9 +243,9 @@ var TilingPattern = (function tilingPattern() {
graphics.transform.apply(graphics, tmpTranslate);
if (bbox && isArray(bbox) && 4 == bbox.length) {
- var bboxWidth = bbox[2] - bbox[0];
- var bboxHeight = bbox[3] - bbox[1];
- graphics.rectangle(bbox[0], bbox[1], bboxWidth, bboxHeight);
+ var bboxWidth = x1 - x0;
+ var bboxHeight = y1 - y0;
+ graphics.rectangle(x0, y0, bboxWidth, bboxHeight);
graphics.clip();
graphics.endPath();
}
@@ -264,7 +265,7 @@ var TilingPattern = (function tilingPattern() {
return [
'TilingPattern', args, codeIR, matrix, bbox, xstep, ystep, paintType
];
- }
+ };
TilingPattern.prototype = {
getPattern: function tiling_getPattern() {
diff --git a/src/pdf.js b/src/pdf.js
index 51f606548..1042a651b 100644
--- a/src/pdf.js
+++ b/src/pdf.js
@@ -7,8 +7,9 @@ var PDFJS = {};
// Use strict in our context only - users might not want it
'use strict';
+ PDFJS.build = 'PDFJSSCRIPT_BUNDLE_VER';
+
// Files are inserted below - see Makefile
/* PDFJSSCRIPT_INCLUDE_ALL */
}).call((typeof window === 'undefined') ? this : window);
-
diff --git a/test/driver.js b/test/driver.js
index 16375c30b..c11cecf56 100644
--- a/test/driver.js
+++ b/test/driver.js
@@ -7,6 +7,11 @@
'use strict';
+// Disable worker support for running test as
+// https://github.com/mozilla/pdf.js/pull/764#issuecomment-2638944
+// "firefox-bin: Fatal IO error 12 (Cannot allocate memory) on X server :1."
+PDFJS.disableWorker = true;
+
var appPath, browser, canvas, currentTaskIdx, manifest, stdout;
var inFlightRequests = 0;
@@ -51,23 +56,29 @@ function load() {
}
function cleanup() {
- var styleSheet = document.styleSheets[0];
- if (styleSheet) {
+ // Clear out all the stylesheets since a new one is created for each font.
+ while (document.styleSheets.length > 0) {
+ var styleSheet = document.styleSheets[0];
while (styleSheet.cssRules.length > 0)
styleSheet.deleteRule(0);
+ var ownerNode = styleSheet.ownerNode;
+ ownerNode.parentNode.removeChild(ownerNode);
}
var guard = document.getElementById('content-end');
var body = document.body;
while (body.lastChild !== guard)
body.removeChild(body.lastChild);
+
+ // Wipe out the link to the pdfdoc so it can be GC'ed.
+ for (var i = 0; i < manifest.length; i++) {
+ if (manifest[i].pdfDoc) {
+ manifest[i].pdfDoc.destroy();
+ delete manifest[i].pdfDoc;
+ }
+ }
}
function nextTask() {
- // If there is a pdfDoc on the last task executed, destroy it to free memory.
- if (task && task.pdfDoc) {
- task.pdfDoc.destroy();
- delete task.pdfDoc;
- }
cleanup();
if (currentTaskIdx == manifest.length) {
diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore
index 854612f83..a757acf34 100644
--- a/test/pdfs/.gitignore
+++ b/test/pdfs/.gitignore
@@ -14,4 +14,5 @@
!sizes.pdf
!close-path-bug.pdf
!alphatrans.pdf
+!devicen.pdf
!cmykjpeg.pdf
diff --git a/test/pdfs/cmykjpeg.pdf b/test/pdfs/cmykjpeg.pdf
index 2f35ef763..8e3b85a06 100644
Binary files a/test/pdfs/cmykjpeg.pdf and b/test/pdfs/cmykjpeg.pdf differ
diff --git a/test/pdfs/devicen.pdf b/test/pdfs/devicen.pdf
new file mode 100644
index 000000000..d20c884ea
Binary files /dev/null and b/test/pdfs/devicen.pdf differ
diff --git a/test/test.py b/test/test.py
index 65def5d8e..256200587 100644
--- a/test/test.py
+++ b/test/test.py
@@ -323,18 +323,18 @@ def verifyPDFs(manifestList):
if os.access(f, os.R_OK):
fileMd5 = hashlib.md5(open(f, 'rb').read()).hexdigest()
if 'md5' not in item:
- print 'ERROR: Missing md5 for file "' + f + '".',
+ print 'WARNING: Missing md5 for file "' + f + '".',
print 'Hash for current file is "' + fileMd5 + '"'
error = True
continue
md5 = item['md5']
if fileMd5 != md5:
- print 'ERROR: MD5 of file "' + f + '" does not match file.',
+ print 'WARNING: MD5 of file "' + f + '" does not match file.',
print 'Expected "' + md5 + '" computed "' + fileMd5 + '"'
error = True
continue
else:
- print 'ERROR: Unable to open file for reading "' + f + '".'
+ print 'WARNING: Unable to open file for reading "' + f + '".'
error = True
return not error
@@ -365,7 +365,8 @@ def setUp(options):
downloadLinkedPDFs(manifestList)
if not verifyPDFs(manifestList):
- raise Exception('ERROR: failed to verify pdfs.')
+ print 'Unable to verify the checksum for the files that are used for testing.'
+ print 'Please re-download the files, or adjust the MD5 checksum in the manifest for the files listed above.\n'
for b in testBrowsers:
State.taskResults[b.name] = { }
diff --git a/test/test_manifest.json b/test/test_manifest.json
index 583e1cbaa..87af30659 100644
--- a/test/test_manifest.json
+++ b/test/test_manifest.json
@@ -19,6 +19,7 @@
},
{ "id": "intelisa-load",
"file": "pdfs/intelisa.pdf",
+ "md5": "f5712097d29287a97f1278839814f682",
"md5": "f3ed5487d1afa34d8b77c0c734a95c79",
"link": true,
"rounds": 1,
@@ -187,7 +188,7 @@
},
{ "id": "f1040",
"file": "pdfs/f1040.pdf",
- "md5": "7323b50c6d28d959b8b4b92c469b2469",
+ "md5": "b59272ce19b4a0c5808c8861441b0741",
"link": true,
"rounds": 1,
"type": "load"
@@ -262,9 +263,16 @@
"rounds": 1,
"type": "eq"
},
+ { "id": "devicen",
+ "file": "pdfs/devicen.pdf",
+ "md5": "aac6a91725435d1376c6ff492dc5cb75",
+ "link": false,
+ "rounds": 1,
+ "type": "eq"
+ },
{ "id": "cmykjpeg",
"file": "pdfs/cmykjpeg.pdf",
- "md5": "8307472972ba962d86d2f60d2ced9a97",
+ "md5": "85d162b48ce98503a382d96f574f70a2",
"link": false,
"rounds": 1,
"type": "eq"
diff --git a/test/unit/obj_spec.js b/test/unit/obj_spec.js
new file mode 100644
index 000000000..4f1a0b57a
--- /dev/null
+++ b/test/unit/obj_spec.js
@@ -0,0 +1,16 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+describe("obj", function() {
+
+ describe("Name", function() {
+ it("should retain the given name", function() {
+ var givenName = "Font";
+ var name = new Name(givenName);
+ expect(name.name).toEqual(givenName);
+ });
+ });
+});
+
diff --git a/test/unit/unit_test.html b/test/unit/unit_test.html
new file mode 100644
index 000000000..1fc28ef83
--- /dev/null
+++ b/test/unit/unit_test.html
@@ -0,0 +1,51 @@
+
+
+
+ pdf.js unit test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+