Merge upstream.

This commit is contained in:
Brendan Dahl 2012-02-08 16:31:30 -08:00
commit a5d9ff8568
66 changed files with 6462 additions and 1424 deletions

4
.gitignore vendored
View File

@ -1,7 +1,5 @@
*~ *~
pdf.pdf
intelisa.pdf
openweb_tm-PRINT.pdf
local.mk local.mk
build/ build/
tags

View File

@ -10,6 +10,8 @@
Kalervo Kujala Kalervo Kujala
Adil Allawi <@ironymark> Adil Allawi <@ironymark>
Jakob Miland <saebekassebil@gmail.com> Jakob Miland <saebekassebil@gmail.com>
Artur Adib <aadib@mozilla.com>
Brendan Dahl <bdahl@mozilla.com>
Permission is hereby granted, free of charge, to any person obtaining a Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),

112
Makefile
View File

@ -3,9 +3,12 @@ BUILD_DIR := build
BUILD_TARGET := $(BUILD_DIR)/pdf.js BUILD_TARGET := $(BUILD_DIR)/pdf.js
DEFAULT_BROWSERS := resources/browser_manifests/browser_manifest.json DEFAULT_BROWSERS := resources/browser_manifests/browser_manifest.json
DEFAULT_TESTS := test_manifest.json DEFAULT_TESTS := test_manifest.json
DEFAULT_PYTHON := python2.7
EXTENSION_SRC := ./extensions/ EXTENSION_SRC := ./extensions/
EXTENSION_BASE_VERSION := 4bb289ec499013de66eb421737a4dbb4a9273eda
FIREFOX_EXTENSION_NAME := pdf.js.xpi FIREFOX_EXTENSION_NAME := pdf.js.xpi
FIREFOX_AMO_EXTENSION_NAME := pdf.js.amo.xpi
CHROME_EXTENSION_NAME := pdf.js.crx CHROME_EXTENSION_NAME := pdf.js.crx
all: bundle all: bundle
@ -34,6 +37,7 @@ PDF_JS_FILES = \
stream.js \ stream.js \
worker.js \ worker.js \
../external/jpgjs/jpg.js \ ../external/jpgjs/jpg.js \
jpx.js \
$(NULL) $(NULL)
# make server # make server
@ -41,8 +45,12 @@ PDF_JS_FILES = \
# This target starts a local web server at localhost:8888. This can be # This target starts a local web server at localhost:8888. This can be
# used for testing all browsers. # used for testing all browsers.
server: server:
@cd test; python test.py --port=8888; @cd test; $(DEFAULT_PYTHON) test.py --port=8888;
# make test
#
# This target runs all the tests excluding the unit-test. This can be used for
# testing all browsers.
test: shell-test browser-test test: shell-test browser-test
# #
@ -65,10 +73,17 @@ bundle: | $(BUILD_DIR)
cat $(PDF_JS_FILES) > all_files.tmp; \ cat $(PDF_JS_FILES) > all_files.tmp; \
sed '/PDFJSSCRIPT_INCLUDE_ALL/ r all_files.tmp' pdf.js > ../$(BUILD_TARGET); \ sed '/PDFJSSCRIPT_INCLUDE_ALL/ r all_files.tmp' pdf.js > ../$(BUILD_TARGET); \
sed -i.bak "s/PDFJSSCRIPT_BUNDLE_VER/`git log --format="%h" -n 1`/" ../$(BUILD_TARGET); \ sed -i.bak "s/PDFJSSCRIPT_BUNDLE_VER/`git log --format="%h" -n 1`/" ../$(BUILD_TARGET); \
rm -f ../$(BUILD_TARGET).bak rm -f ../$(BUILD_TARGET).bak; \
rm -f *.tmp; \ rm -f *.tmp; \
cd .. cd ..
# make unit-test
#
# This target runs in-browser unit tests with js-test-driver and jasmine unit
# test framework.
unit-test:
@cd test/unit/ ; make ;
# make browser-test # make browser-test
# #
# This target runs in-browser tests using two primary arguments: a # This target runs in-browser tests using two primary arguments: a
@ -93,7 +108,7 @@ browser-test:
fi; fi;
cd test; \ cd test; \
python test.py --reftest \ $(DEFAULT_PYTHON) test.py --reftest \
--browserManifestFile=$(PDF_BROWSERS) \ --browserManifestFile=$(PDF_BROWSERS) \
--manifestFile=$(PDF_TESTS) --manifestFile=$(PDF_TESTS)
@ -147,7 +162,10 @@ web: | production extension compiler pages-repo
@cp $(BUILD_TARGET) $(GH_PAGES)/$(BUILD_TARGET) @cp $(BUILD_TARGET) $(GH_PAGES)/$(BUILD_TARGET)
@cp -R web/* $(GH_PAGES)/web @cp -R web/* $(GH_PAGES)/web
@cp web/images/* $(GH_PAGES)/web/images @cp web/images/* $(GH_PAGES)/web/images
@cp $(EXTENSION_SRC)/firefox/*.xpi $(GH_PAGES)/$(EXTENSION_SRC)/firefox/ @cp $(FIREFOX_BUILD_DIR)/$(FIREFOX_EXTENSION_NAME) \
$(FIREFOX_BUILD_DIR)/$(FIREFOX_AMO_EXTENSION_NAME) \
$(FIREFOX_BUILD_DIR)/update.rdf \
$(GH_PAGES)/$(EXTENSION_SRC)/firefox/
@cp $(GH_PAGES)/web/index.html.template $(GH_PAGES)/index.html; @cp $(GH_PAGES)/web/index.html.template $(GH_PAGES)/index.html;
@mv -f $(GH_PAGES)/web/viewer-production.html $(GH_PAGES)/web/viewer.html; @mv -f $(GH_PAGES)/web/viewer-production.html $(GH_PAGES)/web/viewer.html;
@cd $(GH_PAGES); git add -A; @cd $(GH_PAGES); git add -A;
@ -190,35 +208,83 @@ pages-repo: | $(BUILD_DIR)
# This target produce a restartless firefox extension containing a # This target produce a restartless firefox extension containing a
# copy of the pdf.js source. # copy of the pdf.js source.
CONTENT_DIR := content CONTENT_DIR := content
FIREFOX_CONTENT_DIR := $(EXTENSION_SRC)/firefox/$(CONTENT_DIR)/ BUILD_NUMBER := `git log --format=oneline $(EXTENSION_BASE_VERSION).. | wc -l | awk '{print $$1}'`
CHROME_CONTENT_DIR := $(EXTENSION_SRC)/chrome/$(CONTENT_DIR)/ EXTENSION_WEB_FILES = \
PDF_WEB_FILES = \
web/images \ web/images \
web/compatibility.js \
web/viewer.css \ web/viewer.css \
web/viewer.js \ web/viewer.js \
web/viewer.html \
web/viewer-production.html \ web/viewer-production.html \
$(NULL) $(NULL)
FIREFOX_BUILD_DIR := $(BUILD_DIR)/firefox
FIREFOX_BUILD_CONTENT := $(FIREFOX_BUILD_DIR)/$(CONTENT_DIR)/
FIREFOX_CONTENT_DIR := $(EXTENSION_SRC)/firefox/$(CONTENT_DIR)/
FIREFOX_EXTENSION_FILES_TO_COPY = \
*.js \
*.rdf \
components \
$(NULL)
FIREFOX_EXTENSION_FILES = \
content \
*.js \
install.rdf \
components \
content \
$(NULL)
CHROME_BUILD_DIR := $(BUILD_DIR)/chrome
CHROME_CONTENT_DIR := $(EXTENSION_SRC)/chrome/$(CONTENT_DIR)/
CHROME_BUILD_CONTENT := $(CHROME_BUILD_DIR)/$(CONTENT_DIR)/
CHROME_EXTENSION_FILES = \
extensions/chrome/*.json \
extensions/chrome/*.html \
$(NULL)
extension: | production extension: | production
# Clear out everything in the firefox extension build directory
@rm -Rf $(FIREFOX_BUILD_DIR)
@mkdir -p $(FIREFOX_BUILD_CONTENT)
@mkdir -p $(FIREFOX_BUILD_CONTENT)/$(BUILD_DIR)
@mkdir -p $(FIREFOX_BUILD_CONTENT)/web
@cd extensions/firefox; cp -r $(FIREFOX_EXTENSION_FILES_TO_COPY) ../../$(FIREFOX_BUILD_DIR)/
# Copy a standalone version of pdf.js inside the content directory # Copy a standalone version of pdf.js inside the content directory
@rm -Rf $(FIREFOX_CONTENT_DIR) @cp $(BUILD_TARGET) $(FIREFOX_BUILD_CONTENT)/$(BUILD_DIR)/
@mkdir -p $(FIREFOX_CONTENT_DIR)/$(BUILD_DIR) @cp -r $(EXTENSION_WEB_FILES) $(FIREFOX_BUILD_CONTENT)/web/
@mkdir -p $(FIREFOX_CONTENT_DIR)/web @rm $(FIREFOX_BUILD_CONTENT)/web/viewer-production.html
@cp $(BUILD_TARGET) $(FIREFOX_CONTENT_DIR)/$(BUILD_DIR) # Copy over the firefox extension snippet so we can inline pdf.js in it
@cp -r $(PDF_WEB_FILES) $(FIREFOX_CONTENT_DIR)/web/ @cp web/viewer-snippet-firefox-extension.html $(FIREFOX_BUILD_CONTENT)/web/
@mv -f $(FIREFOX_CONTENT_DIR)/web/viewer-production.html $(FIREFOX_CONTENT_DIR)/web/viewer.html # Modify the viewer so it does all the extension only stuff.
@cd $(FIREFOX_BUILD_CONTENT)/web; \
sed -i.bak '/PDFJSSCRIPT_INCLUDE_BUNDLE/ r ../build/pdf.js' viewer-snippet-firefox-extension.html; \
sed -i.bak '/PDFJSSCRIPT_REMOVE/d' viewer.html; \
sed -i.bak '/PDFJSSCRIPT_REMOVE_FIREFOX_EXTENSION/d' viewer.html; \
sed -i.bak '/PDFJSSCRIPT_INCLUDE_FIREFOX_EXTENSION/ r viewer-snippet-firefox-extension.html' viewer.html; \
rm -f *.bak;
# We don't need pdf.js anymore since its inlined
@rm -Rf $(FIREFOX_BUILD_CONTENT)/$(BUILD_DIR)/;
# Update the build version number
@sed -i.bak "s/PDFJSSCRIPT_BUILD/$(BUILD_NUMBER)/" $(FIREFOX_BUILD_DIR)/install.rdf
@sed -i.bak "s/PDFJSSCRIPT_BUILD/$(BUILD_NUMBER)/" $(FIREFOX_BUILD_DIR)/update.rdf
@rm -f $(FIREFOX_BUILD_DIR)/*.bak
# Create the xpi # Create the xpi
@cd $(EXTENSION_SRC)/firefox; zip -r $(FIREFOX_EXTENSION_NAME) * @cd $(FIREFOX_BUILD_DIR); zip -r $(FIREFOX_EXTENSION_NAME) $(FIREFOX_EXTENSION_FILES)
@echo "extension created: " $(FIREFOX_EXTENSION_NAME) @echo "extension created: " $(FIREFOX_EXTENSION_NAME)
# Build the amo extension too (remove the updateUrl)
@sed -i.bak "/updateURL/d" $(FIREFOX_BUILD_DIR)/install.rdf
@rm -f $(FIREFOX_BUILD_DIR)/*.bak
@cd $(FIREFOX_BUILD_DIR); zip -r $(FIREFOX_AMO_EXTENSION_NAME) $(FIREFOX_EXTENSION_FILES)
@echo "AMO extension created: " $(FIREFOX_AMO_EXTENSION_NAME)
# Copy a standalone version of pdf.js inside the extension directory # Clear out everything in the chrome extension build directory
@rm -Rf $(CHROME_CONTENT_DIR) @rm -Rf $(CHROME_BUILD_DIR)
@mkdir -p $(CHROME_CONTENT_DIR)/$(BUILD_DIR) @mkdir -p $(CHROME_BUILD_CONTENT)
@mkdir -p $(CHROME_CONTENT_DIR)/web @mkdir -p $(CHROME_BUILD_CONTENT)/$(BUILD_DIR)
@cp $(BUILD_TARGET) $(CHROME_CONTENT_DIR)/$(BUILD_DIR) @mkdir -p $(CHROME_BUILD_CONTENT)/web
@cp -r $(PDF_WEB_FILES) $(CHROME_CONTENT_DIR)/web/ @cp -R $(CHROME_EXTENSION_FILES) $(CHROME_BUILD_DIR)/
@mv -f $(CHROME_CONTENT_DIR)/web/viewer-production.html $(CHROME_CONTENT_DIR)/web/viewer.html # Copy a standalone version of pdf.js inside the content directory
@cp $(BUILD_TARGET) $(CHROME_BUILD_CONTENT)/$(BUILD_DIR)/
@cp -r $(EXTENSION_WEB_FILES) $(CHROME_BUILD_CONTENT)/web/
@mv -f $(CHROME_BUILD_CONTENT)/web/viewer-production.html $(CHROME_BUILD_CONTENT)/web/viewer.html
# Create the crx # Create the crx
#TODO #TODO

View File

@ -1,9 +1,6 @@
# pdf.js # PDF.JS
## Overview
pdf.js is an HTML5 technology experiment that explores building a faithful pdf.js is an HTML5 technology experiment that explores building a faithful
and efficient Portable Document Format (PDF) renderer without native code and efficient Portable Document Format (PDF) renderer without native code
assistance. assistance.
@ -14,9 +11,9 @@ rendering PDFs, and eventually release a PDF reader extension powered by
pdf.js. Integration with Firefox is a possibility if the experiment proves pdf.js. Integration with Firefox is a possibility if the experiment proves
successful. successful.
# Getting started
## Getting started
### Online demo ### Online demo
@ -29,11 +26,15 @@ using the pdf.js API.
### Extension ### Extension
An up-to-date Firefox extension is also available: A Firefox extension is availble in two places:
+ http://mozilla.github.com/pdf.js/extensions/firefox/pdf.js.xpi + Stable Version: https://addons.mozilla.org/en-US/firefox/addon/pdfjs
+ Development Version: http://mozilla.github.com/pdf.js/extensions/firefox/pdf.js.xpi
(The above link is updated upon every merge to our master branch). The development extension should be quite stable but still might break from time to time.
Also, note that the development extension is updated on every merge and by default Firefox will
auto-update extensions on a daily basis (you can change this through the
`extensions.update.interval` option in `about:config`).
For an experimental Chrome extension, get the code as explained below and issue `make extension`. For an experimental Chrome extension, get the code as explained below and issue `make extension`.
Then open Chrome with the flag `--enable-experimental-extension-apis`, go to `Tools > Extension` Then open Chrome with the flag `--enable-experimental-extension-apis`, go to `Tools > Extension`
@ -68,12 +69,12 @@ In order to bundle all `src/` files into a final `pdf.js`, issue:
This will generate the file `build/pdf.js` that can be included in your final project. (WARNING: That's a large file! Consider minifying it). This will generate the file `build/pdf.js` that can be included in your final project. (WARNING: That's a large file! Consider minifying it).
## Learning # Learning
Here are some initial pointers to help contributors get off the ground. Here are some initial pointers to help contributors get off the ground.
Additional resources are available in a separate section below. Additional resources are available in a separate section below.
#### Hello world ### Hello world
For a "hello world" example, take a look at: For a "hello world" example, take a look at:
@ -82,7 +83,7 @@ For a "hello world" example, take a look at:
This example illustrates the bare minimum ingredients for integrating pdf.js This example illustrates the bare minimum ingredients for integrating pdf.js
in a custom project. in a custom project.
#### Introductory video ### Introductory video
Check out the presentation by our contributor Julian Viereck on the inner Check out the presentation by our contributor Julian Viereck on the inner
workings of PDF and pdf.js: workings of PDF and pdf.js:
@ -92,7 +93,7 @@ workings of PDF and pdf.js:
## Contributing # Contributing
pdf.js is a community-driven project, so contributors are always welcome. pdf.js is a community-driven project, so contributors are always welcome.
Simply fork our repo and contribute away. Good starting places for picking Simply fork our repo and contribute away. Good starting places for picking
@ -122,7 +123,7 @@ You can add your name to it! :)
## Running the tests # Running the tests
pdf.js comes with browser-level regression tests that allow one to probe pdf.js comes with browser-level regression tests that allow one to probe
whether it's able to successfully parse PDFs, as well as compare its output whether it's able to successfully parse PDFs, as well as compare its output
@ -148,7 +149,7 @@ images. The test type `load` simply tests whether the file loads without
raising any errors. raising any errors.
## Running tests through our bot ### Running tests through our bot
If you are a reviewer, you can use our remote bot to issue comprehensive tests If you are a reviewer, you can use our remote bot to issue comprehensive tests
against reference images before merging pull requests. against reference images before merging pull requests.
@ -158,7 +159,7 @@ See the bot repo for details:
+ https://github.com/mozilla/pdf.js-bot + https://github.com/mozilla/pdf.js-bot
## Additional resources # Additional resources
Gallery of user projects and modifications: Gallery of user projects and modifications:
@ -188,7 +189,7 @@ Follow us on twitter: @pdfjs
## PDF-related resources ### PDF-related resources
A really basic overview of PDF is described here: A really basic overview of PDF is described here:
@ -205,4 +206,3 @@ a "PDF Reference" from Adobe:
Recommended chapters to read: "2. Overview", "3.4 File Structure", Recommended chapters to read: "2. Overview", "3.4 File Structure",
"4.1 Graphics Objects" that lists the PDF commands. "4.1 Graphics Objects" that lists the PDF commands.

View File

@ -23,6 +23,7 @@
<script type="text/javascript" src="../../src/stream.js"></script> <script type="text/javascript" src="../../src/stream.js"></script>
<script type="text/javascript" src="../../src/worker.js"></script> <script type="text/javascript" src="../../src/worker.js"></script>
<script type="text/javascript" src="../../external/jpgjs/jpg.js"></script> <script type="text/javascript" src="../../external/jpgjs/jpg.js"></script>
<script type="text/javascript" src="../../src/jpx.js"></script>
<script type="text/javascript"> <script type="text/javascript">
// Specify the main script used to create a new PDF.JS web worker. // Specify the main script used to create a new PDF.JS web worker.

View File

@ -23,6 +23,7 @@
<script type="text/javascript" src="../../src/stream.js"></script> <script type="text/javascript" src="../../src/stream.js"></script>
<script type="text/javascript" src="../../src/worker.js"></script> <script type="text/javascript" src="../../src/worker.js"></script>
<script type="text/javascript" src="../../external/jpgjs/jpg.js"></script> <script type="text/javascript" src="../../external/jpgjs/jpg.js"></script>
<script type="text/javascript" src="../../src/jpx.js"></script>
<script type="text/javascript"> <script type="text/javascript">
// Specify the main script used to create a new PDF.JS web worker. // Specify the main script used to create a new PDF.JS web worker.

View File

@ -3,6 +3,9 @@
'use strict'; 'use strict';
const RESOURCE_NAME = 'pdf.js';
const EXT_PREFIX = 'extensions.uriloader@pdf.js';
let Cc = Components.classes; let Cc = Components.classes;
let Ci = Components.interfaces; let Ci = Components.interfaces;
let Cm = Components.manager; let Cm = Components.manager;
@ -14,33 +17,88 @@ function log(str) {
dump(str + '\n'); dump(str + '\n');
} }
function startup(aData, aReason) { // Register/unregister a class as a component.
let manifestPath = 'chrome.manifest'; let Factory = {
let manifest = Cc['@mozilla.org/file/local;1'] registrar: null,
.createInstance(Ci.nsILocalFile); aClass: null,
try { register: function(aClass) {
manifest.initWithPath(aData.installPath.path); if (this.aClass) {
manifest.append(manifestPath); log('Cannot register more than one class');
Cm.QueryInterface(Ci.nsIComponentRegistrar).autoRegister(manifest); return;
Services.prefs.setBoolPref('extensions.pdf.js.active', true); }
} catch (e) { this.registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
log(e); this.aClass = aClass;
var proto = aClass.prototype;
this.registrar.registerFactory(proto.classID, proto.classDescription,
proto.contractID, this);
},
unregister: function() {
if (!this.aClass) {
log('Class was never registered.');
return;
}
var proto = this.aClass.prototype;
this.registrar.unregisterFactory(proto.classID, this);
this.aClass = null;
},
// nsIFactory::createInstance
createInstance: function(outer, iid) {
if (outer !== null)
throw Cr.NS_ERROR_NO_AGGREGATION;
return (new (this.aClass)).QueryInterface(iid);
} }
};
let pdfStreamConverterUrl = null;
// As of Firefox 13 bootstrapped add-ons don't support automatic registering and
// unregistering of resource urls and components/contracts. Until then we do
// it programatically. See ManifestDirective ManifestParser.cpp for support.
function startup(aData, aReason) {
// Setup the resource url.
var ioService = Services.io;
var resProt = ioService.getProtocolHandler('resource')
.QueryInterface(Ci.nsIResProtocolHandler);
var aliasFile = Cc['@mozilla.org/file/local;1']
.createInstance(Ci.nsILocalFile);
var componentPath = aData.installPath.clone();
componentPath.append('content');
aliasFile.initWithPath(componentPath.path);
var aliasURI = ioService.newFileURI(aliasFile);
resProt.setSubstitution(RESOURCE_NAME, aliasURI);
// Load the component and register it.
pdfStreamConverterUrl = aData.resourceURI.spec +
'components/PdfStreamConverter.js';
Cu.import(pdfStreamConverterUrl);
Factory.register(PdfStreamConverter);
Services.prefs.setBoolPref('extensions.pdf.js.active', true);
} }
function shutdown(aData, aReason) { function shutdown(aData, aReason) {
if (Services.prefs.getBoolPref('extensions.pdf.js.active')) if (Services.prefs.getBoolPref('extensions.pdf.js.active'))
Services.prefs.setBoolPref('extensions.pdf.js.active', false); Services.prefs.setBoolPref('extensions.pdf.js.active', false);
var ioService = Services.io;
var resProt = ioService.getProtocolHandler('resource')
.QueryInterface(Ci.nsIResProtocolHandler);
// Remove the resource url.
resProt.setSubstitution(RESOURCE_NAME, null);
// Remove the contract/component.
Factory.unregister();
// Unload the converter
if (pdfStreamConverterUrl) {
Cu.unload(pdfStreamConverterUrl);
pdfStreamConverterUrl = null;
}
} }
function install(aData, aReason) { function install(aData, aReason) {
let url = 'chrome://pdf.js/content/web/viewer.html?file=%s';
Services.prefs.setCharPref('extensions.pdf.js.url', url);
Services.prefs.setBoolPref('extensions.pdf.js.active', false); Services.prefs.setBoolPref('extensions.pdf.js.active', false);
} }
function uninstall(aData, aReason) { function uninstall(aData, aReason) {
Services.prefs.clearUserPref('extensions.pdf.js.url');
Services.prefs.clearUserPref('extensions.pdf.js.active'); Services.prefs.clearUserPref('extensions.pdf.js.active');
application.prefs.setValue(EXT_PREFIX + '.database', '{}');
} }

View File

@ -1,5 +0,0 @@
content pdf.js content/
component {2278dfd0-b75c-11e0-8257-1ba3d93c9f1a} components/pdfContentHandler.js
contract @mozilla.org/uriloader/content-handler;1?type=application/pdf {2278dfd0-b75c-11e0-8257-1ba3d93c9f1a}

View File

@ -0,0 +1,190 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
var EXPORTED_SYMBOLS = ['PdfStreamConverter'];
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
const PDFJS_EVENT_ID = 'pdf.js.message';
const PDF_CONTENT_TYPE = 'application/pdf';
const EXT_PREFIX = 'extensions.uriloader@pdf.js';
const MAX_DATABASE_LENGTH = 4096;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
function log(aMsg) {
let msg = 'PdfStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
Services.console.logStringMessage(msg);
dump(msg + '\n');
}
function getWindow(top, id) {
return top.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.getOuterWindowWithId(id);
}
function windowID(win) {
return win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.outerWindowID;
}
function topWindow(win) {
return win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
}
let application = Cc['@mozilla.org/fuel/application;1']
.getService(Ci.fuelIApplication);
let privateBrowsing = Cc['@mozilla.org/privatebrowsing;1']
.getService(Ci.nsIPrivateBrowsingService);
let inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled;
// All the priviledged actions.
function ChromeActions() {
this.inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled;
}
ChromeActions.prototype = {
download: function(data) {
Services.wm.getMostRecentWindow('navigator:browser').saveURL(data);
},
setDatabase: function(data) {
if (this.inPrivateBrowswing)
return;
// Protect against something sending tons of data to setDatabase.
if (data.length > MAX_DATABASE_LENGTH)
return;
application.prefs.setValue(EXT_PREFIX + '.database', data);
},
getDatabase: function() {
if (this.inPrivateBrowswing)
return '{}';
return application.prefs.getValue(EXT_PREFIX + '.database', '{}');
}
};
// Event listener to trigger chrome privedged code.
function RequestListener(actions) {
this.actions = actions;
}
// Receive an event and synchronously responds.
RequestListener.prototype.receive = function(event) {
var message = event.target;
var action = message.getUserData('action');
var data = message.getUserData('data');
var actions = this.actions;
if (!(action in actions)) {
log('Unknown action: ' + action);
return;
}
var response = actions[action].call(this.actions, data);
message.setUserData('response', response, null);
};
function PdfStreamConverter() {
}
PdfStreamConverter.prototype = {
// properties required for XPCOM registration:
classID: Components.ID('{6457a96b-2d68-439a-bcfa-44465fbcdbb1}'),
classDescription: 'pdf.js Component',
contractID: '@mozilla.org/streamconv;1?from=application/pdf&to=*/*',
QueryInterface: XPCOMUtils.generateQI([
Ci.nsISupports,
Ci.nsIStreamConverter,
Ci.nsIStreamListener,
Ci.nsIRequestObserver
]),
/*
* This component works as such:
* 1. asyncConvertData stores the listener
* 2. onStartRequest creates a new channel, streams the viewer and cancels
* the request so pdf.js can do the request
* Since the request is cancelled onDataAvailable should not be called. The
* onStopRequest does nothing. The convert function just returns the stream,
* it's just the synchronous version of asyncConvertData.
*/
// nsIStreamConverter::convert
convert: function(aFromStream, aFromType, aToType, aCtxt) {
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
// nsIStreamConverter::asyncConvertData
asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
if (!Services.prefs.getBoolPref('extensions.pdf.js.active'))
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
// Store the listener passed to us
this.listener = aListener;
},
// nsIStreamListener::onDataAvailable
onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
// Do nothing since all the data loading is handled by the viewer.
log('SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!');
},
// nsIRequestObserver::onStartRequest
onStartRequest: function(aRequest, aContext) {
// Setup the request so we can use it below.
aRequest.QueryInterface(Ci.nsIChannel);
// Cancel the request so the viewer can handle it.
aRequest.cancel(Cr.NS_BINDING_ABORTED);
// Create a new channel that is viewer loaded as a resource.
var ioService = Services.io;
var channel = ioService.newChannel(
'resource://pdf.js/web/viewer.html', null, null);
// Keep the URL the same so the browser sees it as the same.
channel.originalURI = aRequest.URI;
channel.asyncOpen(this.listener, aContext);
// Setup a global listener waiting for the next DOM to be created and verfiy
// that its the one we want by its URL. When the correct DOM is found create
// an event listener on that window for the pdf.js events that require
// chrome priviledges. Code snippet from John Galt.
let window = aRequest.loadGroup.groupObserver
.QueryInterface(Ci.nsIWebProgress)
.DOMWindow;
let top = topWindow(window);
let id = windowID(window);
window = null;
top.addEventListener('DOMWindowCreated', function onDOMWinCreated(event) {
let doc = event.originalTarget;
let win = doc.defaultView;
if (id == windowID(win)) {
top.removeEventListener('DOMWindowCreated', onDOMWinCreated, true);
if (!doc.documentURIObject.equals(aRequest.URI))
return;
let requestListener = new RequestListener(new ChromeActions);
win.addEventListener(PDFJS_EVENT_ID, function(event) {
requestListener.receive(event);
}, false, true);
} else if (!getWindow(top, id)) {
top.removeEventListener('DOMWindowCreated', onDOMWinCreated, true);
}
}, true);
},
// nsIRequestObserver::onStopRequest
onStopRequest: function(aRequest, aContext, aStatusCode) {
// Do nothing.
}
};
var NSGetFactory = XPCOMUtils.generateNSGetFactory([PdfStreamConverter]);

View File

@ -1,67 +0,0 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
const PDF_CONTENT_TYPE = 'application/pdf';
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
function log(aMsg) {
let msg = 'pdfContentHandler.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
Cc['@mozilla.org/consoleservice;1'].getService(Ci.nsIConsoleService)
.logStringMessage(msg);
dump(msg + '\n');
}
const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
function pdfContentHandler() {
}
pdfContentHandler.prototype = {
handleContent: function handleContent(aMimetype, aContext, aRequest) {
if (aMimetype != PDF_CONTENT_TYPE)
throw NS_ERROR_WONT_HANDLE_CONTENT;
if (!(aRequest instanceof Ci.nsIChannel))
throw NS_ERROR_WONT_HANDLE_CONTENT;
if (!Services.prefs.getBoolPref('extensions.pdf.js.active'))
throw NS_ERROR_WONT_HANDLE_CONTENT;
let window = null;
let callbacks = aRequest.notificationCallbacks ||
aRequest.loadGroup.notificationCallbacks;
if (!callbacks)
return;
window = callbacks.getInterface(Ci.nsIDOMWindow);
let url = null;
try {
url = Services.prefs.getCharPref('extensions.pdf.js.url');
} catch (e) {
log('Error retrieving the pdf.js base url - ' + e);
throw NS_ERROR_WONT_HANDLE_CONTENT;
}
let targetUrl = aRequest.URI.spec;
if (targetUrl.indexOf('#pdfjs.action=download') >= 0)
throw NS_ERROR_WONT_HANDLE_CONTENT;
aRequest.cancel(Cr.NS_BINDING_ABORTED);
window.location = url.replace('%s', encodeURIComponent(targetUrl));
},
classID: Components.ID('{2278dfd0-b75c-11e0-8257-1ba3d93c9f1a}'),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentHandler])
};
var NSGetFactory = XPCOMUtils.generateNSGetFactory([pdfContentHandler]);

View File

@ -6,20 +6,22 @@
<Description about="urn:mozilla:install-manifest"> <Description about="urn:mozilla:install-manifest">
<em:id>uriloader@pdf.js</em:id> <em:id>uriloader@pdf.js</em:id>
<em:name>pdf.js</em:name> <em:name>pdf.js</em:name>
<em:version>0.1.0</em:version> <em:version>0.2.PDFJSSCRIPT_BUILD</em:version>
<em:iconURL>chrome://pdf.js/skin/logo.png</em:iconURL> <em:iconURL>chrome://pdf.js/skin/logo.png</em:iconURL>
<em:targetApplication> <em:targetApplication>
<Description> <Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>6.0</em:minVersion> <em:minVersion>6.0</em:minVersion>
<em:maxVersion>11.0a1</em:maxVersion> <em:maxVersion>13.0a1</em:maxVersion>
</Description> </Description>
</em:targetApplication> </em:targetApplication>
<em:bootstrap>true</em:bootstrap> <em:bootstrap>true</em:bootstrap>
<em:unpack>true</em:unpack> <em:unpack>true</em:unpack>
<em:creator>Vivien Nicolas</em:creator> <em:creator>Mozilla Labs</em:creator>
<em:description>pdf.js uri loader</em:description> <em:description>pdf.js uri loader</em:description>
<em:homepageURL>https://github.com/mozilla/pdf.js/</em:homepageURL> <em:homepageURL>https://github.com/mozilla/pdf.js/</em:homepageURL>
<em:type>2</em:type> <em:type>2</em:type>
<!-- Use the raw link for updates so we we can use SSL. -->
<em:updateURL>https://github.com/mozilla/pdf.js/raw/gh-pages/extensions/firefox/update.rdf</em:updateURL>
</Description> </Description>
</RDF> </RDF>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<RDF:Description about="urn:mozilla:extension:uriloader@pdf.js">
<em:updates>
<RDF:Seq>
<RDF:li>
<RDF:Description>
<em:version>0.2.PDFJSSCRIPT_BUILD</em:version>
<em:targetApplication>
<RDF:Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>6.0</em:minVersion>
<em:maxVersion>11.*</em:maxVersion>
<!-- Use the raw link for updates so we we can use SSL. -->
<em:updateLink>https://github.com/mozilla/pdf.js/raw/gh-pages/extensions/firefox/pdf.js.xpi</em:updateLink>
</RDF:Description>
</em:targetApplication>
</RDF:Description>
</RDF:li>
</RDF:Seq>
</em:updates>
</RDF:Description>
</RDF:RDF>

View File

@ -1,676 +0,0 @@
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;
};

View File

@ -1,81 +0,0 @@
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; }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 905 B

View File

@ -0,0 +1,198 @@
/**
* @fileoverview Jasmine JsTestDriver Adapter.
* @author misko@hevery.com (Misko Hevery)
*/
(function(window) {
var rootDescribes = new Describes(window);
var describePath = [];
rootDescribes.collectMode();
var JASMINE_TYPE = 'jasmine test case';
TestCase('Jasmine Adapter Tests', null, JASMINE_TYPE);
var jasminePlugin = {
name:'jasmine',
getTestRunsConfigurationFor: function(testCaseInfos, expressions, testRunsConfiguration) {
for (var i = 0; i < testCaseInfos.length; i++) {
if (testCaseInfos[i].getType() == JASMINE_TYPE) {
testRunsConfiguration.push(new jstestdriver.TestRunConfiguration(testCaseInfos[i], []));
}
}
return false;
},
runTestConfiguration: function(testRunConfiguration, onTestDone, onTestRunConfigurationComplete){
if (testRunConfiguration.getTestCaseInfo().getType() != JASMINE_TYPE) return false;
var jasmineEnv = jasmine.currentEnv_ = new jasmine.Env();
rootDescribes.playback();
var specLog = jstestdriver.console.log_ = [];
var start;
jasmineEnv.specFilter = function(spec) {
return rootDescribes.isExclusive(spec);
};
jasmineEnv.reporter = {
log: function(str){
specLog.push(str);
},
reportRunnerStarting: function(runner) { },
reportSpecStarting: function(spec) {
specLog = jstestdriver.console.log_ = [];
start = new Date().getTime();
},
reportSpecResults: function(spec) {
var suite = spec.suite;
var results = spec.results();
if (results.skipped) return;
var end = new Date().getTime();
var messages = [];
var resultItems = results.getItems();
var state = 'passed';
for ( var i = 0; i < resultItems.length; i++) {
if (!resultItems[i].passed()) {
state = resultItems[i].message.match(/AssertionError:/) ? 'error' : 'failed';
messages.push({
message: resultItems[i].toString(),
name: resultItems[i].trace.name,
stack: formatStack(resultItems[i].trace.stack)
});
}
}
onTestDone(
new jstestdriver.TestResult(
suite.getFullName(),
spec.description,
state,
jstestdriver.angular.toJson(messages),
specLog.join('\n'),
end - start));
},
reportSuiteResults: function(suite) {},
reportRunnerResults: function(runner) {
onTestRunConfigurationComplete();
}
};
jasmineEnv.execute();
return true;
},
onTestsFinish: function(){
jasmine.currentEnv_ = null;
rootDescribes.collectMode();
}
};
jstestdriver.pluginRegistrar.register(jasminePlugin);
function formatStack(stack) {
var lines = (stack||'').split(/\r?\n/);
var frames = [];
for (i = 0; i < lines.length; i++) {
if (!lines[i].match(/\/jasmine[\.-]/)) {
frames.push(lines[i].replace(/https?:\/\/\w+(:\d+)?\/test\//, '').replace(/^\s*/, ' '));
}
}
return frames.join('\n');
}
function noop(){}
function Describes(window){
var describes = {};
var beforeEachs = {};
var afterEachs = {};
// Here we store:
// 0: everyone runs
// 1: run everything under ddescribe
// 2: run only iits (ignore ddescribe)
var exclusive = 0;
var collectMode = true;
intercept('describe', describes);
intercept('xdescribe', describes);
intercept('beforeEach', beforeEachs);
intercept('afterEach', afterEachs);
function intercept(functionName, collection){
window[functionName] = function(desc, fn){
if (collectMode) {
collection[desc] = function(){
jasmine.getEnv()[functionName](desc, fn);
};
} else {
jasmine.getEnv()[functionName](desc, fn);
}
};
}
window.ddescribe = function(name, fn){
if (exclusive < 1) {
exclusive = 1; // run ddescribe only
}
window.describe(name, function(){
var oldIt = window.it;
window.it = function(name, fn){
fn.exclusive = 1; // run anything under ddescribe
jasmine.getEnv().it(name, fn);
};
try {
fn.call(this);
} finally {
window.it = oldIt;
};
});
};
window.iit = function(name, fn){
exclusive = fn.exclusive = 2; // run only iits
jasmine.getEnv().it(name, fn);
};
this.collectMode = function() {
collectMode = true;
exclusive = 0; // run everything
};
this.playback = function(){
collectMode = false;
playback(beforeEachs);
playback(afterEachs);
playback(describes);
function playback(set) {
for ( var name in set) {
set[name]();
}
}
};
this.isExclusive = function(spec) {
if (exclusive) {
var blocks = spec.queue.blocks;
for ( var i = 0; i < blocks.length; i++) {
if (blocks[i].func.exclusive >= exclusive) {
return true;
}
}
return false;
}
return true;
};
}
})(window);
// Patch Jasmine for proper stack traces
jasmine.Spec.prototype.fail = function (e) {
var expectationResult = new jasmine.ExpectationResult({
passed: false,
message: e ? jasmine.util.formatException(e) : 'Exception'
});
// PATCH
if (e) {
expectationResult.trace = e;
}
this.results_.addResult(expectationResult);
};

23
external/jasmineAdapter/MIT.LICENSE vendored Normal file
View File

@ -0,0 +1,23 @@
Copyright (c) 2010
Misko Hevery <misko@hevery.com>
Olmo Maldonado <me@ibolmo.com>
Christoph Pojer <christoph.pojer@gmail.com>
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.

Binary file not shown.

202
external/jsTestDriver/LICENSE-2.0.txt vendored Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -23,6 +23,7 @@ var CanvasExtraState = (function CanvasExtraStateClosure() {
this.alphaIsShape = false; this.alphaIsShape = false;
this.fontSize = 0; this.fontSize = 0;
this.textMatrix = IDENTITY_MATRIX; this.textMatrix = IDENTITY_MATRIX;
this.fontMatrix = IDENTITY_MATRIX;
this.leading = 0; this.leading = 0;
// Current point (in user coordinates) // Current point (in user coordinates)
this.x = 0; this.x = 0;
@ -48,6 +49,7 @@ var CanvasExtraState = (function CanvasExtraStateClosure() {
// Note: fill alpha applies to all non-stroking operations // Note: fill alpha applies to all non-stroking operations
this.fillAlpha = 1; this.fillAlpha = 1;
this.strokeAlpha = 1; this.strokeAlpha = 1;
this.lineWidth = 1;
this.old = old; this.old = old;
} }
@ -329,6 +331,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
// Graphics state // Graphics state
setLineWidth: function canvasGraphicsSetLineWidth(width) { setLineWidth: function canvasGraphicsSetLineWidth(width) {
this.current.lineWidth = width;
this.ctx.lineWidth = width; this.ctx.lineWidth = width;
}, },
setLineCap: function canvasGraphicsSetLineCap(style) { setLineCap: function canvasGraphicsSetLineCap(style) {
@ -442,6 +445,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
consumePath = typeof consumePath !== 'undefined' ? consumePath : true; consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
var ctx = this.ctx; var ctx = this.ctx;
var strokeColor = this.current.strokeColor; var strokeColor = this.current.strokeColor;
if (this.current.lineWidth === 0)
ctx.lineWidth = this.getSinglePixelWidth();
// For stroke we want to temporarily change the global alpha to the // For stroke we want to temporarily change the global alpha to the
// stroking alpha. // stroking alpha.
ctx.globalAlpha = this.current.strokeAlpha; ctx.globalAlpha = this.current.strokeAlpha;
@ -542,12 +547,32 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
}, },
setFont: function canvasGraphicsSetFont(fontRefName, size) { setFont: function canvasGraphicsSetFont(fontRefName, size) {
var fontObj = this.objs.get(fontRefName).fontObj; var fontObj = this.objs.get(fontRefName).fontObj;
var current = this.current;
if (!fontObj) { if (!fontObj)
throw 'Can\'t find font for ' + fontRefName; error('Can\'t find font for ' + fontRefName);
// Slice-clone matrix so we can manipulate it without affecting original
if (fontObj.fontMatrix)
current.fontMatrix = fontObj.fontMatrix.slice(0);
else
current.fontMatrix = IDENTITY_MATRIX.slice(0);
// A valid matrix needs all main diagonal elements to be non-zero
// This also ensures we bypass FF bugzilla bug #719844.
if (current.fontMatrix[0] === 0 ||
current.fontMatrix[3] === 0) {
warn('Invalid font matrix for font ' + fontRefName);
} }
var name = fontObj.loadedName || 'sans-serif'; // The spec for Tf (setFont) says that 'size' specifies the font 'scale',
// and in some docs this can be negative (inverted x-y axes).
// We implement this condition with fontMatrix.
if (size < 0) {
size = -size;
current.fontMatrix[0] *= -1;
current.fontMatrix[3] *= -1;
}
this.current.font = fontObj; this.current.font = fontObj;
this.current.fontSize = size; this.current.fontSize = size;
@ -557,7 +582,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
(fontObj.bold ? 'bold' : 'normal'); (fontObj.bold ? 'bold' : 'normal');
var italic = fontObj.italic ? 'italic' : 'normal'; var italic = fontObj.italic ? 'italic' : 'normal';
var serif = fontObj.serif ? 'serif' : 'sans-serif'; var serif = fontObj.isSerifFont ? 'serif' : 'sans-serif';
var typeface = '"' + name + '", ' + serif; var typeface = '"' + name + '", ' + serif;
var rule = italic + ' ' + bold + ' ' + size + 'px ' + typeface; var rule = italic + ' ' + bold + ' ' + size + 'px ' + typeface;
this.ctx.font = rule; this.ctx.font = rule;
@ -591,7 +616,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var ctx = this.ctx; var ctx = this.ctx;
var current = this.current; var current = this.current;
var textHScale = current.textHScale; var textHScale = current.textHScale;
var fontMatrix = current.font.fontMatrix || IDENTITY_MATRIX; var fontMatrix = current.fontMatrix || IDENTITY_MATRIX;
ctx.transform.apply(ctx, current.textMatrix); ctx.transform.apply(ctx, current.textMatrix);
ctx.scale(1, -1); ctx.scale(1, -1);
@ -625,7 +650,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var charSpacing = current.charSpacing; var charSpacing = current.charSpacing;
var wordSpacing = current.wordSpacing; var wordSpacing = current.wordSpacing;
var textHScale = current.textHScale; var textHScale = current.textHScale;
var fontMatrix = font.fontMatrix || IDENTITY_MATRIX; var fontMatrix = current.fontMatrix || IDENTITY_MATRIX;
var textHScale2 = textHScale * fontMatrix[0]; var textHScale2 = textHScale * fontMatrix[0];
var glyphsLength = glyphs.length; var glyphsLength = glyphs.length;
var textLayer = this.textLayer; var textLayer = this.textLayer;
@ -640,7 +665,6 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
ctx.translate(current.x, current.y); ctx.translate(current.x, current.y);
ctx.scale(textHScale, 1); ctx.scale(textHScale, 1);
ctx.lineWidth /= current.textMatrix[0];
if (textSelection) { if (textSelection) {
this.save(); this.save();
@ -664,7 +688,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.restore(); this.restore();
var transformed = Util.applyTransform([glyph.width, 0], fontMatrix); var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
var width = transformed[0] * fontSize + charSpacing; var width = transformed[0] * fontSize +
Util.sign(current.fontMatrix[0]) * charSpacing;
ctx.translate(width, 0); ctx.translate(width, 0);
current.x += width * textHScale; current.x += width * textHScale;
@ -677,49 +702,58 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
} else { } else {
ctx.save(); ctx.save();
this.applyTextTransforms(); this.applyTextTransforms();
ctx.lineWidth /= current.textMatrix[0] * fontMatrix[0];
var lineWidth = current.lineWidth;
var scale = Math.abs(current.textMatrix[0] * fontMatrix[0]);
if (scale == 0 || lineWidth == 0)
lineWidth = this.getSinglePixelWidth();
else
lineWidth /= scale;
ctx.lineWidth = lineWidth;
if (textSelection) if (textSelection)
text.geom = this.getTextGeometry(); text.geom = this.getTextGeometry();
var width = 0; var x = 0;
for (var i = 0; i < glyphsLength; ++i) { for (var i = 0; i < glyphsLength; ++i) {
var glyph = glyphs[i]; var glyph = glyphs[i];
if (glyph === null) { if (glyph === null) {
// word break // word break
width += wordSpacing; x += Util.sign(current.fontMatrix[0]) * wordSpacing;
continue; continue;
} }
var char = glyph.fontChar; var char = glyph.fontChar;
var charWidth = glyph.width * fontSize * 0.001 + charSpacing; var charWidth = glyph.width * fontSize * 0.001 +
Util.sign(current.fontMatrix[0]) * charSpacing;
switch (textRenderingMode) { switch (textRenderingMode) {
default: // other unsupported rendering modes default: // other unsupported rendering modes
case TextRenderingMode.FILL: case TextRenderingMode.FILL:
case TextRenderingMode.FILL_ADD_TO_PATH: case TextRenderingMode.FILL_ADD_TO_PATH:
ctx.fillText(char, width, 0); ctx.fillText(char, x, 0);
break; break;
case TextRenderingMode.STROKE: case TextRenderingMode.STROKE:
case TextRenderingMode.STROKE_ADD_TO_PATH: case TextRenderingMode.STROKE_ADD_TO_PATH:
ctx.strokeText(char, width, 0); ctx.strokeText(char, x, 0);
break; break;
case TextRenderingMode.FILL_STROKE: case TextRenderingMode.FILL_STROKE:
case TextRenderingMode.FILL_STROKE_ADD_TO_PATH: case TextRenderingMode.FILL_STROKE_ADD_TO_PATH:
ctx.fillText(char, width, 0); ctx.fillText(char, x, 0);
ctx.strokeText(char, width, 0); ctx.strokeText(char, x, 0);
break; break;
case TextRenderingMode.INVISIBLE: case TextRenderingMode.INVISIBLE:
break; break;
} }
width += charWidth; x += charWidth;
text.str += glyph.unicode === ' ' ? '\u00A0' : glyph.unicode; text.str += glyph.unicode === ' ' ? '\u00A0' : glyph.unicode;
text.length++; text.length++;
text.canvasWidth += charWidth; text.canvasWidth += charWidth;
} }
current.x += width * textHScale2; current.x += x * textHScale2;
ctx.restore(); ctx.restore();
} }
@ -735,7 +769,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
var fontSize = current.fontSize; var fontSize = current.fontSize;
var textHScale = current.textHScale; var textHScale = current.textHScale;
if (!font.coded) if (!font.coded)
textHScale *= (font.fontMatrix || IDENTITY_MATRIX)[0]; textHScale *= (current.fontMatrix || IDENTITY_MATRIX)[0];
var arrLength = arr.length; var arrLength = arr.length;
var textLayer = this.textLayer; var textLayer = this.textLayer;
var text = {str: '', length: 0, canvasWidth: 0, geom: {}}; var text = {str: '', length: 0, canvasWidth: 0, geom: {}};
@ -832,8 +866,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
}, },
setStrokeColor: function canvasGraphicsSetStrokeColor(/*...*/) { setStrokeColor: function canvasGraphicsSetStrokeColor(/*...*/) {
var cs = this.current.strokeColorSpace; var cs = this.current.strokeColorSpace;
var color = cs.getRgb(arguments); var rgbColor = cs.getRgb(arguments);
var color = Util.makeCssRgb.apply(null, cs.getRgb(arguments)); var color = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]);
this.ctx.strokeStyle = color; this.ctx.strokeStyle = color;
this.current.strokeColor = color; this.current.strokeColor = color;
}, },
@ -855,7 +889,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
} else if (IR[0] == 'RadialAxial' || IR[0] == 'Dummy') { } else if (IR[0] == 'RadialAxial' || IR[0] == 'Dummy') {
var pattern = Pattern.shadingFromIR(this.ctx, IR); var pattern = Pattern.shadingFromIR(this.ctx, IR);
} else { } else {
throw 'Unkown IR type'; error('Unkown IR type ' + IR[0]);
} }
return pattern; return pattern;
}, },
@ -870,7 +904,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
}, },
setFillColor: function canvasGraphicsSetFillColor(/*...*/) { setFillColor: function canvasGraphicsSetFillColor(/*...*/) {
var cs = this.current.fillColorSpace; var cs = this.current.fillColorSpace;
var color = Util.makeCssRgb.apply(null, cs.getRgb(arguments)); var rgbColor = cs.getRgb(arguments);
var color = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]);
this.ctx.fillStyle = color; this.ctx.fillStyle = color;
this.current.fillColor = color; this.current.fillColor = color;
}, },
@ -1141,6 +1176,10 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
}, },
restoreFillRule: function canvasGraphicsRestoreFillRule(rule) { restoreFillRule: function canvasGraphicsRestoreFillRule(rule) {
this.ctx.mozFillRule = rule; this.ctx.mozFillRule = rule;
},
getSinglePixelWidth: function getSinglePixelWidth(scale) {
var inverse = this.ctx.mozCurrentTransformInverse;
return Math.abs(inverse[0] + inverse[2]);
} }
}; };

View File

@ -57,6 +57,11 @@ var ColorSpace = (function ColorSpaceClosure() {
return new AlternateCS(numComps, ColorSpace.fromIR(alt), return new AlternateCS(numComps, ColorSpace.fromIR(alt),
PDFFunction.fromIR(tintFnIR)); PDFFunction.fromIR(tintFnIR));
case 'LabCS':
var whitePoint = IR[1].WhitePoint;
var blackPoint = IR[1].BlackPoint;
var range = IR[1].Range;
return new LabCS(whitePoint, blackPoint, range);
default: default:
error('Unkown name ' + name); error('Unkown name ' + name);
} }
@ -146,6 +151,8 @@ var ColorSpace = (function ColorSpaceClosure() {
var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3]));
return ['AlternateCS', numComps, alt, tintFnIR]; return ['AlternateCS', numComps, alt, tintFnIR];
case 'Lab': case 'Lab':
var params = cs[1].map;
return ['LabCS', params];
default: default:
error('unimplemented color space object "' + mode + '"'); error('unimplemented color space object "' + mode + '"');
} }
@ -369,55 +376,16 @@ var DeviceCmykCS = (function DeviceCmykCSClosure() {
DeviceCmykCS.prototype = { DeviceCmykCS.prototype = {
getRgb: function cmykcs_getRgb(color) { getRgb: function cmykcs_getRgb(color) {
var c = color[0], m = color[1], y = color[2], k = color[3]; var c = color[0], m = color[1], y = color[2], k = color[3];
var c1 = 1 - c, m1 = 1 - m, y1 = 1 - y, k1 = 1 - k;
var x, r, g, b; // CMYK -> CMY: http://www.easyrgb.com/index.php?X=MATH&H=14#text14
// this is a matrix multiplication, unrolled for performance c = (c * (1 - k) + k);
// code is taken from the poppler implementation m = (m * (1 - k) + k);
x = c1 * m1 * y1 * k1; // 0 0 0 0 y = (y * (1 - k) + k);
r = g = b = x;
x = c1 * m1 * y1 * k; // 0 0 0 1 // CMY -> RGB: http://www.easyrgb.com/index.php?X=MATH&H=12#text12
r += 0.1373 * x; var r = (1 - c);
g += 0.1216 * x; var g = (1 - m);
b += 0.1255 * x; var b = (1 - y);
x = c1 * m1 * y * k1; // 0 0 1 0
r += x;
g += 0.9490 * x;
x = c1 * m1 * y * k; // 0 0 1 1
r += 0.1098 * x;
g += 0.1020 * x;
x = c1 * m * y1 * k1; // 0 1 0 0
r += 0.9255 * x;
b += 0.5490 * x;
x = c1 * m * y1 * k; // 0 1 0 1
r += 0.1412 * x;
x = c1 * m * y * k1; // 0 1 1 0
r += 0.9294 * x;
g += 0.1098 * x;
b += 0.1412 * x;
x = c1 * m * y * k; // 0 1 1 1
r += 0.1333 * x;
x = c * m1 * y1 * k1; // 1 0 0 0
g += 0.6784 * x;
b += 0.9373 * x;
x = c * m1 * y1 * k; // 1 0 0 1
g += 0.0588 * x;
b += 0.1412 * x;
x = c * m1 * y * k1; // 1 0 1 0
g += 0.6510 * x;
b += 0.3137 * x;
x = c * m1 * y * k; // 1 0 1 1
g += 0.0745 * x;
x = c * m * y1 * k1; // 1 1 0 0
r += 0.1804 * x;
g += 0.1922 * x;
b += 0.5725 * x;
x = c * m * y1 * k; // 1 1 0 1
b += 0.0078 * x;
x = c * m * y * k1; // 1 1 1 0
r += 0.2118 * x;
g += 0.2119 * x;
b += 0.2235 * x;
return [r, g, b]; return [r, g, b];
}, },
@ -448,3 +416,116 @@ var DeviceCmykCS = (function DeviceCmykCSClosure() {
return DeviceCmykCS; return DeviceCmykCS;
})(); })();
//
// LabCS: Based on "PDF Reference, Sixth Ed", p.250
//
var LabCS = (function LabCSClosure() {
function LabCS(whitePoint, blackPoint, range) {
this.name = 'Lab';
this.numComps = 3;
this.defaultColor = [0, 0, 0];
if (!whitePoint)
error('WhitePoint missing - required for color space Lab');
blackPoint = blackPoint || [0, 0, 0];
range = range || [-100, 100, -100, 100];
// Translate args to spec variables
this.XW = whitePoint[0];
this.YW = whitePoint[1];
this.ZW = whitePoint[2];
this.amin = range[0];
this.amax = range[1];
this.bmin = range[2];
this.bmax = range[3];
// These are here just for completeness - the spec doesn't offer any
// formulas that use BlackPoint in Lab
this.XB = blackPoint[0];
this.YB = blackPoint[1];
this.ZB = blackPoint[2];
// Validate vars as per spec
if (this.XW < 0 || this.ZW < 0 || this.YW !== 1)
error('Invalid WhitePoint components, no fallback available');
if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
warn('Invalid BlackPoint, falling back to default');
this.XB = this.YB = this.ZB = 0;
}
if (this.amin > this.amax || this.bmin > this.bmax) {
warn('Invalid Range, falling back to defaults');
this.amin = -100;
this.amax = 100;
this.bmin = -100;
this.bmax = 100;
}
};
// Function g(x) from spec
function g(x) {
if (x >= 6 / 29)
return x * x * x;
else
return (108 / 841) * (x - 4 / 29);
}
LabCS.prototype = {
getRgb: function labcs_getRgb(color) {
// Ls,as,bs <---> L*,a*,b* in the spec
var Ls = color[0], as = color[1], bs = color[2];
// Adjust limits of 'as' and 'bs'
as = as > this.amax ? this.amax : as;
as = as < this.amin ? this.amin : as;
bs = bs > this.bmax ? this.bmax : bs;
bs = bs < this.bmin ? this.bmin : bs;
// Computes intermediate variables X,Y,Z as per spec
var M = (Ls + 16) / 116;
var L = M + (as / 500);
var N = M - (bs / 200);
var X = this.XW * g(L);
var Y = this.YW * g(M);
var Z = this.ZW * g(N);
// XYZ to RGB 3x3 matrix, from:
// http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html#RTFToC18
var XYZtoRGB = [3.240479, -1.537150, -0.498535,
-0.969256, 1.875992, 0.041556,
0.055648, -0.204043, 1.057311];
return Util.apply3dTransform(XYZtoRGB, [X, Y, Z]);
},
getRgbBuffer: function labcs_getRgbBuffer(input, bits) {
if (bits == 8)
return input;
var scale = 255 / ((1 << bits) - 1);
var i, length = input.length / 3;
var rgbBuf = new Uint8Array(length);
var j = 0;
for (i = 0; i < length; ++i) {
// Convert L*, a*, s* into RGB
var rgb = this.getRgb([input[i], input[i + 1], input[i + 2]]);
rgbBuf[j++] = rgb[0];
rgbBuf[j++] = rgb[1];
rgbBuf[j++] = rgb[2];
}
return rgbBuf;
},
isDefaultDecode: function labcs_isDefaultDecode(decodeMap) {
// From Table 90 in Adobe's:
// "Document management - Portable document format", 1st ed, 2008
if (decodeMap[0] === 0 && decodeMap[1] === 100 &&
decodeMap[2] === this.amin && decodeMap[3] === this.amax &&
decodeMap[4] === this.bmin && decodeMap[5] === this.bmax)
return true;
else
return false;
}
};
return LabCS;
})();

View File

@ -33,7 +33,9 @@ function getPdf(arg, callback) {
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.open('GET', params.url); xhr.open('GET', params.url);
xhr.mozResponseType = xhr.responseType = 'arraybuffer'; xhr.mozResponseType = xhr.responseType = 'arraybuffer';
xhr.expected = (params.url.indexOf('file:') === 0) ? 0 : 200; var protocol = params.url.indexOf(':') < 0 ? window.location.protocol :
params.url.substring(0, params.url.indexOf(':') + 1);
xhr.expected = (protocol === 'http:' || protocol === 'https:') ? 200 : 0;
if ('progress' in params) if ('progress' in params)
xhr.onprogress = params.progress || undefined; xhr.onprogress = params.progress || undefined;
@ -193,6 +195,9 @@ var Page = (function PageClosure() {
for (i = 0; i < n; ++i) for (i = 0; i < n; ++i)
content[i] = xref.fetchIfRef(content[i]); content[i] = xref.fetchIfRef(content[i]);
content = new StreamsSequenceStream(content); content = new StreamsSequenceStream(content);
} else if (!content) {
// replacing non-existent page content with empty one
content = new Stream(new Uint8Array(0));
} }
var pe = this.pe = new PartialEvaluator( var pe = this.pe = new PartialEvaluator(
@ -409,14 +414,14 @@ var Page = (function PageClosure() {
if (callback) if (callback)
callback(e); callback(e);
else else
throw e; error(e);
} }
}.bind(this), }.bind(this),
function pageDisplayReadPromiseError(reason) { function pageDisplayReadPromiseError(reason) {
if (callback) if (callback)
callback(reason); callback(reason);
else else
throw reason; error(reason);
} }
); );
} }
@ -619,17 +624,26 @@ var PDFDoc = (function PDFDocClosure() {
if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') { if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
var workerSrc = PDFJS.workerSrc; var workerSrc = PDFJS.workerSrc;
if (typeof workerSrc === 'undefined') { if (typeof workerSrc === 'undefined') {
throw 'No PDFJS.workerSrc specified'; error('No PDFJS.workerSrc specified');
} }
try { try {
// Some versions of FF can't create a worker on localhost, see: var worker;
// https://bugzilla.mozilla.org/show_bug.cgi?id=683280 if (PDFJS.isFirefoxExtension) {
var worker = new Worker(workerSrc); // The firefox extension can't load the worker from the resource://
// url so we have to inline the script and then use the blob loader.
var bb = new MozBlobBuilder();
bb.append(document.querySelector('#PDFJS_SCRIPT_TAG').textContent);
var blobUrl = window.URL.createObjectURL(bb.getBlob());
worker = new Worker(blobUrl);
} else {
// Some versions of FF can't create a worker on localhost, see:
// https://bugzilla.mozilla.org/show_bug.cgi?id=683280
worker = new Worker(workerSrc);
}
var messageHandler = new MessageHandler('main', worker); var messageHandler = new MessageHandler('main', worker);
// Tell the worker the file it was created from.
messageHandler.send('workerSrc', workerSrc);
messageHandler.on('test', function pdfDocTest(supportTypedArray) { messageHandler.on('test', function pdfDocTest(supportTypedArray) {
if (supportTypedArray) { if (supportTypedArray) {
this.worker = worker; this.worker = worker;
@ -645,7 +659,9 @@ var PDFDoc = (function PDFDocClosure() {
// serializing the typed array. // serializing the typed array.
messageHandler.send('test', testObj); messageHandler.send('test', testObj);
return; return;
} catch (e) {} } catch (e) {
warn('The worker has been disabled.');
}
} }
// Either workers are disabled, not supported or have thrown an exception. // Either workers are disabled, not supported or have thrown an exception.
// Thus, we fallback to a faked worker. // Thus, we fallback to a faked worker.
@ -716,7 +732,7 @@ var PDFDoc = (function PDFDocClosure() {
}); });
break; break;
default: default:
throw 'Got unkown object type ' + type; error('Got unkown object type ' + type);
} }
}, this); }, this);
@ -737,7 +753,7 @@ var PDFDoc = (function PDFDocClosure() {
if (page.displayReadyPromise) if (page.displayReadyPromise)
page.displayReadyPromise.reject(data.error); page.displayReadyPromise.reject(data.error);
else else
throw data.error; error(data.error);
}, this); }, this);
messageHandler.on('jpeg_decode', function(data, promise) { messageHandler.on('jpeg_decode', function(data, promise) {

View File

@ -159,6 +159,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
// a Stream in the main thread. // a Stream in the main thread.
if (translated.file) if (translated.file)
translated.file = translated.file.getBytes(); translated.file = translated.file.getBytes();
if (translated.properties.file) {
translated.properties.file =
translated.properties.file.getBytes();
}
handler.send('obj', [ handler.send('obj', [
loadedName, loadedName,
@ -292,8 +296,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
// Create an IR of the pattern code. // Create an IR of the pattern code.
var depIdx = dependencyArray.length; var depIdx = dependencyArray.length;
var queueObj = {}; var queueObj = {};
var codeIR = this.getIRQueue(pattern, dict.get('Resources'), var codeIR = this.getIRQueue(pattern, dict.get('Resources') ||
queueObj, dependencyArray); resources, queueObj, dependencyArray);
// Add the dependencies that are required to execute the // Add the dependencies that are required to execute the
// codeIR. // codeIR.
@ -336,8 +340,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
// This adds the IRQueue of the xObj to the current queue. // This adds the IRQueue of the xObj to the current queue.
var depIdx = dependencyArray.length; var depIdx = dependencyArray.length;
this.getIRQueue(xobj, xobj.dict.get('Resources'), queue, this.getIRQueue(xobj, xobj.dict.get('Resources') || resources,
dependencyArray); queue, dependencyArray);
// Add the dependencies that are required to execute the // Add the dependencies that are required to execute the
// codeIR. // codeIR.
@ -481,8 +485,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
properties.cidToGidMap = this.readCidToGidMap(cidToGidMap); properties.cidToGidMap = this.readCidToGidMap(cidToGidMap);
} }
var flags = properties.flags;
var differences = []; var differences = [];
var baseEncoding = Encodings.StandardEncoding; var baseEncoding = !!(flags & FontFlags.Symbolic) ?
Encodings.symbolsEncoding : Encodings.StandardEncoding;
var hasEncoding = dict.has('Encoding'); var hasEncoding = dict.has('Encoding');
if (hasEncoding) { if (hasEncoding) {
var encoding = xref.fetchIfRef(dict.get('Encoding')); var encoding = xref.fetchIfRef(dict.get('Encoding'));
@ -758,10 +764,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
baseFontName = baseFontName.name.replace(/[,_]/g, '-'); baseFontName = baseFontName.name.replace(/[,_]/g, '-');
var metrics = this.getBaseFontMetrics(baseFontName); var metrics = this.getBaseFontMetrics(baseFontName);
// Simulating descriptor flags attribute
var fontNameWoStyle = baseFontName.split('-')[0];
var flags = (serifFonts[fontNameWoStyle] ||
(fontNameWoStyle.search(/serif/gi) != -1) ? FontFlags.Serif : 0) |
(symbolsFonts[fontNameWoStyle] ? FontFlags.Symbolic :
FontFlags.Nonsymbolic);
var properties = { var properties = {
type: type.name, type: type.name,
widths: metrics.widths, widths: metrics.widths,
defaultWidth: metrics.defaultWidth, defaultWidth: metrics.defaultWidth,
flags: flags,
firstChar: 0, firstChar: 0,
lastChar: maxCharIndex lastChar: maxCharIndex
}; };
@ -773,11 +787,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
properties: properties properties: properties
}; };
} }
} }
// According to the spec if 'FontDescriptor' is declared, 'FirstChar', // According to the spec if 'FontDescriptor' is declared, 'FirstChar',
// 'LastChar' and 'Widths' should exists too, but some PDF encoders seems // 'LastChar' and 'Widths' should exist too, but some PDF encoders seem
// to ignore this rule when a variant of a standart font is used. // to ignore this rule when a variant of a standart font is used.
// TODO Fill the width array depending on which of the base font this is // TODO Fill the width array depending on which of the base font this is
// a variant. // a variant.

View File

@ -19,6 +19,18 @@ var kPDFGlyphSpaceUnits = 1000;
// Until hinting is fully supported this constant can be used // Until hinting is fully supported this constant can be used
var kHintingEnabled = false; var kHintingEnabled = false;
var FontFlags = {
FixedPitch: 1,
Serif: 2,
Symbolic: 4,
Script: 8,
Nonsymbolic: 32,
Italic: 64,
AllCap: 65536,
SmallCap: 131072,
ForceBold: 262144
};
var Encodings = { var Encodings = {
get ExpertEncoding() { get ExpertEncoding() {
return shadow(this, 'ExpertEncoding', ['', '', '', '', '', '', '', '', '', return shadow(this, 'ExpertEncoding', ['', '', '', '', '', '', '', '', '',
@ -160,19 +172,20 @@ var Encodings = {
'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore',
'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'quoteleft', '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', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'braceleft', 'bar', 'braceright', 'asciitilde', '', '', 'exclamdown', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '',
'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft', '', '', '', '', '', '', '', '', '', '', 'exclamdown', 'cent', 'sterling',
'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', 'daggerdbl', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle',
'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi',
'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'fl', '', 'endash', 'dagger', 'daggerdbl', 'periodcentered', '',
'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright',
'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '',
'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', 'emdash', '', '', '', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent',
'', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron',
'ordfeminine', '', '', '', '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', 'lslash', 'AE', '', 'ordfeminine', '', '', '', '', 'Lslash', 'Oslash', 'OE',
'oslash', 'oe', 'germandbls' 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '',
'lslash', 'oslash', 'oe', 'germandbls'
]); ]);
}, },
get WinAnsiEncoding() { get WinAnsiEncoding() {
@ -339,6 +352,21 @@ var stdFontMap = {
'TimesNewRomanPSMT-Italic': 'Times-Italic' 'TimesNewRomanPSMT-Italic': 'Times-Italic'
}; };
/**
* Holds the map of the non-standard fonts that might be included as a standard
* fonts without glyph data.
*/
var nonStdFontMap = {
'ComicSansMS': 'Comic Sans MS',
'ComicSansMS-Bold': 'Comic Sans MS-Bold',
'ComicSansMS-BoldItalic': 'Comic Sans MS-BoldItalic',
'ComicSansMS-Italic': 'Comic Sans MS-Italic',
'LucidaConsole': 'Courier',
'LucidaConsole-Bold': 'Courier-Bold',
'LucidaConsole-BoldItalic': 'Courier-BoldOblique',
'LucidaConsole-Italic': 'Courier-Oblique'
};
var serifFonts = { var serifFonts = {
'Adobe Jenson': true, 'Adobe Text': true, 'Albertus': true, 'Adobe Jenson': true, 'Adobe Text': true, 'Albertus': true,
'Aldus': true, 'Alexandria': true, 'Algerian': true, 'Aldus': true, 'Alexandria': true, 'Algerian': true,
@ -386,6 +414,23 @@ var serifFonts = {
'Wide Latin': true, 'Windsor': true, 'XITS': true 'Wide Latin': true, 'Windsor': true, 'XITS': true
}; };
var symbolsFonts = {
'Dingbats': true, 'Symbol': true, 'ZapfDingbats': true
};
// Some characters, e.g. copyrightserif, mapped to the private use area and
// might not be displayed using standard fonts. Mapping/hacking well-known chars
// to the similar equivalents in the normal characters range.
function mapPrivateUseChars(code) {
switch (code) {
case 0xF8E9: // copyrightsans
case 0xF6D9: // copyrightserif
return 0x00A9; // copyright
default:
return code;
}
}
var FontLoader = { var FontLoader = {
listeningForFontLoad: false, listeningForFontLoad: false,
@ -528,7 +573,8 @@ var FontLoader = {
src += ' window.onload = function fontLoaderOnload() {\n'; src += ' window.onload = function fontLoaderOnload() {\n';
src += ' parent.postMessage(JSON.stringify(fontNames), "*");\n'; src += ' parent.postMessage(JSON.stringify(fontNames), "*");\n';
src += ' }'; src += ' }';
src += '</script></head><body>'; // Hack so the end script tag isn't counted if this is inline JS.
src += '</scr' + 'ipt></head><body>';
for (var i = 0, ii = names.length; i < ii; ++i) { for (var i = 0, ii = names.length; i < ii; ++i) {
src += '<p style="font-family:\'' + names[i] + '\'">Hi</p>'; src += '<p style="font-family:\'' + names[i] + '\'">Hi</p>';
} }
@ -742,7 +788,8 @@ var Font = (function FontClosure() {
var names = name.split('+'); var names = name.split('+');
names = names.length > 1 ? names[1] : names[0]; names = names.length > 1 ? names[1] : names[0];
names = names.split(/[-,_]/g)[0]; names = names.split(/[-,_]/g)[0];
this.serif = serifFonts[names] || (name.search(/serif/gi) != -1); this.isSerifFont = !!(properties.flags & FontFlags.Serif);
this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
var type = properties.type; var type = properties.type;
this.type = type; this.type = type;
@ -750,7 +797,7 @@ var Font = (function FontClosure() {
// If the font is to be ignored, register it like an already loaded font // 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. // to avoid the cost of waiting for it be be loaded by the platform.
if (properties.ignore) { if (properties.ignore) {
this.loadedName = this.serif ? 'serif' : 'sans-serif'; this.loadedName = this.isSerifFont ? 'serif' : 'sans-serif';
this.loading = false; this.loading = false;
return; return;
} }
@ -780,7 +827,7 @@ var Font = (function FontClosure() {
// The file data is not specified. Trying to fix the font name // The file data is not specified. Trying to fix the font name
// to be used with the canvas.font. // to be used with the canvas.font.
var fontName = name.replace(/[,_]/g, '-'); var fontName = name.replace(/[,_]/g, '-');
fontName = stdFontMap[fontName] || fontName; fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;
this.bold = (fontName.search(/bold/gi) != -1); this.bold = (fontName.search(/bold/gi) != -1);
this.italic = (fontName.search(/oblique/gi) != -1) || this.italic = (fontName.search(/oblique/gi) != -1) ||
@ -830,7 +877,6 @@ var Font = (function FontClosure() {
this.widthMultiplier = !properties.fontMatrix ? 1.0 : this.widthMultiplier = !properties.fontMatrix ? 1.0 :
1.0 / properties.fontMatrix[0]; 1.0 / properties.fontMatrix[0];
this.encoding = properties.baseEncoding; this.encoding = properties.baseEncoding;
this.hasShortCmap = properties.hasShortCmap;
this.loadedName = getUniqueName(); this.loadedName = getUniqueName();
this.loading = true; this.loading = true;
}; };
@ -1800,7 +1846,32 @@ var Font = (function FontClosure() {
this.useToUnicode = true; this.useToUnicode = true;
} }
} }
properties.hasShortCmap = hasShortCmap;
if (hasShortCmap && this.hasEncoding && !this.isSymbolicFont) {
// Re-encode short map encoding to unicode -- that simplifies the
// resolution of MacRoman encoded glyphs logic for TrueType fonts:
// copying all characters to private use area, all mapping all known
// glyphs to the unicodes. The glyphs and ids arrays will grow.
var usedUnicodes = [];
for (var i = 0, ii = glyphs.length; i < ii; i++) {
var code = glyphs[i].unicode;
glyphs[i].unicode += kCmapGlyphOffset;
var glyphName = properties.baseEncoding[code];
if (glyphName in GlyphsUnicode) {
var unicode = GlyphsUnicode[glyphName];
if (unicode in usedUnicodes)
continue;
usedUnicodes[unicode] = true;
glyphs.push({
unicode: unicode,
code: glyphs[i].code
});
ids.push(ids[i]);
}
}
}
// remove glyph references outside range of avaialable glyphs // remove glyph references outside range of avaialable glyphs
for (var i = 0, ii = ids.length; i < ii; i++) { for (var i = 0, ii = ids.length; i < ii; i++) {
@ -2092,10 +2163,11 @@ var Font = (function FontClosure() {
window.btoa(data) + ');'); window.btoa(data) + ');');
var rule = "@font-face { font-family:'" + fontName + "';src:" + url + '}'; var rule = "@font-face { font-family:'" + fontName + "';src:" + url + '}';
var styleElement = document.createElement('style');
document.documentElement.getElementsByTagName('head')[0].appendChild( document.documentElement.getElementsByTagName('head')[0].appendChild(
document.createElement('style')); styleElement);
var styleSheet = document.styleSheets[document.styleSheets.length - 1]; var styleSheet = styleElement.sheet;
styleSheet.insertRule(rule, styleSheet.cssRules.length); styleSheet.insertRule(rule, styleSheet.cssRules.length);
return rule; return rule;
@ -2141,7 +2213,7 @@ var Font = (function FontClosure() {
case 'CIDFontType0': case 'CIDFontType0':
if (this.noUnicodeAdaptation) { if (this.noUnicodeAdaptation) {
width = this.widths[this.unicodeToCID[charcode] || charcode]; width = this.widths[this.unicodeToCID[charcode] || charcode];
unicode = charcode; unicode = mapPrivateUseChars(charcode);
break; break;
} }
unicode = this.toUnicode[charcode] || charcode; unicode = this.toUnicode[charcode] || charcode;
@ -2149,7 +2221,7 @@ var Font = (function FontClosure() {
case 'CIDFontType2': case 'CIDFontType2':
if (this.noUnicodeAdaptation) { if (this.noUnicodeAdaptation) {
width = this.widths[this.unicodeToCID[charcode] || charcode]; width = this.widths[this.unicodeToCID[charcode] || charcode];
unicode = charcode; unicode = mapPrivateUseChars(charcode);
break; break;
} }
unicode = this.toUnicode[charcode] || charcode; unicode = this.toUnicode[charcode] || charcode;
@ -2159,7 +2231,7 @@ var Font = (function FontClosure() {
if (!isNum(width)) if (!isNum(width))
width = this.widths[glyphName]; width = this.widths[glyphName];
if (this.noUnicodeAdaptation) { if (this.noUnicodeAdaptation) {
unicode = GlyphsUnicode[glyphName] || charcode; unicode = mapPrivateUseChars(GlyphsUnicode[glyphName] || charcode);
break; break;
} }
unicode = this.glyphNameMap[glyphName] || unicode = this.glyphNameMap[glyphName] ||
@ -2184,19 +2256,14 @@ var Font = (function FontClosure() {
unicode = GlyphsUnicode[glyphName] || charcode; unicode = GlyphsUnicode[glyphName] || charcode;
break; break;
} }
if (!this.hasEncoding) { if (!this.hasEncoding || this.isSymbolicFont) {
unicode = this.useToUnicode ? this.toUnicode[charcode] : charcode; unicode = this.useToUnicode ? this.toUnicode[charcode] : charcode;
break; break;
} }
if (this.hasShortCmap && false) {
var j = Encodings.MacRomanEncoding.indexOf(glyphName); // MacRoman encoding address by re-encoding the cmap table
unicode = j >= 0 ? j : unicode = glyphName in this.glyphNameMap ?
this.glyphNameMap[glyphName]; this.glyphNameMap[glyphName] : GlyphsUnicode[glyphName];
} else {
unicode = glyphName in GlyphsUnicode ?
GlyphsUnicode[glyphName] :
this.glyphNameMap[glyphName];
}
break; break;
default: default:
warn('Unsupported font type: ' + this.type); warn('Unsupported font type: ' + this.type);
@ -2553,7 +2620,13 @@ var Type1Parser = function type1Parser() {
while (str[index++] != ']') while (str[index++] != ']')
count++; count++;
var array = str.substr(start, count).split(' '); str = str.substr(start, count);
str = str.trim();
// Remove adjacent spaces
str = str.replace(/\s+/g, ' ');
var array = str.split(' ');
for (var i = 0, ii = array.length; i < ii; i++) for (var i = 0, ii = array.length; i < ii; i++)
array[i] = parseFloat(array[i] || 0); array[i] = parseFloat(array[i] || 0);
return array; return array;
@ -3299,15 +3372,9 @@ var Type2CFF = (function Type2CFFClosure() {
inverseEncoding[encoding[charcode]] = charcode | 0; inverseEncoding[encoding[charcode]] = charcode | 0;
for (var i = 0, ii = charsets.length; i < ii; i++) { for (var i = 0, ii = charsets.length; i < ii; i++) {
var glyph = charsets[i]; var glyph = charsets[i];
if (glyph == '.notdef') { if (glyph == '.notdef')
charstrings.push({
unicode: 0,
code: 0,
gid: i,
glyph: glyph
});
continue; continue;
}
var code = inverseEncoding[i]; var code = inverseEncoding[i];
if (!code || isSpecialUnicode(code)) { if (!code || isSpecialUnicode(code)) {
unassignedUnicodeItems.push(i); unassignedUnicodeItems.push(i);
@ -3560,7 +3627,7 @@ var Type2CFF = (function Type2CFFClosure() {
dict['cidOperatorPresent'] = true; dict['cidOperatorPresent'] = true;
break; break;
default: default:
TODO('interpret top dict key'); TODO('interpret top dict key: ' + key);
} }
} }
return dict; return dict;

View File

@ -125,109 +125,99 @@ var PDFFunction = (function PDFFunctionClosure() {
else else
decode = toMultiArray(decode); 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); var samples = this.getSampleArray(size, outputSize, bps, str);
return [ return [
CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size, CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size,
outputSize, bps, range, inputMul, idxMul, nSamples outputSize, Math.pow(2, bps) - 1, range
]; ];
}, },
constructSampledFromIR: function pdfFunctionConstructSampledFromIR(IR) { constructSampledFromIR: function pdfFunctionConstructSampledFromIR(IR) {
var inputSize = IR[1]; // See chapter 3, page 109 of the PDF reference
var domain = IR[2]; function interpolate(x, xmin, xmax, ymin, ymax) {
var encode = IR[3]; return ymin + ((x - xmin) * ((ymax - ymin) / (xmax - xmin)));
var decode = IR[4]; }
var samples = IR[5];
var size = IR[6];
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) { return function constructSampledFromIRResult(args) {
if (inputSize != args.length) // See chapter 3, page 110 of the PDF reference.
var m = IR[1];
var domain = IR[2];
var encode = IR[3];
var decode = IR[4];
var samples = IR[5];
var size = IR[6];
var n = IR[7];
var mask = IR[8];
var range = IR[9];
if (m != args.length)
error('Incorrect number of arguments: ' + inputSize + ' != ' + error('Incorrect number of arguments: ' + inputSize + ' != ' +
args.length); 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;
// map input values into sample array var x = args;
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];
}
// for each output, do m-linear interpolation // Building the cube vertices: its part and sample index
for (i = 0; i < outputSize; ++i) { // http://rjwagner49.com/Mathematics/Interpolation.pdf
var cubeVertices = 1 << m;
var cubeN = new Float64Array(cubeVertices);
var cubeVertex = new Uint32Array(cubeVertices);
for (var j = 0; j < cubeVertices; j++)
cubeN[j] = 1;
// pull 2^m values out of the sample array var k = n, pos = 1;
for (j = 0; j < (1 << inputSize); ++j) { // Map x_i to y_j for 0 <= i < m using the sampled function.
idx = i; for (var i = 0; i < m; ++i) {
for (k = 0, t = j; k < inputSize; ++k, t >>= 1) { // x_i' = min(max(x_i, Domain_2i), Domain_2i+1)
idx += idxMul[k] * (e[k][t & 1]); var domain_2i = domain[i][0];
} var domain_2i_1 = domain[i][1];
if (idx >= 0 && idx < nSamples) { var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1);
sBuf[j] = samples[idx];
// e_i = Interpolate(x_i', Domain_2i, Domain_2i+1,
// Encode_2i, Encode_2i+1)
var e = interpolate(xi, domain_2i, domain_2i_1,
encode[i][0], encode[i][1]);
// e_i' = min(max(e_i, 0), Size_i - 1)
var size_i = size[i];
e = Math.min(Math.max(e, 0), size_i - 1);
// Adjusting the cube: N and vertex sample index
var e0 = e < size_i - 1 ? Math.floor(e) : e - 1; // e1 = e0 + 1;
var n0 = e0 + 1 - e; // (e1 - e) / (e1 - e0);
var n1 = e - e0; // (e - e0) / (e1 - e0);
var offset0 = e0 * k;
var offset1 = offset0 + k; // e1 * k
for (var j = 0; j < cubeVertices; j++) {
if (j & pos) {
cubeN[j] *= n1;
cubeVertex[j] += offset1;
} else { } else {
sBuf[j] = 0; // TODO Investigate if this is what Adobe does cubeN[j] *= n0;
cubeVertex[j] += offset0;
} }
} }
// do m sets of interpolations k *= size_i;
for (j = 0, t = (1 << inputSize); j < inputSize; ++j, t >>= 1) { pos <<= 1;
for (k = 0; k < t; k += 2) {
sBuf[k >> 1] = efrac0[j] * sBuf[k] + efrac1[j] * sBuf[k + 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 out;
var y = new Float64Array(n);
for (var j = 0; j < n; ++j) {
// Sum all cube vertices' samples portions
var rj = 0;
for (var i = 0; i < cubeVertices; i++)
rj += samples[cubeVertex[i] + j] * cubeN[i];
// r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1,
// Decode_2j, Decode_2j+1)
rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]);
// y_j = min(max(r_j, range_2j), range_2j+1)
y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]);
}
return y;
} }
}, },

1854
src/jpx.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -287,74 +287,69 @@ var XRef = (function XRefClosure() {
XRef.prototype = { XRef.prototype = {
readXRefTable: function readXRefTable(parser) { readXRefTable: function readXRefTable(parser) {
// Example of cross-reference table:
// xref
// 0 1 <-- subsection header (first obj #, obj count)
// 0000000000 65535 f <-- actual object (offset, generation #, f/n)
// 23 2 <-- subsection header ... and so on ...
// 0000025518 00002 n
// 0000025635 00000 n
// trailer
// ...
// Outer loop is over subsection headers
var obj; var obj;
while (true) { while (!isCmd(obj = parser.getObj(), 'trailer')) {
if (isCmd(obj = parser.getObj(), 'trailer')) var first = obj,
break; count = parser.getObj();
if (!isInt(obj))
error('Invalid XRef table'); if (!isInt(first) || !isInt(count))
var first = obj; error('Invalid XRef table: wrong types in subsection header');
if (!isInt(obj = parser.getObj()))
error('Invalid XRef table'); // Inner loop is over objects themselves
var n = obj; for (var i = 0; i < count; i++) {
if (first < 0 || n < 0 || (first + n) != ((first + n) | 0))
error('Invalid XRef table: ' + first + ', ' + n);
for (var i = first; i < first + n; ++i) {
var entry = {}; var entry = {};
if (!isInt(obj = parser.getObj())) entry.offset = parser.getObj();
error('Invalid XRef table: ' + first + ', ' + n); entry.gen = parser.getObj();
entry.offset = obj; var type = parser.getObj();
if (!isInt(obj = parser.getObj()))
error('Invalid XRef table: ' + first + ', ' + n); if (isCmd(type, 'f'))
entry.gen = obj;
obj = parser.getObj();
if (isCmd(obj, 'n')) {
entry.uncompressed = true;
} else if (isCmd(obj, 'f')) {
entry.free = true; entry.free = true;
} else { else if (isCmd(type, 'n'))
error('Invalid XRef table: ' + first + ', ' + n); entry.uncompressed = true;
}
if (!this.entries[i]) { // Validate entry obj
// In some buggy PDF files the xref table claims to start at 1 if (!isInt(entry.offset) || !isInt(entry.gen) ||
// instead of 0. !(entry.free || entry.uncompressed)) {
if (i == 1 && first == 1 && error('Invalid entry in XRef subsection: ' + first + ', ' + count);
entry.offset == 0 && entry.gen == 65535 && entry.free) {
i = first = 0;
}
this.entries[i] = entry;
} }
if (!this.entries[i + first])
this.entries[i + first] = entry;
} }
} }
// read the trailer dictionary // Sanity check: as per spec, first object must have these properties
var dict; if (this.entries[0] &&
if (!isDict(dict = parser.getObj())) !(this.entries[0].gen === 65535 && this.entries[0].free))
error('Invalid XRef table'); error('Invalid XRef table: unexpected first object');
// get the 'Prev' pointer // Sanity check
var prev; if (!isCmd(obj, 'trailer'))
obj = dict.get('Prev'); error('Invalid XRef table: could not find trailer dictionary');
if (isInt(obj)) {
prev = obj;
} else if (isRef(obj)) {
// certain buggy PDF generators generate "/Prev NNN 0 R" instead
// of "/Prev NNN"
prev = obj.num;
}
if (prev) {
this.readXRef(prev);
}
// check for 'XRefStm' key // Read trailer dictionary, e.g.
if (isInt(obj = dict.get('XRefStm'))) { // trailer
var pos = obj; // << /Size 22
// ignore previously loaded xref streams (possible infinite recursion) // /Root 20R
if (!(pos in this.xrefstms)) { // /Info 10R
this.xrefstms[pos] = 1; // /ID [ <81b14aafa313db63dbd6f981e49f94f4> ]
this.readXRef(pos); // >>
} // The parser goes through the entire stream << ... >> and provides
} // a getter interface for the key-value table
var dict = parser.getObj();
if (!isDict(dict))
error('Invalid XRef table: could not parse trailer dictionary');
return dict; return dict;
}, },
@ -407,9 +402,6 @@ var XRef = (function XRefClosure() {
} }
range.splice(0, 2); range.splice(0, 2);
} }
var prev = streamParameters.get('Prev');
if (isInt(prev))
this.readXRef(prev);
return streamParameters; return streamParameters;
}, },
indexObjects: function indexObjects() { indexObjects: function indexObjects() {
@ -529,22 +521,47 @@ var XRef = (function XRefClosure() {
try { try {
var parser = new Parser(new Lexer(stream), true); var parser = new Parser(new Lexer(stream), true);
var obj = parser.getObj(); var obj = parser.getObj();
var dict;
// parse an old-style xref table // Get dictionary
if (isCmd(obj, 'xref')) if (isCmd(obj, 'xref')) {
return this.readXRefTable(parser); // Parse end-of-file XRef
dict = this.readXRefTable(parser);
// parse an xref stream // Recursively get other XRefs 'XRefStm', if any
if (isInt(obj)) { obj = dict.get('XRefStm');
if (isInt(obj)) {
var pos = obj;
// ignore previously loaded xref streams
// (possible infinite recursion)
if (!(pos in this.xrefstms)) {
this.xrefstms[pos] = 1;
this.readXRef(pos);
}
}
} else if (isInt(obj)) {
// Parse in-stream XRef
if (!isInt(parser.getObj()) || if (!isInt(parser.getObj()) ||
!isCmd(parser.getObj(), 'obj') || !isCmd(parser.getObj(), 'obj') ||
!isStream(obj = parser.getObj())) { !isStream(obj = parser.getObj())) {
error('Invalid XRef stream'); error('Invalid XRef stream');
} }
return this.readXRefStream(obj); dict = this.readXRefStream(obj);
} }
// Recursively get previous dictionary, if any
obj = dict.get('Prev');
if (isInt(obj))
this.readXRef(obj);
else if (isRef(obj)) {
// The spec says Prev must not be a reference, i.e. "/Prev NNN"
// This is a fallback for non-compliant PDFs, i.e. "/Prev NNN 0 R"
this.readXRef(obj.num);
}
return dict;
} catch (e) { } catch (e) {
log('Reading of the xref table/stream failed: ' + e); log('(while reading XRef): ' + e);
} }
warn('Indexing all PDF objects'); warn('Indexing all PDF objects');
@ -574,7 +591,7 @@ var XRef = (function XRefClosure() {
var stream, parser; var stream, parser;
if (e.uncompressed) { if (e.uncompressed) {
if (e.gen != gen) if (e.gen != gen)
throw ('inconsistent generation in XRef'); error('inconsistent generation in XRef');
stream = this.stream.makeSubStream(e.offset); stream = this.stream.makeSubStream(e.offset);
parser = new Parser(new Lexer(stream), true, this); parser = new Parser(new Lexer(stream), true, this);
var obj1 = parser.getObj(); var obj1 = parser.getObj();
@ -703,7 +720,7 @@ var PDFObjects = (function PDFObjectsClosure() {
// If there isn't an object yet or the object isn't resolved, then the // If there isn't an object yet or the object isn't resolved, then the
// data isn't ready yet! // data isn't ready yet!
if (!obj || !obj.isResolved) { if (!obj || !obj.isResolved) {
throw 'Requesting object that isn\'t resolved yet ' + objId; error('Requesting object that isn\'t resolved yet ' + objId);
return null; return null;
} else { } else {
return obj.data; return obj.data;

View File

@ -240,6 +240,10 @@ var Parser = (function ParserClosure() {
var bytes = stream.getBytes(length); var bytes = stream.getBytes(length);
return new JpegStream(bytes, stream.dict, this.xref); return new JpegStream(bytes, stream.dict, this.xref);
} }
if (name == 'JPXDecode' || name == 'JPX') {
var bytes = stream.getBytes(length);
return new JpxStream(bytes, stream.dict);
}
if (name == 'ASCII85Decode' || name == 'A85') { if (name == 'ASCII85Decode' || name == 'A85') {
return new Ascii85Stream(stream); return new Ascii85Stream(stream);
} }
@ -249,6 +253,9 @@ var Parser = (function ParserClosure() {
if (name == 'CCITTFaxDecode' || name == 'CCF') { if (name == 'CCITTFaxDecode' || name == 'CCF') {
return new CCITTFaxStream(stream, params); return new CCITTFaxStream(stream, params);
} }
if (name == 'RunLengthDecode') {
return new RunLengthStream(stream);
}
warn('filter "' + name + '" not supported yet'); warn('filter "' + name + '" not supported yet');
return stream; return stream;
} }

View File

@ -94,9 +94,9 @@ Shadings.RadialAxial = (function RadialAxialClosure() {
var colorStops = []; var colorStops = [];
for (var i = t0; i <= t1; i += step) { for (var i = t0; i <= t1; i += step) {
var color = fn([i]); var rgbColor = cs.getRgb(fn([i]));
var rgbColor = Util.makeCssRgb.apply(this, cs.getRgb(color)); var cssColor = Util.makeCssRgb(rgbColor[0], rgbColor[1], rgbColor[2]);
colorStops.push([(i - t0) / diff, rgbColor]); colorStops.push([(i - t0) / diff, cssColor]);
} }
this.colorStops = colorStops; this.colorStops = colorStops;
@ -234,9 +234,9 @@ var TilingPattern = (function TilingPatternClosure() {
tmpCtx.strokeStyle = ctx.strokeStyle; tmpCtx.strokeStyle = ctx.strokeStyle;
break; break;
case PaintType.UNCOLORED: case PaintType.UNCOLORED:
color = Util.makeCssRgb.apply(this, color); var cssColor = Util.makeCssRgb(this, color[0], color[1], color[2]);
tmpCtx.fillStyle = color; tmpCtx.fillStyle = cssColor;
tmpCtx.strokeStyle = color; tmpCtx.strokeStyle = cssColor;
break; break;
default: default:
error('Unsupported paint type: ' + paintType); error('Unsupported paint type: ' + paintType);

View File

@ -821,21 +821,25 @@ var JpegStream = (function JpegStreamClosure() {
JpegStream.prototype.ensureBuffer = function jpegStreamEnsureBuffer(req) { JpegStream.prototype.ensureBuffer = function jpegStreamEnsureBuffer(req) {
if (this.bufferLength) if (this.bufferLength)
return; return;
var jpegImage = new JpegImage(); try {
if (this.colorTransform != -1) var jpegImage = new JpegImage();
jpegImage.colorTransform = this.colorTransform; if (this.colorTransform != -1)
jpegImage.parse(this.bytes); jpegImage.colorTransform = this.colorTransform;
var width = jpegImage.width; jpegImage.parse(this.bytes);
var height = jpegImage.height; var width = jpegImage.width;
var data = jpegImage.getData(width, height); var height = jpegImage.height;
this.buffer = data; var data = jpegImage.getData(width, height);
this.bufferLength = data.length; this.buffer = data;
this.bufferLength = data.length;
} catch (e) {
error('JPEG error: ' + e);
}
}; };
JpegStream.prototype.getIR = function jpegStreamGetIR() { JpegStream.prototype.getIR = function jpegStreamGetIR() {
return bytesToString(this.bytes); return bytesToString(this.bytes);
}; };
JpegStream.prototype.getChar = function jpegStreamGetChar() { JpegStream.prototype.getChar = function jpegStreamGetChar() {
error('internal error: getChar is not valid on JpegStream'); error('internal error: getChar is not valid on JpegStream');
}; };
/** /**
* Checks if the image can be decoded and displayed by the browser without any * Checks if the image can be decoded and displayed by the browser without any
@ -869,6 +873,107 @@ var JpegStream = (function JpegStreamClosure() {
return JpegStream; return JpegStream;
})(); })();
/**
* For JPEG 2000's we use a library to decode these images and
* the stream behaves like all the other DecodeStreams.
*/
var JpxStream = (function JpxStreamClosure() {
function JpxStream(bytes, dict) {
this.dict = dict;
this.bytes = bytes;
DecodeStream.call(this);
}
JpxStream.prototype = Object.create(DecodeStream.prototype);
JpxStream.prototype.ensureBuffer = function jpxStreamEnsureBuffer(req) {
if (this.bufferLength)
return;
var jpxImage = new JpxImage();
jpxImage.parse(this.bytes);
var width = jpxImage.width;
var height = jpxImage.height;
var componentsCount = jpxImage.componentsCount;
if (componentsCount != 1 && componentsCount != 3 && componentsCount != 4)
error('JPX with ' + componentsCount + ' components is not supported');
var data = new Uint8Array(width * height * componentsCount);
for (var k = 0, kk = jpxImage.tiles.length; k < kk; k++) {
var tileCompoments = jpxImage.tiles[k];
var tileWidth = tileCompoments[0].width;
var tileHeight = tileCompoments[0].height;
var tileLeft = tileCompoments[0].left;
var tileTop = tileCompoments[0].top;
var dataPosition, sourcePosition, data0, data1, data2, data3, rowFeed;
switch (componentsCount) {
case 1:
data0 = tileCompoments[0].items;
dataPosition = width * tileTop + tileLeft;
rowFeed = width - tileWidth;
sourcePosition = 0;
for (var j = 0; j < tileHeight; j++) {
for (var i = 0; i < tileWidth; i++)
data[dataPosition++] = data0[sourcePosition++];
dataPosition += rowFeed;
}
break;
case 3:
data0 = tileCompoments[0].items;
data1 = tileCompoments[1].items;
data2 = tileCompoments[2].items;
dataPosition = (width * tileTop + tileLeft) * 3;
rowFeed = (width - tileWidth) * 3;
sourcePosition = 0;
for (var j = 0; j < tileHeight; j++) {
for (var i = 0; i < tileWidth; i++) {
data[dataPosition++] = data0[sourcePosition];
data[dataPosition++] = data1[sourcePosition];
data[dataPosition++] = data2[sourcePosition];
sourcePosition++;
}
dataPosition += rowFeed;
}
break;
case 4:
data0 = tileCompoments[0].items;
data1 = tileCompoments[1].items;
data2 = tileCompoments[2].items;
data3 = tileCompoments[3].items;
dataPosition = (width * tileTop + tileLeft) * 4;
rowFeed = (width - tileWidth) * 4;
sourcePosition = 0;
for (var j = 0; j < tileHeight; j++) {
for (var i = 0; i < tileWidth; i++) {
data[dataPosition++] = data0[sourcePosition];
data[dataPosition++] = data1[sourcePosition];
data[dataPosition++] = data2[sourcePosition];
data[dataPosition++] = data3[sourcePosition];
sourcePosition++;
}
dataPosition += rowFeed;
}
break;
}
}
this.buffer = data;
this.bufferLength = data.length;
};
JpxStream.prototype.getChar = function jpxStreamGetChar() {
error('internal error: getChar is not valid on JpxStream');
};
return JpxStream;
})();
var DecryptStream = (function DecryptStreamClosure() { var DecryptStream = (function DecryptStreamClosure() {
function DecryptStream(str, decrypt) { function DecryptStream(str, decrypt) {
this.str = str; this.str = str;
@ -1041,6 +1146,51 @@ var AsciiHexStream = (function AsciiHexStreamClosure() {
return AsciiHexStream; return AsciiHexStream;
})(); })();
var RunLengthStream = (function RunLengthStreamClosure() {
function RunLengthStream(str) {
this.str = str;
this.dict = str.dict;
DecodeStream.call(this);
}
RunLengthStream.prototype = Object.create(DecodeStream.prototype);
RunLengthStream.prototype.readBlock = function runLengthStreamReadBlock() {
// The repeatHeader has following format. The first byte defines type of run
// and amount of bytes to repeat/copy: n = 0 through 127 - copy next n bytes
// (in addition to the second byte from the header), n = 129 through 255 -
// duplicate the second byte from the header (257 - n) times, n = 128 - end.
var repeatHeader = this.str.getBytes(2);
if (!repeatHeader || repeatHeader.length < 2 || repeatHeader[0] == 128) {
this.eof = true;
return;
}
var bufferLength = this.bufferLength;
var n = repeatHeader[0];
if (n < 128) {
// copy n bytes
var buffer = this.ensureBuffer(bufferLength + n + 1);
buffer[bufferLength++] = repeatHeader[1];
if (n > 0) {
var source = this.str.getBytes(n);
buffer.set(source, bufferLength);
bufferLength += n;
}
} else {
n = 257 - n;
var b = repeatHeader[1];
var buffer = this.ensureBuffer(bufferLength + n + 1);
for (var i = 0; i < n; i++)
buffer[bufferLength++] = b;
}
this.bufferLength = bufferLength;
};
return RunLengthStream;
})();
var CCITTFaxStream = (function CCITTFaxStreamClosure() { var CCITTFaxStream = (function CCITTFaxStreamClosure() {
var ccittEOL = -2; var ccittEOL = -2;

View File

@ -78,21 +78,43 @@ var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
var Util = (function UtilClosure() { var Util = (function UtilClosure() {
function Util() {} function Util() {}
Util.makeCssRgb = function makergb(r, g, b) { Util.makeCssRgb = function makergb(r, g, b) {
var ri = (255 * r) | 0, gi = (255 * g) | 0, bi = (255 * b) | 0; var ri = (255 * r) | 0, gi = (255 * g) | 0, bi = (255 * b) | 0;
return 'rgb(' + ri + ',' + gi + ',' + bi + ')'; return 'rgb(' + ri + ',' + gi + ',' + bi + ')';
}; };
Util.makeCssCmyk = function makecmyk(c, m, y, k) { Util.makeCssCmyk = function makecmyk(c, m, y, k) {
c = (new DeviceCmykCS()).getRgb([c, m, y, k]); c = (new DeviceCmykCS()).getRgb([c, m, y, k]);
var ri = (255 * c[0]) | 0, gi = (255 * c[1]) | 0, bi = (255 * c[2]) | 0; var ri = (255 * c[0]) | 0, gi = (255 * c[1]) | 0, bi = (255 * c[2]) | 0;
return 'rgb(' + ri + ',' + gi + ',' + bi + ')'; return 'rgb(' + ri + ',' + gi + ',' + bi + ')';
}; };
// For 2d affine transforms
Util.applyTransform = function apply(p, m) { Util.applyTransform = function apply(p, m) {
var xt = p[0] * m[0] + p[1] * m[2] + m[4]; var xt = p[0] * m[0] + p[1] * m[2] + m[4];
var yt = p[0] * m[1] + p[1] * m[3] + m[5]; var yt = p[0] * m[1] + p[1] * m[3] + m[5];
return [xt, yt]; return [xt, yt];
}; };
// Apply a generic 3d matrix M on a 3-vector v:
// | a b c | | X |
// | d e f | x | Y |
// | g h i | | Z |
// M is assumed to be serialized as [a,b,c,d,e,f,g,h,i],
// with v as [X,Y,Z]
Util.apply3dTransform = function apply3d(m, v) {
return [
m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
m[6] * v[0] + m[7] * v[1] + m[8] * v[2]
];
}
Util.sign = function sign(num) {
return num < 0 ? -1 : 1;
};
return Util; return Util;
})(); })();
@ -255,8 +277,8 @@ var Promise = (function PromiseClosure() {
return; return;
} }
if (this._data !== EMPTY_PROMISE) { if (this._data !== EMPTY_PROMISE) {
throw 'Promise ' + this.name + error('Promise ' + this.name +
': Cannot set the data of a promise twice'; ': Cannot set the data of a promise twice');
} }
this._data = value; this._data = value;
this.hasData = true; this.hasData = true;
@ -268,7 +290,7 @@ var Promise = (function PromiseClosure() {
get data() { get data() {
if (this._data === EMPTY_PROMISE) { if (this._data === EMPTY_PROMISE) {
throw 'Promise ' + this.name + ': Cannot get data that isn\'t set'; error('Promise ' + this.name + ': Cannot get data that isn\'t set');
} }
return this._data; return this._data;
}, },
@ -283,10 +305,10 @@ var Promise = (function PromiseClosure() {
resolve: function promiseResolve(data) { resolve: function promiseResolve(data) {
if (this.isResolved) { if (this.isResolved) {
throw 'A Promise can be resolved only once ' + this.name; error('A Promise can be resolved only once ' + this.name);
} }
if (this.isRejected) { if (this.isRejected) {
throw 'The Promise was already rejected ' + this.name; error('The Promise was already rejected ' + this.name);
} }
this.isResolved = true; this.isResolved = true;
@ -300,10 +322,10 @@ var Promise = (function PromiseClosure() {
reject: function proimseReject(reason) { reject: function proimseReject(reason) {
if (this.isRejected) { if (this.isRejected) {
throw 'A Promise can be rejected only once ' + this.name; error('A Promise can be rejected only once ' + this.name);
} }
if (this.isResolved) { if (this.isResolved) {
throw 'The Promise was already resolved ' + this.name; error('The Promise was already resolved ' + this.name);
} }
this.isRejected = true; this.isRejected = true;
@ -317,7 +339,7 @@ var Promise = (function PromiseClosure() {
then: function promiseThen(callback, errback) { then: function promiseThen(callback, errback) {
if (!callback) { if (!callback) {
throw 'Requiring callback' + this.name; error('Requiring callback' + this.name);
} }
// If the promise is already resolved, call the callback directly. // If the promise is already resolved, call the callback directly.

View File

@ -26,7 +26,7 @@ function MessageHandler(name, comObj) {
delete callbacks[callbackId]; delete callbacks[callbackId];
callback(data.data); callback(data.data);
} else { } else {
throw 'Cannot resolve callback ' + callbackId; error('Cannot resolve callback ' + callbackId);
} }
} else if (data.action in ah) { } else if (data.action in ah) {
var action = ah[data.action]; var action = ah[data.action];
@ -44,7 +44,7 @@ function MessageHandler(name, comObj) {
action[0].call(action[1], data.data); action[0].call(action[1], data.data);
} }
} else { } else {
throw 'Unkown action from worker: ' + data.action; error('Unkown action from worker: ' + data.action);
} }
}; };
} }
@ -53,7 +53,7 @@ MessageHandler.prototype = {
on: function messageHandlerOn(actionName, handler, scope) { on: function messageHandlerOn(actionName, handler, scope) {
var ah = this.actionHandler; var ah = this.actionHandler;
if (ah[actionName]) { if (ah[actionName]) {
throw 'There is already an actionName called "' + actionName + '"'; error('There is already an actionName called "' + actionName + '"');
} }
ah[actionName] = [handler, scope]; ah[actionName] = [handler, scope];
}, },
@ -85,13 +85,6 @@ var WorkerMessageHandler = {
handler.send('test', data instanceof Uint8Array); handler.send('test', data instanceof Uint8Array);
}); });
handler.on('workerSrc', function wphSetupWorkerSrc(data) {
// In development, the `workerSrc` message is handled in the
// `worker_loader.js` file. In production the workerProcessHandler is
// called for this. This servers as a dummy to prevent calling an
// undefined action `workerSrc`.
});
handler.on('doc', function wphSetupDoc(data) { handler.on('doc', function wphSetupDoc(data) {
// Create only the model of the PDFDoc, which is enough for // Create only the model of the PDFDoc, which is enough for
// processing the content of the pdf. // processing the content of the pdf.
@ -116,11 +109,27 @@ var WorkerMessageHandler = {
// Pre compile the pdf page and fetch the fonts/images. // Pre compile the pdf page and fetch the fonts/images.
IRQueue = page.getIRQueue(handler, dependency); IRQueue = page.getIRQueue(handler, dependency);
} catch (e) { } catch (e) {
var minimumStackMessage =
'worker.js: while trying to getPage() and getIRQueue()';
// Turn the error into an obj that can be serialized // Turn the error into an obj that can be serialized
e = { if (typeof e === 'string') {
message: typeof e === 'object' ? e.message : e, e = {
stack: typeof e === 'object' ? e.stack : null message: e,
}; stack: minimumStackMessage
};
} else if (typeof e === 'object') {
e = {
message: e.message || e.toString(),
stack: e.stack || minimumStackMessage
};
} else {
e = {
message: 'Unknown exception type: ' + (typeof e),
stack: minimumStackMessage
};
}
handler.send('page_error', { handler.send('page_error', {
pageNum: pageNum, pageNum: pageNum,
error: e error: e
@ -215,6 +224,7 @@ var workerConsole = {
action: 'console_error', action: 'console_error',
data: args data: args
}); });
throw 'pdf.js execution error';
}, },
time: function time(name) { time: function time(name) {
@ -224,7 +234,7 @@ var workerConsole = {
timeEnd: function timeEnd(name) { timeEnd: function timeEnd(name) {
var time = consoleTimer[name]; var time = consoleTimer[name];
if (time == null) { if (time == null) {
throw 'Unkown timer name ' + name; error('Unkown timer name ' + name);
} }
this.log('Timer:', name, Date.now() - time); this.log('Timer:', name, Date.now() - time);
} }

View File

@ -3,51 +3,31 @@
'use strict'; 'use strict';
function onMessageLoader(evt) { // List of files to include;
// Reset the `onmessage` function as it was only set to call var files = [
// this function the first time a message is passed to the worker 'core.js',
// but shouldn't get called anytime afterwards. 'util.js',
this.onmessage = null; 'canvas.js',
'obj.js',
'function.js',
'charsets.js',
'cidmaps.js',
'colorspace.js',
'crypto.js',
'evaluator.js',
'fonts.js',
'glyphlist.js',
'image.js',
'metrics.js',
'parser.js',
'pattern.js',
'stream.js',
'worker.js',
'../external/jpgjs/jpg.js',
'jpx.js'
];
if (evt.data.action !== 'workerSrc') { // Load all the files.
throw 'Worker expects first message to be `workerSrc`'; for (var i = 0; i < files.length; i++) {
} importScripts(files[i]);
// Content of `PDFJS.workerSrc` as defined on the main thread.
var workerSrc = evt.data.data;
// Extract the directory that contains the source files to load.
// Assuming the source files have the same relative possition as the
// `workerSrc` file.
var dir = workerSrc.substring(0, workerSrc.lastIndexOf('/') + 1);
// List of files to include;
var files = [
'core.js',
'util.js',
'canvas.js',
'obj.js',
'function.js',
'charsets.js',
'cidmaps.js',
'colorspace.js',
'crypto.js',
'evaluator.js',
'fonts.js',
'glyphlist.js',
'image.js',
'metrics.js',
'parser.js',
'pattern.js',
'stream.js',
'worker.js',
'../external/jpgjs/jpg.js'
];
// Load all the files.
for (var i = 0; i < files.length; i++) {
importScripts(dir + files[i]);
}
} }
this.onmessage = onMessageLoader;

3
test/.gitignore vendored
View File

@ -0,0 +1,3 @@
ref/
tmp/

View File

@ -78,6 +78,14 @@ function cleanup() {
} }
} }
function exceptionToString(e) {
if (typeof e !== 'object')
return String(e);
if (!('message' in e))
return JSON.stringify(e);
return e.message + ('stack' in e ? ' at ' + e.stack.split('\n')[0] : '');
}
function nextTask() { function nextTask() {
cleanup(); cleanup();
@ -95,7 +103,7 @@ function nextTask() {
try { try {
task.pdfDoc = new PDFJS.PDFDoc(data); task.pdfDoc = new PDFJS.PDFDoc(data);
} catch (e) { } catch (e) {
failure = 'load PDF doc : ' + e.toString(); failure = 'load PDF doc : ' + exceptionToString(e);
} }
task.pageNum = task.firstPage || 1; task.pageNum = task.firstPage || 1;
nextPage(task, failure); nextPage(task, failure);
@ -185,7 +193,7 @@ function nextPage(task, loadError) {
textLayerBuilder textLayerBuilder
); );
} catch (e) { } catch (e) {
failure = 'page setup : ' + e.toString(); failure = 'page setup : ' + exceptionToString(e);
} }
} }

View File

@ -22,3 +22,5 @@
!issue918.pdf !issue918.pdf
!smaskdim.pdf !smaskdim.pdf
!type4psfunc.pdf !type4psfunc.pdf
!S2.pdf
!zerowidthline.pdf

2549
test/pdfs/S2.pdf Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
http://www.intel.com/content/dam/doc/manual/64-ia-32-architectures-software-developer-vol-1-manual.pdf http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-1-manual.pdf

View File

@ -0,0 +1 @@
http://francois.laustriat.free.fr/test/caravan-d.pdf

View File

@ -0,0 +1 @@
http://ernestinefont.com/wp-content/themes/iA3%201.2.1/assets/pdf/ErnestinePro-InfoGuide.pdf

View File

@ -0,0 +1 @@
http://mcpherrin.ca/code/mozilla/engl208b.pdf

View File

@ -0,0 +1 @@
http://www.faithaliveresources.org/Content/Site135/FilesSamples/105315400440pdf_00000009843.pdf

View File

@ -0,0 +1 @@
https://vmp.ethz.ch/pdfs/diplome/vordiplome/Block%201/Algorithmen_%26_Komplexitaet/AlgoKo_f08_Aufg.pdf

View File

@ -0,0 +1 @@
http://www.cscw2012.org/docs/program.pdf

View File

@ -0,0 +1 @@
http://blog.lassus.se/files/liveprogramming.pdf

BIN
test/pdfs/zerowidthline.pdf Normal file

Binary file not shown.

View File

@ -9,7 +9,7 @@ USAGE_EXAMPLE = "%prog"
# The local web server uses the git repo as the document root. # The local web server uses the git repo as the document root.
DOC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),"..")) DOC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),".."))
ANAL = True GIT_CLONE_CHECK = True
DEFAULT_MANIFEST_FILE = 'test_manifest.json' DEFAULT_MANIFEST_FILE = 'test_manifest.json'
EQLOG_FILE = 'eq.log' EQLOG_FILE = 'eq.log'
BROWSERLOG_FILE = 'browser.log' BROWSERLOG_FILE = 'browser.log'
@ -344,7 +344,7 @@ def verifyPDFs(manifestList):
def setUp(options): def setUp(options):
# Only serve files from a pdf.js clone # Only serve files from a pdf.js clone
assert not ANAL or os.path.isfile('../src/pdf.js') and os.path.isdir('../.git') assert not GIT_CLONE_CHECK or os.path.isfile('../src/pdf.js') and os.path.isdir('../.git')
if options.masterMode and os.path.isdir(TMPDIR): if options.masterMode and os.path.isdir(TMPDIR):
print 'Temporary snapshot dir tmp/ is still around.' print 'Temporary snapshot dir tmp/ is still around.'

View File

@ -19,7 +19,7 @@
}, },
{ "id": "intelisa-eq", { "id": "intelisa-eq",
"file": "pdfs/intelisa.pdf", "file": "pdfs/intelisa.pdf",
"md5": "f5712097d29287a97f1278839814f682", "md5": "83032ccbfdc5a66269b1403971110abe",
"link": true, "link": true,
"pageLimit": 100, "pageLimit": 100,
"rounds": 1, "rounds": 1,
@ -176,7 +176,6 @@
"md5": "eb7b224107205db4fea9f7df0185f77d", "md5": "eb7b224107205db4fea9f7df0185f77d",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"skipPages": [12,31],
"type": "eq" "type": "eq"
}, },
{ "id": "fips197", { "id": "fips197",
@ -389,11 +388,75 @@
"link": true, "link": true,
"type": "load" "type": "load"
}, },
{ "id": "issue1010",
"file": "pdfs/issue1010.pdf",
"md5": "f991ef093484a107fe9f59dff18fc155",
"rounds": 1,
"link": true,
"type": "eq"
},
{ "id": "issue1015", { "id": "issue1015",
"file": "pdfs/issue1015.pdf", "file": "pdfs/issue1015.pdf",
"md5": "b61503d1b445742b665212866afb60e2", "md5": "b61503d1b445742b665212866afb60e2",
"rounds": 1, "rounds": 1,
"link": true, "link": true,
"type": "eq" "type": "eq"
},
{ "id": "issue1096",
"file": "pdfs/issue1096.pdf",
"md5": "7f75d2b4b93c78d401ff39e8c1b00612",
"rounds": 1,
"pageLimit": 10,
"link": true,
"type": "eq"
},
{ "id": "issue1127",
"file": "pdfs/issue1127.pdf",
"md5": "4fb2be5ffefeafda4ba977de2a1bb4d8",
"rounds": 1,
"link": true,
"type": "eq"
},
{ "id": "liveprogramming",
"file": "pdfs/liveprogramming.pdf",
"md5": "7bd4dad1188232ef597d36fd72c33e52",
"rounds": 1,
"pageLimit": 3,
"link": true,
"type": "load"
},
{ "id": "S2-eq",
"file": "pdfs/S2.pdf",
"md5": "d0b6137846df6e0fe058f234a87fb588",
"rounds": 1,
"type": "eq"
},
{ "id": "issue1055",
"file": "pdfs/issue1055.pdf",
"md5": "3ba56c2e48dce81da8669b1b9cf98ff0",
"rounds": 1,
"link": true,
"type": "eq"
},
{ "id": "zerowidthline",
"file": "pdfs/zerowidthline.pdf",
"md5": "295d26e61a85635433f8e4b768953f60",
"rounds": 1,
"link": false,
"type": "eq"
},
{ "id": "issue1133",
"file": "pdfs/issue1133.pdf",
"md5": "d1b61580cb100e3df93d33703af1773a",
"rounds": 1,
"link": true,
"type": "eq"
},
{ "id": "issue1049",
"file": "pdfs/issue1049.pdf",
"md5": "15473fffcdde9fb8f3756a4cf1aab347",
"rounds": 1,
"link": true,
"type": "eq"
} }
] ]

View File

@ -22,6 +22,7 @@
<script type="text/javascript" src="/src/stream.js"></script> <script type="text/javascript" src="/src/stream.js"></script>
<script type="text/javascript" src="/src/worker.js"></script> <script type="text/javascript" src="/src/worker.js"></script>
<script type="text/javascript" src="/external/jpgjs/jpg.js"></script> <script type="text/javascript" src="/external/jpgjs/jpg.js"></script>
<script type="text/javascript" src="/src/jpx.js"></script>
<script type="text/javascript" src="driver.js"></script> <script type="text/javascript" src="driver.js"></script>
<script type="text/javascript"> <script type="text/javascript">

116
test/unit/Makefile Normal file
View File

@ -0,0 +1,116 @@
# Create temporary profile directory name.
TEMP_PROFILE:=$(shell echo `pwd`)/test_reports/temp_profile
# These are the Firefox command line arguments.
FIREFOX_ARGS:=-no-remote -profile $(TEMP_PROFILE)
# These are the Chrome command line arguments.
CHROME_ARGS:=--user-data-dir=$(TEMP_PROFILE) --no-first-run --disable-sync
# Unit test uses the manifest from ref test to determine which browsers will
# be used for running the unit tests.
MANIFEST:=../resources/browser_manifests/browser_manifest.json
# This is a helper command to separate multiple browsers to their own lines
# for an easier sed operation.
SPLIT_LINES:=sed 's|,|,@|g' | tr '@' '\n'
# This is a helper command to join multiple lines together.
JOIN_LINES:=tr -d '\n'
# Fetch the paths to browsers that are going to be used in testing.
# For OS X the path to the binary needs to be added.
# Add the browser arguments for each browser.
# Create random profile directory for each browser.
BROWSERS_PATHS:=$(shell echo `\
sed -n 's|.*"path":\(.*\)|\1,|p' $(MANIFEST) | \
$(JOIN_LINES) \
`)
# The browser_manifest.json file has only the app directory for mac browsers.
# The absolute path to the browser binary needs to be used.
BROWSERS_PATHS_WITH_MAC_CORRECTION:=$(shell echo '$(BROWSERS_PATHS)' | \
$(SPLIT_LINES) | \
sed 's|\(Aurora\.app\)|\1/Contents/MacOS/firefox-bin|' | \
sed 's|\(Firefox.*\.app\)|\1/Contents/MacOS/firefox-bin|' | \
sed 's|\(Google Chrome\.app\)|\1/Contents/MacOS/Google Chrome|' | \
$(JOIN_LINES) \
)
# Replace " with @@@@ so that echoing do not destroy the quotation marks.
QUOTATION_MARK:=\"
SUBSTITUTE_FOR_QUOTATION_MARK:=@@@@
# Each of the browser can have their own separate arguments.
BROWSERS_WITH_ARGUMENTS:=$(shell echo '$(BROWSERS_PATHS_WITH_MAC_CORRECTION)' | \
$(SPLIT_LINES) | \
sed "s|\(irefox.*\)\($(QUOTATION_MARK)\),|\1;$(FIREFOX_ARGS)\2,|" | \
sed "s|\(hrome.*\)\($(QUOTATION_MARK)\),|\1;$(CHROME_ARGS)\2,|" | \
$(JOIN_LINES) \
)
# A temporary profile directory is needed for each of the browser. In this way
# a unit test run will not disturb the main browsing session of the user. The
# $RANDOM shell variable is used to generate non-conflicting temporary
# directories.
BROWSERS_WITH_UKNOWN_RANDOM_PROFILE_PATHS:=$(shell echo '$(BROWSERS_WITH_ARGUMENTS)' | \
$(SPLIT_LINES) | \
sed 's|\(temp_profile\)|\1_$$RANDOM$$RANDOM|' | \
sed "s|$(QUOTATION_MARK)|$(SUBSTITUTE_FOR_QUOTATION_MARK)|g" | \
$(JOIN_LINES) \
)
# Echo the variable so that the unknown random directories become known.
# Replace @@@@ with " so that jsTestDriver will work properly.
BROWSERS:=$(shell echo "$(BROWSERS_WITH_UKNOWN_RANDOM_PROFILE_PATHS)" | \
sed "s|$(SUBSTITUTE_FOR_QUOTATION_MARK)|$(QUOTATION_MARK)|g" \
)
# Get the known random directories for browsers. This information will be used
# to create the profile directories beforehand. Create also the dummy temp
# profile directory so that the mkdir command would not fail for browsers that
# do not need it.
PROFILES:=$(TEMP_PROFILE) $(shell echo '$(BROWSERS)' | \
$(SPLIT_LINES) | \
sed -n "s|.*\( $(TEMP_PROFILE)_[0-9]\+\).*|\1|p" | \
$(JOIN_LINES) \
)
# This is the command to invoke the unit test.
PROG:=java \
-Xms512m \
-Xmx1024m \
-jar ../../external/jsTestDriver/JsTestDriver-1.3.3d.jar \
--config ./jsTestDriver.conf \
--reset \
--port 4224 \
--browser $(BROWSERS) \
--tests all \
--testOutput ./test_reports/
# This default rule runs the unit tests with the constructed command.
test:
@mkdir -p $(PROFILES)
$(PROG)
@rm -rf $(PROFILES)
# In case this Makefile needs to be debugged then this rule will provide all
# the information from intermediate steps.
debug:
@echo 'Debug browsers paths: $(BROWSERS_PATHS)'
@echo
@echo 'Debug browsers paths with mac correction: $(BROWSERS_PATHS_WITH_MAC_CORRECTION)'
@echo
@echo 'Debug browsers with arguments: $(BROWSERS_WITH_ARGUMENTS)'
@echo
@echo 'Debug browsers random profile paths: $(BROWSERS_WITH_UKNOWN_RANDOM_PROFILE_PATHS)'
@echo
@echo 'Debug browsers: $(BROWSERS)'
@echo
@echo 'Debug profiles: $(PROFILES)'
@echo
@echo 'Command to be run: $(PROG)'
@echo
.phony:: test

View File

@ -0,0 +1,30 @@
server: http://localhost:4224
load:
- ../../external/jasmine/jasmine.js
- ../../external/jasmineAdapter/JasmineAdapter.js
- ../../src/obj.js
- ../../src/core.js
- ../../src/util.js
- ../../src/canvas.js
- ../../src/obj.js
- ../../src/function.js
- ../../src/charsets.js
- ../../src/cidmaps.js
- ../../src/colorspace.js
- ../../src/crypto.js
- ../../src/evaluator.js
- ../../src/fonts.js
- ../../src/glyphlist.js
- ../../src/image.js
- ../../src/metrics.js
- ../../src/parser.js
- ../../src/pattern.js
- ../../src/stream.js
- ../../src/worker.js
- ../../external/jpgjs/jpg.js
- ../unit/obj_spec.js
- ../unit/function_spec.js
- ../unit/crypto_spec.js
- ../unit/stream_spec.js

3
test/unit/test_reports/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
TEST*
temp*

View File

@ -1,72 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>pdf.js unit test</title>
<link rel="shortcut icon" type="image/png" href="../../external/jasmine/jasmine_favicon.png">
<link rel="stylesheet" type="text/css" href="../../external/jasmine/jasmine.css">
<script type="text/javascript" src="../../external/jasmine/jasmine.js"></script>
<script type="text/javascript" src="../../external/jasmine/jasmine-html.js"></script>
<!-- include spec files here... -->
<script type="text/javascript" src="obj_spec.js"></script>
<script type="text/javascript" src="function_spec.js"></script>
<script type="text/javascript" src="crypto_spec.js"></script>
<script type="text/javascript" src="stream_spec.js"></script>
<!-- include source files here... -->
<script type="text/javascript" src="../../src/core.js"></script>
<script type="text/javascript" src="../../src/util.js"></script>
<script type="text/javascript" src="../../src/canvas.js"></script>
<script type="text/javascript" src="../../src/obj.js"></script>
<script type="text/javascript" src="../../src/function.js"></script>
<script type="text/javascript" src="../../src/charsets.js"></script>
<script type="text/javascript" src="../../src/cidmaps.js"></script>
<script type="text/javascript" src="../../src/colorspace.js"></script>
<script type="text/javascript" src="../../src/crypto.js"></script>
<script type="text/javascript" src="../../src/evaluator.js"></script>
<script type="text/javascript" src="../../src/fonts.js"></script>
<script type="text/javascript" src="../../src/glyphlist.js"></script>
<script type="text/javascript" src="../../src/image.js"></script>
<script type="text/javascript" src="../../src/metrics.js"></script>
<script type="text/javascript" src="../../src/parser.js"></script>
<script type="text/javascript" src="../../src/pattern.js"></script>
<script type="text/javascript" src="../../src/stream.js"></script>
<script type="text/javascript" src="../../src/worker.js"></script>
<script type="text/javascript" src="../../external/jpgjs/jpg.js"></script>
<script type="text/javascript">
'use strict';
(function pdfJsUnitTest() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var trivialReporter = new jasmine.TrivialReporter();
jasmineEnv.addReporter(trivialReporter);
jasmineEnv.specFilter = function pdfJsUnitTestSpecFilter(spec) {
return trivialReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function pdfJsUnitTestOnload() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
</script>
</head>
<body>
</body>
</html>

View File

@ -224,3 +224,10 @@
} }
}); });
})(); })();
// Check console compatability
(function checkConsoleCompatibility() {
if (typeof console == 'undefined') {
console = {log: function() {}};
}
})();

View File

@ -20,7 +20,8 @@
height="48.000000px" height="48.000000px"
width="48.000000px" width="48.000000px"
inkscape:output_extension="org.inkscape.output.svg.inkscape" inkscape:output_extension="org.inkscape.output.svg.inkscape"
version="1.1"> version="1.1"
viewbox="0 0 48 48">
<defs <defs
id="defs3"> id="defs3">
<inkscape:perspective <inkscape:perspective

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

@ -16,7 +16,8 @@
id="svg2994" id="svg2994"
height="48px" height="48px"
width="48px" width="48px"
inkscape:output_extension="org.inkscape.output.svg.inkscape"> inkscape:output_extension="org.inkscape.output.svg.inkscape"
viewbox="0 0 48 48">
<defs <defs
id="defs3"> id="defs3">
<inkscape:perspective <inkscape:perspective

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -16,7 +16,8 @@
id="svg2913" id="svg2913"
height="48px" height="48px"
width="48px" width="48px"
inkscape:output_extension="org.inkscape.output.svg.inkscape"> inkscape:output_extension="org.inkscape.output.svg.inkscape"
viewbox="0 0 48 48">
<defs <defs
id="defs3"> id="defs3">
<inkscape:perspective <inkscape:perspective

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -19,7 +19,8 @@
inkscape:export-filename="/home/jimmac/Desktop/wi-fi.png" inkscape:export-filename="/home/jimmac/Desktop/wi-fi.png"
inkscape:export-xdpi="90.000000" inkscape:export-xdpi="90.000000"
inkscape:export-ydpi="90.000000" inkscape:export-ydpi="90.000000"
inkscape:output_extension="org.inkscape.output.svg.inkscape"> inkscape:output_extension="org.inkscape.output.svg.inkscape"
viewbox="0 0 48 48">
<defs <defs
id="defs3"> id="defs3">
<inkscape:perspective <inkscape:perspective

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -19,7 +19,8 @@
inkscape:export-filename="/home/jimmac/Desktop/wi-fi.png" inkscape:export-filename="/home/jimmac/Desktop/wi-fi.png"
inkscape:export-xdpi="90.000000" inkscape:export-xdpi="90.000000"
inkscape:export-ydpi="90.000000" inkscape:export-ydpi="90.000000"
inkscape:output_extension="org.inkscape.output.svg.inkscape"> inkscape:output_extension="org.inkscape.output.svg.inkscape"
viewbox="0 0 48 48">
<defs <defs
id="defs3"> id="defs3">
<inkscape:perspective <inkscape:perspective

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
web/images/loading-icon.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -16,7 +16,8 @@
inkscape:version="0.46" inkscape:version="0.46"
sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/actions" sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/actions"
sodipodi:docname="list-add.svg" sodipodi:docname="list-add.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"> inkscape:output_extension="org.inkscape.output.svg.inkscape"
viewbox="0 0 48 48">
<defs <defs
id="defs6433"> id="defs6433">
<inkscape:perspective <inkscape:perspective

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -16,7 +16,8 @@
inkscape:version="0.46" inkscape:version="0.46"
sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/actions" sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/actions"
sodipodi:docname="list-remove.svg" sodipodi:docname="list-remove.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"> inkscape:output_extension="org.inkscape.output.svg.inkscape"
viewbox="0 0 48 48">
<defs <defs
id="defs6433"> id="defs6433">
<inkscape:perspective <inkscape:perspective

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,14 @@
<!-- This snippet is used in firefox extension, see Makefile -->
<base href="resource://pdf.js/web/" />
<script type="text/javascript" id="PDFJS_SCRIPT_TAG">
<!--
// pdf.js is inlined here because resource:// urls won't work
// for loading workers.
/* PDFJSSCRIPT_INCLUDE_BUNDLE */
-->
</script>
<script type="text/javascript">
// This specifies the location of the pdf.js file.
PDFJS.isFirefoxExtension = true;
PDFJS.workerSrc = 'none';
</script>

View File

@ -9,7 +9,7 @@ body {
} }
[hidden] { [hidden] {
display: none; display: none !important;
} }
/* === Toolbar === */ /* === Toolbar === */
@ -241,6 +241,16 @@ canvas {
-webkit-box-shadow: 0px 2px 10px #ff0; -webkit-box-shadow: 0px 2px 10px #ff0;
} }
.loadingIcon {
position: absolute;
display: block;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: url('images/loading-icon.gif') center no-repeat;
}
.textLayer { .textLayer {
position: absolute; position: absolute;
left: 0; left: 0;

View File

@ -2,9 +2,11 @@
<html> <html>
<head> <head>
<title>Simple pdf.js page viewer</title> <title>Simple pdf.js page viewer</title>
<!-- PDFJSSCRIPT_INCLUDE_FIREFOX_EXTENSION -->
<link rel="stylesheet" href="viewer.css"/> <link rel="stylesheet" href="viewer.css"/>
<script type="text/javascript" src="compatibility.js"></script> <script type="text/javascript" src="compatibility.js"></script> <!-- PDFJSSCRIPT_REMOVE_FIREFOX_EXTENSION -->
<!-- PDFJSSCRIPT_INCLUDE_BUILD --> <!-- PDFJSSCRIPT_INCLUDE_BUILD -->
<script type="text/javascript" src="../src/core.js"></script> <!-- PDFJSSCRIPT_REMOVE --> <script type="text/javascript" src="../src/core.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
@ -26,6 +28,7 @@
<script type="text/javascript" src="../src/stream.js"></script> <!-- PDFJSSCRIPT_REMOVE --> <script type="text/javascript" src="../src/stream.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/worker.js"></script> <!-- PDFJSSCRIPT_REMOVE --> <script type="text/javascript" src="../src/worker.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../external/jpgjs/jpg.js"></script> <!-- PDFJSSCRIPT_REMOVE --> <script type="text/javascript" src="../external/jpgjs/jpg.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/jpx.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript">PDFJS.workerSrc = '../src/worker_loader.js';</script> <!-- PDFJSSCRIPT_REMOVE --> <script type="text/javascript">PDFJS.workerSrc = '../src/worker_loader.js';</script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="viewer.js"></script> <script type="text/javascript" src="viewer.js"></script>
@ -90,7 +93,7 @@
<input id="fileInput" type="file" oncontextmenu="return false;"/> <input id="fileInput" type="file" oncontextmenu="return false;"/>
<div class="separator"></div> <div id="fileInputSeperator" class="separator"></div>
<a href="#" id="viewBookmark" title="Bookmark (or copy) current location"> <a href="#" id="viewBookmark" title="Bookmark (or copy) current location">
<img src="images/bookmark.svg" alt="Bookmark" align="top" height="16"/> <img src="images/bookmark.svg" alt="Bookmark" align="top" height="16"/>

View File

@ -6,6 +6,7 @@
var kDefaultURL = 'compressed.tracemonkey-pldi-09.pdf'; var kDefaultURL = 'compressed.tracemonkey-pldi-09.pdf';
var kDefaultScale = 'auto'; var kDefaultScale = 'auto';
var kDefaultScaleDelta = 1.1; var kDefaultScaleDelta = 1.1;
var kUnknownScale = 0;
var kCacheSize = 20; var kCacheSize = 20;
var kCssUnits = 96.0 / 72.0; var kCssUnits = 96.0 / 72.0;
var kScrollbarPadding = 40; var kScrollbarPadding = 40;
@ -61,34 +62,54 @@ var RenderingQueue = (function RenderingQueueClosure() {
return RenderingQueue; return RenderingQueue;
})(); })();
var FirefoxCom = (function FirefoxComClosure() {
return {
/**
* Creates an event that hopefully the extension is listening for and will
* synchronously respond to.
* @param {String} action The action to trigger.
* @param {String} data Optional data to send.
* @return {*} The response.
*/
request: function(action, data) {
var request = document.createTextNode('');
request.setUserData('action', action, null);
request.setUserData('data', data, null);
document.documentElement.appendChild(request);
var sender = document.createEvent('Events');
sender.initEvent('pdf.js.message', true, false);
request.dispatchEvent(sender);
var response = request.getUserData('response');
document.documentElement.removeChild(request);
return response;
}
};
})();
// Settings Manager - This is a utility for saving settings // Settings Manager - This is a utility for saving settings
// First we see if localStorage is available, FF bug #495747 // First we see if localStorage is available
// If not, we use FUEL in FF // If not, we use FUEL in FF
var Settings = (function SettingsClosure() { var Settings = (function SettingsClosure() {
var isLocalStorageEnabled = (function localStorageEnabledTest() { var isLocalStorageEnabled = (function localStorageEnabledTest() {
// Feature test as per http://diveintohtml5.info/storage.html
// The additional localStorage call is to get around a FF quirk, see
// bug #495747 in bugzilla
try { try {
localStorage; return 'localStorage' in window && window['localStorage'] !== null &&
localStorage;
} catch (e) { } catch (e) {
return false; return false;
} }
return true;
})(); })();
var extPrefix = 'extensions.uriloader@pdf.js';
var isExtension = location.protocol == 'chrome:' && !isLocalStorageEnabled; var isFirefoxExtension = PDFJS.isFirefoxExtension;
var inPrivateBrowsing = false;
if (isExtension) {
var pbs = Components.classes['@mozilla.org/privatebrowsing;1']
.getService(Components.interfaces.nsIPrivateBrowsingService);
inPrivateBrowsing = pbs.privateBrowsingEnabled;
}
function Settings(fingerprint) { function Settings(fingerprint) {
var database = null; var database = null;
var index; var index;
if (inPrivateBrowsing) if (isFirefoxExtension)
return false; database = FirefoxCom.request('getDatabase', null) || '{}';
else if (isExtension)
database = Application.prefs.getValue(extPrefix + '.database', '{}');
else if (isLocalStorageEnabled) else if (isLocalStorageEnabled)
database = localStorage.getItem('database') || '{}'; database = localStorage.getItem('database') || '{}';
else else
@ -110,31 +131,27 @@ var Settings = (function SettingsClosure() {
index = database.files.push({fingerprint: fingerprint}) - 1; index = database.files.push({fingerprint: fingerprint}) - 1;
this.file = database.files[index]; this.file = database.files[index];
this.database = database; this.database = database;
if (isExtension)
Application.prefs.setValue(extPrefix + '.database',
JSON.stringify(database));
else if (isLocalStorageEnabled)
localStorage.setItem('database', JSON.stringify(database));
} }
Settings.prototype = { Settings.prototype = {
set: function settingsSet(name, val) { set: function settingsSet(name, val) {
if (inPrivateBrowsing) if (!('file' in this))
return false; return false;
var file = this.file; var file = this.file;
file[name] = val; file[name] = val;
if (isExtension) var database = JSON.stringify(this.database);
Application.prefs.setValue(extPrefix + '.database', if (isFirefoxExtension)
JSON.stringify(this.database)); FirefoxCom.request('setDatabase', database);
else if (isLocalStorageEnabled) else if (isLocalStorageEnabled)
localStorage.setItem('database', JSON.stringify(this.database)); localStorage.setItem('database', database);
}, },
get: function settingsGet(name, defaultValue) { get: function settingsGet(name, defaultValue) {
if (inPrivateBrowsing) if (!('file' in this))
return defaultValue; return defaultValue;
else
return this.file[name] || defaultValue; return this.file[name] || defaultValue;
} }
}; };
@ -148,7 +165,7 @@ var currentPageNumber = 1;
var PDFView = { var PDFView = {
pages: [], pages: [],
thumbnails: [], thumbnails: [],
currentScale: 0, currentScale: kUnknownScale,
currentScaleValue: null, currentScaleValue: null,
initialBookmark: document.location.hash.substring(1), initialBookmark: document.location.hash.substring(1),
@ -203,12 +220,12 @@ var PDFView = {
zoomIn: function pdfViewZoomIn() { zoomIn: function pdfViewZoomIn() {
var newScale = Math.min(kMaxScale, this.currentScale * kDefaultScaleDelta); var newScale = Math.min(kMaxScale, this.currentScale * kDefaultScaleDelta);
this.setScale(newScale, true); this.parseScale(newScale, true);
}, },
zoomOut: function pdfViewZoomOut() { zoomOut: function pdfViewZoomOut() {
var newScale = Math.max(kMinScale, this.currentScale / kDefaultScaleDelta); var newScale = Math.max(kMinScale, this.currentScale / kDefaultScaleDelta);
this.setScale(newScale, true); this.parseScale(newScale, true);
}, },
set page(val) { set page(val) {
@ -258,7 +275,7 @@ var PDFView = {
}, },
error: function getPdfError(e) { error: function getPdfError(e) {
var loadingIndicator = document.getElementById('loading'); var loadingIndicator = document.getElementById('loading');
loadingIndicator.innerHTML = 'Error'; loadingIndicator.textContent = 'Error';
var moreInfo = { var moreInfo = {
message: 'Unexpected server response of ' + e.target.status + '.' message: 'Unexpected server response of ' + e.target.status + '.'
}; };
@ -273,7 +290,13 @@ var PDFView = {
}, },
download: function pdfViewDownload() { download: function pdfViewDownload() {
window.open(this.url + '#pdfjs.action=download', '_parent'); var url = this.url.split('#')[0];
if (PDFJS.isFirefoxExtension) {
FirefoxCom.request('download', url);
} else {
url += '#pdfjs.action=download', '_parent';
window.open(url, '_parent');
}
}, },
navigateTo: function pdfViewNavigateTo(dest) { navigateTo: function pdfViewNavigateTo(dest) {
@ -294,14 +317,14 @@ var PDFView = {
getDestinationHash: function pdfViewGetDestinationHash(dest) { getDestinationHash: function pdfViewGetDestinationHash(dest) {
if (typeof dest === 'string') if (typeof dest === 'string')
return '#' + escape(dest); return PDFView.getAnchorUrl('#' + escape(dest));
if (dest instanceof Array) { if (dest instanceof Array) {
var destRef = dest[0]; // see navigateTo method for dest format var destRef = dest[0]; // see navigateTo method for dest format
var pageNumber = destRef instanceof Object ? var pageNumber = destRef instanceof Object ?
this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
(destRef + 1); (destRef + 1);
if (pageNumber) { if (pageNumber) {
var pdfOpenParams = '#page=' + pageNumber; var pdfOpenParams = PDFView.getAnchorUrl('#page=' + pageNumber);
var destKind = dest[1]; var destKind = dest[1];
if ('name' in destKind && destKind.name == 'XYZ') { if ('name' in destKind && destKind.name == 'XYZ') {
var scale = (dest[4] || this.currentScale); var scale = (dest[4] || this.currentScale);
@ -316,6 +339,17 @@ var PDFView = {
return ''; return '';
}, },
/**
* For the firefox extension we prefix the full url on anchor links so they
* don't come up as resource:// urls and so open in new tab/window works.
* @param {String} anchor The anchor hash include the #.
*/
getAnchorUrl: function getAnchorUrl(anchor) {
if (PDFJS.isFirefoxExtension)
return this.url.split('#')[0] + anchor;
return anchor;
},
/** /**
* Show the error box. * Show the error box.
* @param {String} message A message that is human readable. * @param {String} message A message that is human readable.
@ -328,7 +362,7 @@ var PDFView = {
errorWrapper.removeAttribute('hidden'); errorWrapper.removeAttribute('hidden');
var errorMessage = document.getElementById('errorMessage'); var errorMessage = document.getElementById('errorMessage');
errorMessage.innerHTML = message; errorMessage.textContent = message;
var closeButton = document.getElementById('errorClose'); var closeButton = document.getElementById('errorClose');
closeButton.onclick = function() { closeButton.onclick = function() {
@ -354,8 +388,14 @@ var PDFView = {
if (moreInfo) { if (moreInfo) {
errorMoreInfo.value += 'Message: ' + moreInfo.message; errorMoreInfo.value += 'Message: ' + moreInfo.message;
if (moreInfo.stack) if (moreInfo.stack) {
errorMoreInfo.value += '\n' + 'Stack: ' + moreInfo.stack; errorMoreInfo.value += '\n' + 'Stack: ' + moreInfo.stack;
} else {
if (moreInfo.filename)
errorMoreInfo.value += '\n' + 'File: ' + moreInfo.filename;
if (moreInfo.lineNumber)
errorMoreInfo.value += '\n' + 'Line: ' + moreInfo.lineNumber;
}
} }
errorMoreInfo.rows = errorMoreInfo.value.split('\n').length - 1; errorMoreInfo.rows = errorMoreInfo.value.split('\n').length - 1;
}, },
@ -363,7 +403,7 @@ var PDFView = {
progress: function pdfViewProgress(level) { progress: function pdfViewProgress(level) {
var percent = Math.round(level * 100); var percent = Math.round(level * 100);
var loadingIndicator = document.getElementById('loading'); var loadingIndicator = document.getElementById('loading');
loadingIndicator.innerHTML = 'Loading... ' + percent + '%'; loadingIndicator.textContent = 'Loading... ' + percent + '%';
}, },
load: function pdfViewLoad(data, scale) { load: function pdfViewLoad(data, scale) {
@ -403,7 +443,7 @@ var PDFView = {
var pagesCount = pdf.numPages; var pagesCount = pdf.numPages;
var id = pdf.fingerprint; var id = pdf.fingerprint;
var storedHash = null; var storedHash = null;
document.getElementById('numPages').innerHTML = pagesCount; document.getElementById('numPages').textContent = pagesCount;
document.getElementById('pageNumber').max = pagesCount; document.getElementById('pageNumber').max = pagesCount;
PDFView.documentFingerprint = id; PDFView.documentFingerprint = id;
var store = PDFView.store = new Settings(id); var store = PDFView.store = new Settings(id);
@ -453,10 +493,16 @@ var PDFView = {
} }
else if (storedHash) else if (storedHash)
this.setHash(storedHash); this.setHash(storedHash);
else { else if (scale) {
this.parseScale(scale || kDefaultScale, true); this.parseScale(scale, true);
this.page = 1; this.page = 1;
} }
if (PDFView.currentScale === kUnknownScale) {
// Scale was not initialized: invalid bookmark or scale was not specified.
// Setting the default one.
this.parseScale(kDefaultScale, true);
}
}, },
setHash: function pdfViewSetHash(hash) { setHash: function pdfViewSetHash(hash) {
@ -612,6 +658,10 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
div.removeAttribute('data-loaded'); div.removeAttribute('data-loaded');
delete this.canvas; delete this.canvas;
this.loadingIconDiv = document.createElement('div');
this.loadingIconDiv.className = 'loadingIcon';
div.appendChild(this.loadingIconDiv);
}; };
function setupAnnotations(content, scale) { function setupAnnotations(content, scale) {
@ -649,7 +699,15 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
if (!item.content) { if (!item.content) {
content.setAttribute('hidden', true); content.setAttribute('hidden', true);
} else { } else {
text.innerHTML = item.content.replace('\n', '<br />'); var e = document.createElement('span');
var lines = item.content.split('\n');
for (var i = 0, ii = lines.length; i < ii; ++i) {
var line = lines[i];
e.appendChild(document.createTextNode(line));
if (i < (ii - 1))
e.appendChild(document.createElement('br'));
}
text.appendChild(e);
image.addEventListener('mouseover', function annotationImageOver() { image.addEventListener('mouseover', function annotationImageOver() {
this.nextSibling.removeAttribute('hidden'); this.nextSibling.removeAttribute('hidden');
}, false); }, false);
@ -743,6 +801,8 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
if (scale && scale !== PDFView.currentScale) if (scale && scale !== PDFView.currentScale)
PDFView.parseScale(scale, true); PDFView.parseScale(scale, true);
else if (PDFView.currentScale === kUnknownScale)
PDFView.parseScale(kDefaultScale, true);
setTimeout(function pageViewScrollIntoViewRelayout() { setTimeout(function pageViewScrollIntoViewRelayout() {
// letting page to re-layout before scrolling // letting page to re-layout before scrolling
@ -766,7 +826,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
}; };
this.drawingRequired = function() { this.drawingRequired = function() {
return !div.hasChildNodes(); return !div.querySelector('canvas');
}; };
this.draw = function pageviewDraw(callback) { this.draw = function pageviewDraw(callback) {
@ -801,19 +861,23 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
ctx.restore(); ctx.restore();
ctx.translate(-this.x * scale, -this.y * scale); ctx.translate(-this.x * scale, -this.y * scale);
this.content.startRendering(ctx, // Rendering area
(function pageViewDrawCallback(error) {
if (error)
PDFView.error('An error occurred while rendering the page.', error);
this.stats = content.bench;
this.updateStats();
if (this.onAfterDraw)
this.onAfterDraw();
cache.push(this); var self = this;
callback(); this.content.startRendering(ctx, function pageViewDrawCallback(error) {
}).bind(this), textLayer div.removeChild(self.loadingIconDiv);
);
if (error)
PDFView.error('An error occurred while rendering the page.', error);
self.stats = content.bench;
self.updateStats();
if (self.onAfterDraw)
self.onAfterDraw();
cache.push(self);
callback();
}, textLayer);
setupAnnotations(this.content, this.scale); setupAnnotations(this.content, this.scale);
div.setAttribute('data-loaded', true); div.setAttribute('data-loaded', true);
@ -831,7 +895,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
var ThumbnailView = function thumbnailView(container, page, id, pageRatio) { var ThumbnailView = function thumbnailView(container, page, id, pageRatio) {
var anchor = document.createElement('a'); var anchor = document.createElement('a');
anchor.href = '#' + id; anchor.href = PDFView.getAnchorUrl('#page=' + id);
anchor.onclick = function stopNivigation() { anchor.onclick = function stopNivigation() {
PDFView.page = id; PDFView.page = id;
return false; return false;
@ -963,22 +1027,55 @@ var TextLayerBuilder = function textLayerBuilder(textLayerDiv) {
var self = this; var self = this;
var textDivs = this.textDivs; var textDivs = this.textDivs;
var textLayerDiv = this.textLayerDiv; var textLayerDiv = this.textLayerDiv;
this.textLayerTimer = setInterval(function renderTextLayer() { var renderTimer = null;
var renderingDone = false;
var renderInterval = 0;
var resumeInterval = 500; // in ms
// Render the text layer, one div at a time
function renderTextLayer() {
if (textDivs.length === 0) { if (textDivs.length === 0) {
clearInterval(self.textLayerTimer); clearInterval(renderTimer);
renderingDone = true;
return; return;
} }
var textDiv = textDivs.shift(); var textDiv = textDivs.shift();
if (textDiv.dataset.textLength >= 1) { // avoid div by zero if (textDiv.dataset.textLength > 0) {
textLayerDiv.appendChild(textDiv); textLayerDiv.appendChild(textDiv);
// Adjust div width (via letterSpacing) to match canvas text
// Due to the .offsetWidth calls, this is slow if (textDiv.dataset.textLength > 1) { // avoid div by zero
textDiv.style.letterSpacing = // Adjust div width (via letterSpacing) to match canvas text
((textDiv.dataset.canvasWidth - textDiv.offsetWidth) / // Due to the .offsetWidth calls, this is slow
(textDiv.dataset.textLength - 1)) + 'px'; // This needs to come after appending to the DOM
textDiv.style.letterSpacing =
((textDiv.dataset.canvasWidth - textDiv.offsetWidth) /
(textDiv.dataset.textLength - 1)) + 'px';
}
} // textLength > 0
}
renderTimer = setInterval(renderTextLayer, renderInterval);
// Stop rendering when user scrolls. Resume after XXX milliseconds
// of no scroll events
var scrollTimer = null;
function textLayerOnScroll() {
if (renderingDone) {
window.removeEventListener('scroll', textLayerOnScroll, false);
return;
} }
}, 0);
}; // Immediately pause rendering
clearInterval(renderTimer);
clearTimeout(scrollTimer);
scrollTimer = setTimeout(function textLayerScrollTimer() {
// Resume rendering
renderTimer = setInterval(renderTextLayer, renderInterval);
}, resumeInterval);
}; // textLayerOnScroll
window.addEventListener('scroll', textLayerOnScroll, false);
}; // endLayout
this.appendText = function textLayerBuilderAppendText(text, this.appendText = function textLayerBuilderAppendText(text,
fontName, fontSize) { fontName, fontSize) {
@ -1006,12 +1103,18 @@ window.addEventListener('load', function webViewerLoad(evt) {
} }
var scale = ('scale' in params) ? params.scale : 0; var scale = ('scale' in params) ? params.scale : 0;
PDFView.open(params.file || kDefaultURL, parseFloat(scale)); var file = PDFJS.isFirefoxExtension ?
window.location.toString() : params.file || kDefaultURL;
PDFView.open(file, parseFloat(scale));
if (!window.File || !window.FileReader || !window.FileList || !window.Blob) if (PDFJS.isFirefoxExtension || !window.File || !window.FileReader ||
!window.FileList || !window.Blob) {
document.getElementById('fileInput').setAttribute('hidden', 'true'); document.getElementById('fileInput').setAttribute('hidden', 'true');
else document.getElementById('fileInputSeperator')
.setAttribute('hidden', 'true');
} else {
document.getElementById('fileInput').value = null; document.getElementById('fileInput').value = null;
}
if ('disableWorker' in params) if ('disableWorker' in params)
PDFJS.disableWorker = (params['disableWorker'] === 'true'); PDFJS.disableWorker = (params['disableWorker'] === 'true');
@ -1101,8 +1204,8 @@ function updateViewarea() {
store.set('zoom', normalizedScaleValue); store.set('zoom', normalizedScaleValue);
store.set('scrollLeft', Math.round(topLeft.x)); store.set('scrollLeft', Math.round(topLeft.x));
store.set('scrollTop', Math.round(topLeft.y)); store.set('scrollTop', Math.round(topLeft.y));
var href = PDFView.getAnchorUrl(pdfOpenParams);
document.getElementById('viewBookmark').href = pdfOpenParams; document.getElementById('viewBookmark').href = href;
} }
window.addEventListener('scroll', function webViewerScroll(evt) { window.addEventListener('scroll', function webViewerScroll(evt) {
@ -1242,7 +1345,7 @@ window.addEventListener('keydown', function keydown(evt) {
handled = true; handled = true;
break; break;
case 48: // '0' case 48: // '0'
PDFView.setScale(kDefaultScale, true); PDFView.parseScale(kDefaultScale, true);
handled = true; handled = true;
break; break;
case 37: // left arrow case 37: // left arrow