Merge with upstream

This commit is contained in:
Vivien Nicolas 2011-11-06 17:27:43 +01:00
commit 97ebd26591
53 changed files with 8430 additions and 7964 deletions

2
.gitignore vendored
View File

@ -3,3 +3,5 @@ pdf.pdf
intelisa.pdf intelisa.pdf
openweb_tm-PRINT.pdf openweb_tm-PRINT.pdf
local.mk local.mk
build/

118
Makefile
View File

@ -1,5 +1,6 @@
REPO = git@github.com:andreasgal/pdf.js.git REPO = git@github.com:mozilla/pdf.js.git
BUILD_DIR := build BUILD_DIR := build
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
@ -7,24 +8,33 @@ EXTENSION_SRC := ./extensions/
FIREFOX_EXTENSION_NAME := pdf.js.xpi FIREFOX_EXTENSION_NAME := pdf.js.xpi
CHROME_EXTENSION_NAME := pdf.js.crx CHROME_EXTENSION_NAME := pdf.js.crx
all: bundle
# Let folks define custom rules for their clones. # Let folks define custom rules for their clones.
-include local.mk -include local.mk
# JS files needed for pdf.js. # JS files needed for pdf.js.
# This list doesn't account for the 'worker' directory.
PDF_JS_FILES = \ PDF_JS_FILES = \
pdf.js \ core.js \
crypto.js \ util.js \
fonts.js \ canvas.js \
metrics.js \ obj.js \
charsets.js \ function.js \
cidmaps.js \ charsets.js \
glyphlist.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 \
$(NULL) $(NULL)
# not sure what to do for all yet
all: help
# make server # make server
# #
# 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
@ -34,6 +44,28 @@ server:
test: shell-test browser-test test: shell-test browser-test
#
# Create production output (pdf.js, and corresponding changes to web files)
#
production: | bundle
@echo "Preparing web/viewer-production.html"; \
cd web; \
sed '/PDFJSSCRIPT_REMOVE/d' viewer.html > viewer-1.tmp; \
sed '/PDFJSSCRIPT_INCLUDE_BUILD/ r viewer-snippet.html' viewer-1.tmp > viewer-production.html; \
rm -f *.tmp; \
cd ..
#
# Bundle pdf.js
#
bundle: | $(BUILD_DIR)
@echo "Bundling source files into $(BUILD_TARGET)"
@cd src; \
cat $(PDF_JS_FILES) > all_files.tmp; \
sed '/PDFJSSCRIPT_INCLUDE_ALL/ r all_files.tmp' pdf.js > ../$(BUILD_TARGET); \
rm -f *.tmp; \
cd ..
# 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,11 +125,11 @@ browser-test:
# To install gjslint, see: # To install gjslint, see:
# #
# <http://code.google.com/closure/utilities/docs/linter_howto.html> # <http://code.google.com/closure/utilities/docs/linter_howto.html>
SRC_DIRS := . utils worker web test examples/helloworld extensions/firefox \ SRC_DIRS := . src utils web test examples/helloworld extensions/firefox \
extensions/firefox/components extensions/chrome extensions/firefox/components extensions/chrome
GJSLINT_FILES = $(foreach DIR,$(SRC_DIRS),$(wildcard $(DIR)/*.js)) GJSLINT_FILES = $(foreach DIR,$(SRC_DIRS),$(wildcard $(DIR)/*.js))
lint: lint:
gjslint $(GJSLINT_FILES) gjslint --nojsdoc $(GJSLINT_FILES)
# make web # make web
# #
@ -108,14 +140,13 @@ lint:
# TODO: Use the Closure compiler to optimize the pdf.js files. # TODO: Use the Closure compiler to optimize the pdf.js files.
# #
GH_PAGES = $(BUILD_DIR)/gh-pages GH_PAGES = $(BUILD_DIR)/gh-pages
web: | extension compiler pages-repo \ web: | production extension compiler pages-repo
$(addprefix $(GH_PAGES)/, $(PDF_JS_FILES)) \ @cp $(BUILD_TARGET) $(GH_PAGES)/$(BUILD_TARGET)
$(addprefix $(GH_PAGES)/, $(wildcard web/*.*)) \ @cp -R web/* $(GH_PAGES)/web
$(addprefix $(GH_PAGES)/, $(wildcard web/images/*.*)) \ @cp web/images/* $(GH_PAGES)/web/images
$(addprefix $(GH_PAGES)/, $(wildcard $(EXTENSION_SRC)/firefox/*.xpi)) \ @cp $(EXTENSION_SRC)/firefox/*.xpi $(GH_PAGES)/$(EXTENSION_SRC)/firefox/
$(addprefix $(GH_PAGES)/, $(wildcard $(EXTENSION_SRC)/chrome/*.crx))
@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;
@cd $(GH_PAGES); git add -A; @cd $(GH_PAGES); git add -A;
@echo @echo
@echo "Website built in $(GH_PAGES)." @echo "Website built in $(GH_PAGES)."
@ -135,22 +166,8 @@ pages-repo: | $(BUILD_DIR)
fi; fi;
@mkdir -p $(GH_PAGES)/web; @mkdir -p $(GH_PAGES)/web;
@mkdir -p $(GH_PAGES)/web/images; @mkdir -p $(GH_PAGES)/web/images;
@mkdir -p $(GH_PAGES)/build;
@mkdir -p $(GH_PAGES)/$(EXTENSION_SRC)/firefox; @mkdir -p $(GH_PAGES)/$(EXTENSION_SRC)/firefox;
@mkdir -p $(GH_PAGES)/$(EXTENSION_SRC)/chrome;
$(GH_PAGES)/%.js: %.js
@cp $< $@
$(GH_PAGES)/web/%: web/%
@cp $< $@
$(GH_PAGES)/web/images/%: web/images/%
@cp $< $@
$(GH_PAGES)/$(EXTENSION_SRC)/firefox/%: $(EXTENSION_SRC)/firefox/%
@cp -R $< $@
$(GH_PAGES)/$(EXTENSION_SRC)/chrome/%: $(EXTENSION_SRC)/chrome/%
# # make compiler # # make compiler
# # # #
@ -169,29 +186,36 @@ $(GH_PAGES)/$(EXTENSION_SRC)/chrome/%: $(EXTENSION_SRC)/chrome/%
# #
# 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 := firefox/content CONTENT_DIR := content
FIREFOX_CONTENT_DIR := $(EXTENSION_SRC)/firefox/$(CONTENT_DIR)/
CHROME_CONTENT_DIR := $(EXTENSION_SRC)/chrome/$(CONTENT_DIR)/
PDF_WEB_FILES = \ PDF_WEB_FILES = \
web/images \ web/images \
web/compatibility.js \ web/compatibility.js \
web/viewer.css \ web/viewer.css \
web/viewer.js \ web/viewer.js \
web/viewer.html \ web/viewer-production.html \
$(NULL) $(NULL)
extension: extension: | production
# Copy a standalone version of pdf.js inside the content directory # Copy a standalone version of pdf.js inside the content directory
@rm -Rf $(EXTENSION_SRC)/$(CONTENT_DIR)/ @rm -Rf $(FIREFOX_CONTENT_DIR)
@mkdir -p $(EXTENSION_SRC)/$(CONTENT_DIR)/web @mkdir -p $(FIREFOX_CONTENT_DIR)/$(BUILD_DIR)
@cp $(PDF_JS_FILES) $(EXTENSION_SRC)/$(CONTENT_DIR)/ @mkdir -p $(FIREFOX_CONTENT_DIR)/web
@cp -r $(PDF_WEB_FILES) $(EXTENSION_SRC)/$(CONTENT_DIR)/web/ @cp $(BUILD_TARGET) $(FIREFOX_CONTENT_DIR)/$(BUILD_DIR)
@cp -r $(PDF_WEB_FILES) $(FIREFOX_CONTENT_DIR)/web/
@mv -f $(FIREFOX_CONTENT_DIR)/web/viewer-production.html $(FIREFOX_CONTENT_DIR)/web/viewer.html
# Create the xpi # Create the xpi
@cd $(EXTENSION_SRC)/firefox; zip -r $(FIREFOX_EXTENSION_NAME) * @cd $(EXTENSION_SRC)/firefox; zip -r $(FIREFOX_EXTENSION_NAME) *
@echo "extension created: " $(FIREFOX_EXTENSION_NAME) @echo "extension created: " $(FIREFOX_EXTENSION_NAME)
# Copy a standalone version of pdf.js inside the extension directory # Copy a standalone version of pdf.js inside the extension directory
@cp $(PDF_JS_FILES) $(EXTENSION_SRC)/chrome/ @rm -Rf $(CHROME_CONTENT_DIR)
@mkdir -p $(EXTENSION_SRC)/chrome/web @mkdir -p $(CHROME_CONTENT_DIR)/$(BUILD_DIR)
@cp -r $(PDF_WEB_FILES) $(EXTENSION_SRC)/chrome/web/ @mkdir -p $(CHROME_CONTENT_DIR)/web
@cp $(BUILD_TARGET) $(CHROME_CONTENT_DIR)/$(BUILD_DIR)
@cp -r $(PDF_WEB_FILES) $(CHROME_CONTENT_DIR)/web/
@mv -f $(CHROME_CONTENT_DIR)/web/viewer-production.html $(CHROME_CONTENT_DIR)/web/viewer.html
# Create the crx # Create the crx
#TODO #TODO
@ -211,5 +235,5 @@ clean:
help: help:
@echo "Read the comments in the Makefile for guidance."; @echo "Read the comments in the Makefile for guidance.";
.PHONY:: all test browser-test font-test shell-test \ .PHONY:: production test browser-test font-test shell-test \
shell-msg lint clean web compiler help server shell-msg lint clean web compiler help server

View File

@ -22,16 +22,24 @@ successful.
For an online demo, visit: For an online demo, visit:
+ http://andreasgal.github.com/pdf.js/web/viewer.html + http://mozilla.github.com/pdf.js/web/viewer.html
This demo provides an interactive interface for displaying and browsing PDFs This demo provides an interactive interface for displaying and browsing PDFs
using the pdf.js API. using the pdf.js API.
### Extension
A Firefox extension is also available:
+ http://mozilla.github.com/pdf.js/extensions/firefox/pdf.js.xpi
However, note that the extension might not reflect the latest source in our master branch.
### Getting the code ### Getting the code
To get a local copy of the current code, clone it using git: To get a local copy of the current code, clone it using git:
$ git clone git://github.com/andreasgal/pdf.js.git pdfjs $ git clone git://github.com/mozilla/pdf.js.git pdfjs
$ cd pdfjs $ cd pdfjs
Next, you need to start a local web server as some browsers don't allow opening Next, you need to start a local web server as some browsers don't allow opening
@ -47,11 +55,29 @@ You can also view all the test pdf files on the right side serving
+ http://localhost:8888/test/pdfs/?frame + http://localhost:8888/test/pdfs/?frame
### Learning ### Building pdf.js
In order to bundle all `src/` files into a final `pdf.js`, issue:
$ make
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
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
For a "hello world" example, take a look at:
+ [examples/helloworld/hello.js](https://github.com/mozilla/pdf.js/blob/master/examples/helloworld/hello.js)
This example illustrates the bare minimum ingredients for integrating pdf.js
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
@ -59,14 +85,6 @@ workings of PDF and pdf.js:
+ http://www.youtube.com/watch?v=Iv15UY-4Fg8 + http://www.youtube.com/watch?v=Iv15UY-4Fg8
#### Hello world
For a "hello world" example, take a look at:
+ [examples/helloworld/hello.js](https://github.com/andreasgal/pdf.js/blob/master/examples/helloworld/hello.js)
This example illustrates the bare minimum ingredients for integrating pdf.js
in a custom project.
@ -74,19 +92,19 @@ in a custom project.
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. A great place to start is our Simply fork our repo and contribute away. A great place to start is our
[open issues](https://github.com/andreasgal/pdf.js/issues). For better consistency and [open issues](https://github.com/mozilla/pdf.js/issues). For better consistency and
long-term stability, please do look around the code and try to follow our conventions. long-term stability, please do look around the code and try to follow our conventions.
More information about the contributor process can be found on the More information about the contributor process can be found on the
[contributor wiki page](https://github.com/andreasgal/pdf.js/wiki/Contributing). [contributor wiki page](https://github.com/mozilla/pdf.js/wiki/Contributing).
If you don't want to hack on the project or have little spare time, __you still If you don't want to hack on the project or have little spare time, __you still
can help!__ Just open PDFs in the can help!__ Just open PDFs in the
[online demo](http://andreasgal.github.com/pdf.js/web/viewer.html) and report [online demo](http://mozilla.github.com/pdf.js/web/viewer.html) and report
any breakage in rendering. any breakage in rendering.
Our Github contributors so far: Our Github contributors so far:
+ https://github.com/andreasgal/pdf.js/contributors + https://github.com/mozilla/pdf.js/contributors
You can add your name to it! :) You can add your name to it! :)
@ -125,14 +143,14 @@ against reference images before merging pull requests.
See the bot repo for details: See the bot repo for details:
+ https://github.com/arturadib/pdf.js-bot + https://github.com/mozilla/pdf.js-bot
## Additional resources ## Additional resources
Our demo site is here: Our demo site is here:
+ http://andreasgal.github.com/pdf.js/web/viewer.html + http://mozilla.github.com/pdf.js/web/viewer.html
You can read more about pdf.js here: You can read more about pdf.js here:

View File

@ -7,11 +7,11 @@
'use strict'; 'use strict';
getPdf('helloworld.pdf', function getPdfHelloWorld(data) { PDFJS.getPdf('helloworld.pdf', function getPdfHelloWorld(data) {
// //
// Instantiate PDFDoc with PDF data // Instantiate PDFDoc with PDF data
// //
var pdf = new PDFDoc(data); var pdf = new PDFJS.PDFDoc(data);
var page = pdf.getPage(1); var page = pdf.getPage(1);
var scale = 1.5; var scale = 1.5;

View File

@ -2,14 +2,34 @@
<html> <html>
<head> <head>
<!-- PDF.js-specific --> <!-- In production, only one script (pdf.js) is necessary -->
<script type="text/javascript" src="../../pdf.js"></script> <!-- In production, change the content of PDFJS.workerSrc below -->
<script type="text/javascript" src="../../metrics.js"></script> <script type="text/javascript" src="../../src/core.js"></script>
<script type="text/javascript" src="../../fonts.js"></script> <script type="text/javascript" src="../../src/util.js"></script>
<script type="text/javascript" src="../../glyphlist.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">
// Specify the main script used to create a new PDF.JS web worker.
// In production, change this to point to the combined `pdf.js` file.
PDFJS.workerSrc = '../../src/worker_loader.js';
</script>
<script type="text/javascript" src="hello.js"></script> <script type="text/javascript" src="hello.js"></script>
</head> </head>
<body> <body>
<canvas id="the-canvas" style="border:1px solid black;"/> <canvas id="the-canvas" style="border:1px solid black;"/>

1
extensions/firefox/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
content/

View File

@ -16,21 +16,31 @@ function log(str) {
function startup(aData, aReason) { function startup(aData, aReason) {
let manifestPath = 'chrome.manifest'; let manifestPath = 'chrome.manifest';
let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile); let manifest = Cc['@mozilla.org/file/local;1']
.createInstance(Ci.nsILocalFile);
try { try {
file.initWithPath(aData.installPath.path); manifest.initWithPath(aData.installPath.path);
file.append(manifestPath); manifest.append(manifestPath);
Cm.QueryInterface(Ci.nsIComponentRegistrar).autoRegister(file); Cm.QueryInterface(Ci.nsIComponentRegistrar).autoRegister(manifest);
Services.prefs.setBoolPref('extensions.pdf.js.active', true);
} catch (e) { } catch (e) {
log(e); log(e);
} }
} }
function shutdown(aData, aReason) { function shutdown(aData, aReason) {
if (Services.prefs.getBoolPref('extensions.pdf.js.active'))
Services.prefs.setBoolPref('extensions.pdf.js.active', false);
} }
function install(aData, aReason) { function install(aData, aReason) {
let url = 'chrome://pdf.js/content/web/viewer.html?file=%s'; let url = 'chrome://pdf.js/content/web/viewer.html?file=%s';
Services.prefs.setCharPref('extensions.pdf.js.url', url); Services.prefs.setCharPref('extensions.pdf.js.url', url);
Services.prefs.setBoolPref('extensions.pdf.js.active', false);
}
function uninstall(aData, aReason) {
Services.prefs.clearUserPref('extensions.pdf.js.url');
Services.prefs.clearUserPref('extensions.pdf.js.active');
} }

View File

@ -32,6 +32,9 @@ pdfContentHandler.prototype = {
if (!(aRequest instanceof Ci.nsIChannel)) if (!(aRequest instanceof Ci.nsIChannel))
throw NS_ERROR_WONT_HANDLE_CONTENT; throw NS_ERROR_WONT_HANDLE_CONTENT;
if (!Services.prefs.getBoolPref('extensions.pdf.js.active'))
throw NS_ERROR_WONT_HANDLE_CONTENT;
let window = null; let window = null;
let callbacks = aRequest.notificationCallbacks || let callbacks = aRequest.notificationCallbacks ||
aRequest.loadGroup.notificationCallbacks; aRequest.loadGroup.notificationCallbacks;
@ -53,7 +56,7 @@ pdfContentHandler.prototype = {
throw NS_ERROR_WONT_HANDLE_CONTENT; throw NS_ERROR_WONT_HANDLE_CONTENT;
aRequest.cancel(Cr.NS_BINDING_ABORTED); aRequest.cancel(Cr.NS_BINDING_ABORTED);
window.location = url.replace('%s', targetUrl); window.location = url.replace('%s', encodeURIComponent(targetUrl));
}, },
classID: Components.ID('{2278dfd0-b75c-11e0-8257-1ba3d93c9f1a}'), classID: Components.ID('{2278dfd0-b75c-11e0-8257-1ba3d93c9f1a}'),

View File

@ -19,6 +19,6 @@
<em:unpack>true</em:unpack> <em:unpack>true</em:unpack>
<em:creator>Vivien Nicolas</em:creator> <em:creator>Vivien Nicolas</em:creator>
<em:description>pdf.js uri loader</em:description> <em:description>pdf.js uri loader</em:description>
<em:homepageURL>https://github.com/andreasgal/pdf.js/</em:homepageURL> <em:homepageURL>https://github.com/mozilla/pdf.js/</em:homepageURL>
</Description> </Description>
</RDF> </RDF>

6858
pdf.js

File diff suppressed because it is too large Load Diff

844
src/canvas.js Normal file
View File

@ -0,0 +1,844 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
// <canvas> contexts store most of the state we need natively.
// However, PDF needs a bit more state, which we store here.
var CanvasExtraState = (function canvasExtraState() {
function constructor(old) {
// Are soft masks and alpha values shapes or opacities?
this.alphaIsShape = false;
this.fontSize = 0;
this.textMatrix = IDENTITY_MATRIX;
this.leading = 0;
// Current point (in user coordinates)
this.x = 0;
this.y = 0;
// Start of text line (in text coordinates)
this.lineX = 0;
this.lineY = 0;
// Character and word spacing
this.charSpacing = 0;
this.wordSpacing = 0;
this.textHScale = 1;
// Color spaces
this.fillColorSpace = new DeviceGrayCS();
this.fillColorSpaceObj = null;
this.strokeColorSpace = new DeviceGrayCS();
this.strokeColorSpaceObj = null;
this.fillColorObj = null;
this.strokeColorObj = null;
// Default fore and background colors
this.fillColor = '#000000';
this.strokeColor = '#000000';
// Note: fill alpha applies to all non-stroking operations
this.fillAlpha = 1;
this.strokeAlpha = 1;
this.old = old;
}
constructor.prototype = {
clone: function canvasextra_clone() {
return Object.create(this);
},
setCurrentPoint: function canvasextra_setCurrentPoint(x, y) {
this.x = x;
this.y = y;
}
};
return constructor;
})();
function ScratchCanvas(width, height) {
var canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
return canvas;
}
var CanvasGraphics = (function canvasGraphics() {
// Defines the time the executeIRQueue is going to be executing
// before it stops and shedules a continue of execution.
var kExecutionTime = 50;
// Number of IR commands to execute before checking
// if we execute longer then `kExecutionTime`.
var kExecutionTimeCheck = 500;
function constructor(canvasCtx, objs) {
this.ctx = canvasCtx;
this.current = new CanvasExtraState();
this.stateStack = [];
this.pendingClip = null;
this.res = null;
this.xobjs = null;
this.ScratchCanvas = ScratchCanvas;
this.objs = objs;
}
var LINE_CAP_STYLES = ['butt', 'round', 'square'];
var LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
var NORMAL_CLIP = {};
var EO_CLIP = {};
constructor.prototype = {
beginDrawing: function canvasGraphicsBeginDrawing(mediaBox) {
var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height;
this.ctx.save();
switch (mediaBox.rotate) {
case 0:
this.ctx.transform(1, 0, 0, -1, 0, ch);
break;
case 90:
this.ctx.transform(0, 1, 1, 0, 0, 0);
break;
case 180:
this.ctx.transform(-1, 0, 0, 1, cw, 0);
break;
case 270:
this.ctx.transform(0, -1, -1, 0, cw, ch);
break;
}
this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height);
},
executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR,
executionStartIdx, continueCallback) {
var argsArray = codeIR.argsArray;
var fnArray = codeIR.fnArray;
var i = executionStartIdx || 0;
var argsArrayLen = argsArray.length;
var executionEndIdx;
var startTime = Date.now();
var objs = this.objs;
do {
executionEndIdx = Math.min(argsArrayLen, i + kExecutionTimeCheck);
for (i; i < executionEndIdx; i++) {
if (fnArray[i] !== 'dependency') {
this[fnArray[i]].apply(this, argsArray[i]);
} else {
var deps = argsArray[i];
for (var n = 0, nn = deps.length; n < nn; n++) {
var depObjId = deps[n];
// If the promise isn't resolved yet, add the continueCallback
// to the promise and bail out.
if (!objs.isResolved(depObjId)) {
objs.get(depObjId, continueCallback);
return i;
}
}
}
}
// If the entire IRQueue was executed, stop as were done.
if (i == argsArrayLen) {
return i;
}
// If the execution took longer then a certain amount of time, shedule
// to continue exeution after a short delay.
// However, this is only possible if a 'continueCallback' is passed in.
if (continueCallback && (Date.now() - startTime) > kExecutionTime) {
setTimeout(continueCallback, 0);
return i;
}
// If the IRQueue isn't executed completly yet OR the execution time
// was short enough, do another execution round.
} while (true);
},
endDrawing: function canvasGraphicsEndDrawing() {
this.ctx.restore();
},
// Graphics state
setLineWidth: function canvasGraphicsSetLineWidth(width) {
this.ctx.lineWidth = width;
},
setLineCap: function canvasGraphicsSetLineCap(style) {
this.ctx.lineCap = LINE_CAP_STYLES[style];
},
setLineJoin: function canvasGraphicsSetLineJoin(style) {
this.ctx.lineJoin = LINE_JOIN_STYLES[style];
},
setMiterLimit: function canvasGraphicsSetMiterLimit(limit) {
this.ctx.miterLimit = limit;
},
setDash: function canvasGraphicsSetDash(dashArray, dashPhase) {
this.ctx.mozDash = dashArray;
this.ctx.mozDashOffset = dashPhase;
},
setRenderingIntent: function canvasGraphicsSetRenderingIntent(intent) {
TODO('set rendering intent: ' + intent);
},
setFlatness: function canvasGraphicsSetFlatness(flatness) {
TODO('set flatness: ' + flatness);
},
setGState: function canvasGraphicsSetGState(states) {
for (var i = 0, ii = states.length; i < ii; i++) {
var state = states[i];
var key = state[0];
var value = state[1];
switch (key) {
case 'LW':
this.setLineWidth(value);
break;
case 'LC':
this.setLineCap(value);
break;
case 'LJ':
this.setLineJoin(value);
break;
case 'ML':
this.setMiterLimit(value);
break;
case 'D':
this.setDash(value[0], value[1]);
break;
case 'RI':
this.setRenderingIntent(value);
break;
case 'FL':
this.setFlatness(value);
break;
case 'Font':
this.setFont(state[1], state[2]);
break;
case 'CA':
this.current.strokeAlpha = state[1];
break;
case 'ca':
this.current.fillAlpha = state[1];
this.ctx.globalAlpha = state[1];
break;
}
}
},
save: function canvasGraphicsSave() {
this.ctx.save();
var old = this.current;
this.stateStack.push(old);
this.current = old.clone();
},
restore: function canvasGraphicsRestore() {
var prev = this.stateStack.pop();
if (prev) {
this.current = prev;
this.ctx.restore();
}
},
transform: function canvasGraphicsTransform(a, b, c, d, e, f) {
this.ctx.transform(a, b, c, d, e, f);
},
// Path
moveTo: function canvasGraphicsMoveTo(x, y) {
this.ctx.moveTo(x, y);
this.current.setCurrentPoint(x, y);
},
lineTo: function canvasGraphicsLineTo(x, y) {
this.ctx.lineTo(x, y);
this.current.setCurrentPoint(x, y);
},
curveTo: function canvasGraphicsCurveTo(x1, y1, x2, y2, x3, y3) {
this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3);
this.current.setCurrentPoint(x3, y3);
},
curveTo2: function canvasGraphicsCurveTo2(x2, y2, x3, y3) {
var current = this.current;
this.ctx.bezierCurveTo(current.x, current.y, x2, y2, x3, y3);
current.setCurrentPoint(x3, y3);
},
curveTo3: function canvasGraphicsCurveTo3(x1, y1, x3, y3) {
this.curveTo(x1, y1, x3, y3, x3, y3);
this.current.setCurrentPoint(x3, y3);
},
closePath: function canvasGraphicsClosePath() {
this.ctx.closePath();
},
rectangle: function canvasGraphicsRectangle(x, y, width, height) {
this.ctx.rect(x, y, width, height);
},
stroke: function canvasGraphicsStroke(consumePath) {
consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
var ctx = this.ctx;
var strokeColor = this.current.strokeColor;
// For stroke we want to temporarily change the global alpha to the
// stroking alpha.
ctx.globalAlpha = this.current.strokeAlpha;
if (strokeColor && strokeColor.hasOwnProperty('type') &&
strokeColor.type === 'Pattern') {
// for patterns, we transform to pattern space, calculate
// the pattern, call stroke, and restore to user space
ctx.save();
ctx.strokeStyle = strokeColor.getPattern(ctx);
ctx.stroke();
ctx.restore();
} else {
ctx.stroke();
}
if (consumePath)
this.consumePath();
// Restore the global alpha to the fill alpha
ctx.globalAlpha = this.current.fillAlpha;
},
closeStroke: function canvasGraphicsCloseStroke() {
this.closePath();
this.stroke();
},
fill: function canvasGraphicsFill(consumePath) {
consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
var ctx = this.ctx;
var fillColor = this.current.fillColor;
if (fillColor && fillColor.hasOwnProperty('type') &&
fillColor.type === 'Pattern') {
ctx.save();
ctx.fillStyle = fillColor.getPattern(ctx);
ctx.fill();
ctx.restore();
} else {
ctx.fill();
}
if (consumePath)
this.consumePath();
},
eoFill: function canvasGraphicsEoFill() {
var savedFillRule = this.setEOFillRule();
this.fill();
this.restoreFillRule(savedFillRule);
},
fillStroke: function canvasGraphicsFillStroke() {
this.fill(false);
this.stroke(false);
this.consumePath();
},
eoFillStroke: function canvasGraphicsEoFillStroke() {
var savedFillRule = this.setEOFillRule();
this.fillStroke();
this.restoreFillRule(savedFillRule);
},
closeFillStroke: function canvasGraphicsCloseFillStroke() {
this.closePath();
this.fillStroke();
},
closeEOFillStroke: function canvasGraphicsCloseEOFillStroke() {
var savedFillRule = this.setEOFillRule();
this.closePath();
this.fillStroke();
this.restoreFillRule(savedFillRule);
},
endPath: function canvasGraphicsEndPath() {
this.consumePath();
},
// Clipping
clip: function canvasGraphicsClip() {
this.pendingClip = NORMAL_CLIP;
},
eoClip: function canvasGraphicsEoClip() {
this.pendingClip = EO_CLIP;
},
// Text
beginText: function canvasGraphicsBeginText() {
this.current.textMatrix = IDENTITY_MATRIX;
this.current.x = this.current.lineX = 0;
this.current.y = this.current.lineY = 0;
},
endText: function canvasGraphicsEndText() {
},
setCharSpacing: function canvasGraphicsSetCharSpacing(spacing) {
this.current.charSpacing = spacing;
},
setWordSpacing: function canvasGraphicsSetWordSpacing(spacing) {
this.current.wordSpacing = spacing;
},
setHScale: function canvasGraphicsSetHScale(scale) {
this.current.textHScale = scale / 100;
},
setLeading: function canvasGraphicsSetLeading(leading) {
this.current.leading = -leading;
},
setFont: function canvasGraphicsSetFont(fontRefName, size) {
var fontObj = this.objs.get(fontRefName).fontObj;
if (!fontObj) {
throw 'Can\'t find font for ' + fontRefName;
}
var name = fontObj.loadedName || 'sans-serif';
this.current.font = fontObj;
this.current.fontSize = size;
var name = fontObj.loadedName || 'sans-serif';
var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') :
(fontObj.bold ? 'bold' : 'normal');
var italic = fontObj.italic ? 'italic' : 'normal';
var serif = fontObj.serif ? 'serif' : 'sans-serif';
var typeface = '"' + name + '", ' + serif;
var rule = italic + ' ' + bold + ' ' + size + 'px ' + typeface;
this.ctx.font = rule;
},
setTextRenderingMode: function canvasGraphicsSetTextRenderingMode(mode) {
TODO('text rendering mode: ' + mode);
},
setTextRise: function canvasGraphicsSetTextRise(rise) {
TODO('text rise: ' + rise);
},
moveText: function canvasGraphicsMoveText(x, y) {
this.current.x = this.current.lineX += x;
this.current.y = this.current.lineY += y;
},
setLeadingMoveText: function canvasGraphicsSetLeadingMoveText(x, y) {
this.setLeading(-y);
this.moveText(x, y);
},
setTextMatrix: function canvasGraphicsSetTextMatrix(a, b, c, d, e, f) {
this.current.textMatrix = [a, b, c, d, e, f];
this.current.x = this.current.lineX = 0;
this.current.y = this.current.lineY = 0;
},
nextLine: function canvasGraphicsNextLine() {
this.moveText(0, this.current.leading);
},
showText: function canvasGraphicsShowText(text) {
var ctx = this.ctx;
var current = this.current;
var font = current.font;
var glyphs = font.charsToGlyphs(text);
var fontSize = current.fontSize;
var charSpacing = current.charSpacing;
var wordSpacing = current.wordSpacing;
var textHScale = current.textHScale;
var glyphsLength = glyphs.length;
if (font.coded) {
ctx.save();
ctx.transform.apply(ctx, current.textMatrix);
ctx.translate(current.x, current.y);
var fontMatrix = font.fontMatrix || IDENTITY_MATRIX;
ctx.scale(1 / textHScale, 1);
for (var i = 0; i < glyphsLength; ++i) {
var glyph = glyphs[i];
if (glyph === null) {
// word break
this.ctx.translate(wordSpacing, 0);
continue;
}
this.save();
ctx.scale(fontSize, fontSize);
ctx.transform.apply(ctx, fontMatrix);
this.executeIRQueue(glyph.IRQueue);
this.restore();
var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
var width = transformed[0] * fontSize + charSpacing;
ctx.translate(width, 0);
current.x += width;
}
ctx.restore();
} else {
ctx.save();
ctx.transform.apply(ctx, current.textMatrix);
ctx.scale(1, -1);
ctx.translate(current.x, -1 * current.y);
ctx.transform.apply(ctx, font.fontMatrix || IDENTITY_MATRIX);
ctx.scale(1 / textHScale, 1);
var width = 0;
for (var i = 0; i < glyphsLength; ++i) {
var glyph = glyphs[i];
if (glyph === null) {
// word break
width += wordSpacing;
continue;
}
var unicode = glyph.unicode;
var char = (unicode >= 0x10000) ?
String.fromCharCode(0xD800 | ((unicode - 0x10000) >> 10),
0xDC00 | (unicode & 0x3FF)) : String.fromCharCode(unicode);
ctx.fillText(char, width, 0);
width += glyph.width * fontSize * 0.001 + charSpacing;
}
current.x += width;
ctx.restore();
}
},
showSpacedText: function canvasGraphicsShowSpacedText(arr) {
var ctx = this.ctx;
var current = this.current;
var fontSize = current.fontSize;
var textHScale = current.textHScale;
var arrLength = arr.length;
for (var i = 0; i < arrLength; ++i) {
var e = arr[i];
if (isNum(e)) {
current.x -= e * 0.001 * fontSize * textHScale;
} else if (isString(e)) {
this.showText(e);
} else {
malformed('TJ array element ' + e + ' is not string or num');
}
}
},
nextLineShowText: function canvasGraphicsNextLineShowText(text) {
this.nextLine();
this.showText(text);
},
nextLineSetSpacingShowText:
function canvasGraphicsNextLineSetSpacingShowText(wordSpacing,
charSpacing,
text) {
this.setWordSpacing(wordSpacing);
this.setCharSpacing(charSpacing);
this.nextLineShowText(text);
},
// Type3 fonts
setCharWidth: function canvasGraphicsSetCharWidth(xWidth, yWidth) {
// We can safely ignore this since the width should be the same
// as the width in the Widths array.
},
setCharWidthAndBounds: function canvasGraphicsSetCharWidthAndBounds(xWidth,
yWidth,
llx,
lly,
urx,
ury) {
// TODO According to the spec we're also suppose to ignore any operators
// that set color or include images while processing this type3 font.
this.rectangle(llx, lly, urx - llx, ury - lly);
this.clip();
this.endPath();
},
// Color
setStrokeColorSpace: function canvasGraphicsSetStrokeColorSpace(raw) {
this.current.strokeColorSpace = ColorSpace.fromIR(raw);
},
setFillColorSpace: function canvasGraphicsSetFillColorSpace(raw) {
this.current.fillColorSpace = ColorSpace.fromIR(raw);
},
setStrokeColor: function canvasGraphicsSetStrokeColor(/*...*/) {
var cs = this.current.strokeColorSpace;
var color = cs.getRgb(arguments);
this.setStrokeRGBColor.apply(this, color);
},
getColorN_IR_Pattern: function canvasGraphicsGetColorN_IR_Pattern(IR, cs) {
if (IR[0] == 'TilingPattern') {
var args = IR[1];
var base = cs.base;
var color;
if (base) {
var baseComps = base.numComps;
color = [];
for (var i = 0; i < baseComps; ++i)
color.push(args[i]);
color = base.getRgb(color);
}
var pattern = new TilingPattern(IR, color, this.ctx, this.objs);
} else if (IR[0] == 'RadialAxial' || IR[0] == 'Dummy') {
var pattern = Pattern.shadingFromIR(this.ctx, IR);
} else {
throw 'Unkown IR type';
}
return pattern;
},
setStrokeColorN_IR: function canvasGraphicsSetStrokeColorN(/*...*/) {
var cs = this.current.strokeColorSpace;
if (cs.name == 'Pattern') {
this.current.strokeColor = this.getColorN_IR_Pattern(arguments, cs);
} else {
this.setStrokeColor.apply(this, arguments);
}
},
setFillColor: function canvasGraphicsSetFillColor(/*...*/) {
var cs = this.current.fillColorSpace;
var color = cs.getRgb(arguments);
this.setFillRGBColor.apply(this, color);
},
setFillColorN_IR: function canvasGraphicsSetFillColorN(/*...*/) {
var cs = this.current.fillColorSpace;
if (cs.name == 'Pattern') {
this.current.fillColor = this.getColorN_IR_Pattern(arguments, cs);
} else {
this.setFillColor.apply(this, arguments);
}
},
setStrokeGray: function canvasGraphicsSetStrokeGray(gray) {
this.setStrokeRGBColor(gray, gray, gray);
},
setFillGray: function canvasGraphicsSetFillGray(gray) {
this.setFillRGBColor(gray, gray, gray);
},
setStrokeRGBColor: function canvasGraphicsSetStrokeRGBColor(r, g, b) {
var color = Util.makeCssRgb(r, g, b);
this.ctx.strokeStyle = color;
this.current.strokeColor = color;
},
setFillRGBColor: function canvasGraphicsSetFillRGBColor(r, g, b) {
var color = Util.makeCssRgb(r, g, b);
this.ctx.fillStyle = color;
this.current.fillColor = color;
},
setStrokeCMYKColor: function canvasGraphicsSetStrokeCMYKColor(c, m, y, k) {
var color = Util.makeCssCmyk(c, m, y, k);
this.ctx.strokeStyle = color;
this.current.strokeColor = color;
},
setFillCMYKColor: function canvasGraphicsSetFillCMYKColor(c, m, y, k) {
var color = Util.makeCssCmyk(c, m, y, k);
this.ctx.fillStyle = color;
this.current.fillColor = color;
},
shadingFill: function canvasGraphicsShadingFill(patternIR) {
var ctx = this.ctx;
this.save();
ctx.fillStyle = Pattern.shadingFromIR(ctx, patternIR);
var inv = ctx.mozCurrentTransformInverse;
if (inv) {
var canvas = ctx.canvas;
var width = canvas.width;
var height = canvas.height;
var bl = Util.applyTransform([0, 0], inv);
var br = Util.applyTransform([0, width], inv);
var ul = Util.applyTransform([height, 0], inv);
var ur = Util.applyTransform([height, width], inv);
var x0 = Math.min(bl[0], br[0], ul[0], ur[0]);
var y0 = Math.min(bl[1], br[1], ul[1], ur[1]);
var x1 = Math.max(bl[0], br[0], ul[0], ur[0]);
var y1 = Math.max(bl[1], br[1], ul[1], ur[1]);
this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
} else {
// HACK to draw the gradient onto an infinite rectangle.
// PDF gradients are drawn across the entire image while
// Canvas only allows gradients to be drawn in a rectangle
// The following bug should allow us to remove this.
// https://bugzilla.mozilla.org/show_bug.cgi?id=664884
this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
}
this.restore();
},
// Images
beginInlineImage: function canvasGraphicsBeginInlineImage() {
error('Should not call beginInlineImage');
},
beginImageData: function canvasGraphicsBeginImageData() {
error('Should not call beginImageData');
},
paintFormXObjectBegin: function canvasGraphicsPaintFormXObjectBegin(matrix,
bbox) {
this.save();
if (matrix && isArray(matrix) && 6 == matrix.length)
this.transform.apply(this, matrix);
if (bbox && isArray(bbox) && 4 == bbox.length) {
var width = bbox[2] - bbox[0];
var height = bbox[3] - bbox[1];
this.rectangle(bbox[0], bbox[1], width, height);
this.clip();
this.endPath();
}
},
paintFormXObjectEnd: function canvasGraphicsPaintFormXObjectEnd() {
this.restore();
},
paintJpegXObject: function canvasGraphicsPaintJpegXObject(objId, w, h) {
var image = this.objs.get(objId);
if (!image) {
error('Dependent image isn\'t ready yet');
}
this.save();
var ctx = this.ctx;
// scale the image to the unit square
ctx.scale(1 / w, -1 / h);
var domImage = image.getImage();
ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height,
0, -h, w, h);
this.restore();
},
paintImageMaskXObject: function canvasGraphicsPaintImageMaskXObject(
imgArray, inverseDecode, width, height) {
function applyStencilMask(buffer, inverseDecode) {
var imgArrayPos = 0;
var i, j, mask, buf;
// removing making non-masked pixels transparent
var bufferPos = 3; // alpha component offset
for (i = 0; i < height; i++) {
mask = 0;
for (j = 0; j < width; j++) {
if (!mask) {
buf = imgArray[imgArrayPos++];
mask = 128;
}
if (!(buf & mask) == inverseDecode) {
buffer[bufferPos] = 0;
}
bufferPos += 4;
mask >>= 1;
}
}
}
this.save();
var ctx = this.ctx;
var w = width, h = height;
// scale the image to the unit square
ctx.scale(1 / w, -1 / h);
var tmpCanvas = new this.ScratchCanvas(w, h);
var tmpCtx = tmpCanvas.getContext('2d');
var fillColor = this.current.fillColor;
tmpCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') &&
fillColor.type === 'Pattern') ?
fillColor.getPattern(tmpCtx) : fillColor;
tmpCtx.fillRect(0, 0, w, h);
var imgData = tmpCtx.getImageData(0, 0, w, h);
var pixels = imgData.data;
applyStencilMask(pixels, inverseDecode);
tmpCtx.putImageData(imgData, 0, 0);
ctx.drawImage(tmpCanvas, 0, -h);
this.restore();
},
paintImageXObject: function canvasGraphicsPaintImageXObject(imgData) {
this.save();
var ctx = this.ctx;
var w = imgData.width;
var h = imgData.height;
// scale the image to the unit square
ctx.scale(1 / w, -1 / h);
var tmpCanvas = new this.ScratchCanvas(w, h);
var tmpCtx = tmpCanvas.getContext('2d');
var tmpImgData;
// Some browsers can set an UInt8Array directly as imageData, some
// can't. As long as we don't have proper feature detection, just
// copy over each pixel and set the imageData that way.
tmpImgData = tmpCtx.getImageData(0, 0, w, h);
// Copy over the imageData.
var tmpImgDataPixels = tmpImgData.data;
var len = tmpImgDataPixels.length;
while (len--) {
tmpImgDataPixels[len] = imgData.data[len];
}
tmpCtx.putImageData(tmpImgData, 0, 0);
ctx.drawImage(tmpCanvas, 0, -h);
this.restore();
},
// Marked content
markPoint: function canvasGraphicsMarkPoint(tag) {
TODO('Marked content');
},
markPointProps: function canvasGraphicsMarkPointProps(tag, properties) {
TODO('Marked content');
},
beginMarkedContent: function canvasGraphicsBeginMarkedContent(tag) {
TODO('Marked content');
},
beginMarkedContentProps:
function canvasGraphicsBeginMarkedContentProps(tag, properties) {
TODO('Marked content');
},
endMarkedContent: function canvasGraphicsEndMarkedContent() {
TODO('Marked content');
},
// Compatibility
beginCompat: function canvasGraphicsBeginCompat() {
TODO('ignore undefined operators (should we do that anyway?)');
},
endCompat: function canvasGraphicsEndCompat() {
TODO('stop ignoring undefined operators');
},
// Helper functions
consumePath: function canvasGraphicsConsumePath() {
if (this.pendingClip) {
var savedFillRule = null;
if (this.pendingClip == EO_CLIP)
savedFillRule = this.setEOFillRule();
this.ctx.clip();
this.pendingClip = null;
if (savedFillRule !== null)
this.restoreFillRule(savedFillRule);
}
this.ctx.beginPath();
},
// We generally keep the canvas context set for
// nonzero-winding, and just set evenodd for the operations
// that need them.
setEOFillRule: function canvasGraphicsSetEOFillRule() {
var savedFillRule = this.ctx.mozFillRule;
this.ctx.mozFillRule = 'evenodd';
return savedFillRule;
},
restoreFillRule: function canvasGraphicsRestoreFillRule(rule) {
this.ctx.mozFillRule = rule;
}
};
return constructor;
})();

View File

@ -6930,3 +6930,4 @@ var CIDToUnicodeMaps = {
{f: 39, c: 19576}, {f: 111, c: 19620}, {f: 148, c: 19738}, {f: 39, c: 19576}, {f: 111, c: 19620}, {f: 148, c: 19738},
{f: 7, c: 19887}] {f: 7, c: 19887}]
}; };

392
src/colorspace.js Normal file
View File

@ -0,0 +1,392 @@
/* -*- 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 ColorSpace = (function colorSpaceColorSpace() {
// Constructor should define this.numComps, this.defaultColor, this.name
function constructor() {
error('should not call ColorSpace constructor');
}
constructor.prototype = {
// Input: array of size numComps representing color component values
// Output: array of rgb values, each value ranging from [0.1]
getRgb: function colorSpaceGetRgb(color) {
error('Should not call ColorSpace.getRgb: ' + color);
},
// Input: Uint8Array of component values, each value scaled to [0,255]
// Output: Uint8Array of rgb values, each value scaled to [0,255]
getRgbBuffer: function colorSpaceGetRgbBuffer(input) {
error('Should not call ColorSpace.getRgbBuffer: ' + input);
}
};
constructor.parse = function colorSpaceParse(cs, xref, res) {
var IR = constructor.parseToIR(cs, xref, res);
if (IR instanceof SeparationCS)
return IR;
return constructor.fromIR(IR);
};
constructor.fromIR = function colorSpaceFromIR(IR) {
var name = isArray(IR) ? IR[0] : IR;
switch (name) {
case 'DeviceGrayCS':
return new DeviceGrayCS();
case 'DeviceRgbCS':
return new DeviceRgbCS();
case 'DeviceCmykCS':
return new DeviceCmykCS();
case 'PatternCS':
var basePatternCS = IR[1];
if (basePatternCS)
basePatternCS = ColorSpace.fromIR(basePatternCS);
return new PatternCS(basePatternCS);
case 'IndexedCS':
var baseIndexedCS = IR[1];
var hiVal = IR[2];
var lookup = IR[3];
return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup);
case 'SeparationCS':
var alt = IR[1];
var tintFnIR = IR[2];
return new SeparationCS(ColorSpace.fromIR(alt),
PDFFunction.fromIR(tintFnIR));
default:
error('Unkown name ' + name);
}
return null;
};
constructor.parseToIR = function colorSpaceParseToIR(cs, xref, res) {
if (isName(cs)) {
var colorSpaces = xref.fetchIfRef(res.get('ColorSpace'));
if (isDict(colorSpaces)) {
var refcs = colorSpaces.get(cs.name);
if (refcs)
cs = refcs;
}
}
cs = xref.fetchIfRef(cs);
var mode;
if (isName(cs)) {
mode = cs.name;
this.mode = mode;
switch (mode) {
case 'DeviceGray':
case 'G':
return 'DeviceGrayCS';
case 'DeviceRGB':
case 'RGB':
return 'DeviceRgbCS';
case 'DeviceCMYK':
case 'CMYK':
return 'DeviceCmykCS';
case 'Pattern':
return ['PatternCS', null];
default:
error('unrecognized colorspace ' + mode);
}
} else if (isArray(cs)) {
mode = cs[0].name;
this.mode = mode;
switch (mode) {
case 'DeviceGray':
case 'G':
return 'DeviceGrayCS';
case 'DeviceRGB':
case 'RGB':
return 'DeviceRgbCS';
case 'DeviceCMYK':
case 'CMYK':
return 'DeviceCmykCS';
case 'CalGray':
return 'DeviceGrayCS';
case 'CalRGB':
return 'DeviceRgbCS';
case 'ICCBased':
var stream = xref.fetchIfRef(cs[1]);
var dict = stream.dict;
var numComps = dict.get('N');
if (numComps == 1)
return 'DeviceGrayCS';
if (numComps == 3)
return 'DeviceRgbCS';
if (numComps == 4)
return 'DeviceCmykCS';
break;
case 'Pattern':
var basePatternCS = cs[1];
if (basePatternCS)
basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res);
return ['PatternCS', basePatternCS];
case 'Indexed':
var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res);
var hiVal = cs[2] + 1;
var lookup = xref.fetchIfRef(cs[3]);
return ['IndexedCS', baseIndexedCS, hiVal, lookup];
case 'Separation':
var alt = ColorSpace.parseToIR(cs[2], xref, res);
var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3]));
return ['SeparationCS', alt, tintFnIR];
case 'Lab':
case 'DeviceN':
default:
error('unimplemented color space object "' + mode + '"');
}
} else {
error('unrecognized color space object: "' + cs + '"');
}
return null;
};
return constructor;
})();
var SeparationCS = (function separationCS() {
function constructor(base, tintFn) {
this.name = 'Separation';
this.numComps = 1;
this.defaultColor = [1];
this.base = base;
this.tintFn = tintFn;
}
constructor.prototype = {
getRgb: function sepcs_getRgb(color) {
var tinted = this.tintFn(color);
return this.base.getRgb(tinted);
},
getRgbBuffer: function sepcs_getRgbBuffer(input, bits) {
var tintFn = this.tintFn;
var base = this.base;
var scale = 1 / ((1 << bits) - 1);
var length = input.length;
var pos = 0;
var numComps = base.numComps;
var baseBuf = new Uint8Array(numComps * length);
for (var i = 0; i < length; ++i) {
var scaled = input[i] * scale;
var tinted = tintFn([scaled]);
for (var j = 0; j < numComps; ++j)
baseBuf[pos++] = 255 * tinted[j];
}
return base.getRgbBuffer(baseBuf, 8);
}
};
return constructor;
})();
var PatternCS = (function patternCS() {
function constructor(baseCS) {
this.name = 'Pattern';
this.base = baseCS;
}
constructor.prototype = {};
return constructor;
})();
var IndexedCS = (function indexedCS() {
function constructor(base, highVal, lookup) {
this.name = 'Indexed';
this.numComps = 1;
this.defaultColor = [0];
this.base = base;
this.highVal = highVal;
var baseNumComps = base.numComps;
var length = baseNumComps * highVal;
var lookupArray = new Uint8Array(length);
if (isStream(lookup)) {
var bytes = lookup.getBytes(length);
lookupArray.set(bytes);
} else if (isString(lookup)) {
for (var i = 0; i < length; ++i)
lookupArray[i] = lookup.charCodeAt(i);
} else {
error('Unrecognized lookup table: ' + lookup);
}
this.lookup = lookupArray;
}
constructor.prototype = {
getRgb: function indexcs_getRgb(color) {
var numComps = this.base.numComps;
var start = color[0] * numComps;
var c = [];
for (var i = start, ii = start + numComps; i < ii; ++i)
c.push(this.lookup[i]);
return this.base.getRgb(c);
},
getRgbBuffer: function indexcs_getRgbBuffer(input) {
var base = this.base;
var numComps = base.numComps;
var lookup = this.lookup;
var length = input.length;
var baseBuf = new Uint8Array(length * numComps);
var baseBufPos = 0;
for (var i = 0; i < length; ++i) {
var lookupPos = input[i] * numComps;
for (var j = 0; j < numComps; ++j) {
baseBuf[baseBufPos++] = lookup[lookupPos + j];
}
}
return base.getRgbBuffer(baseBuf, 8);
}
};
return constructor;
})();
var DeviceGrayCS = (function deviceGrayCS() {
function constructor() {
this.name = 'DeviceGray';
this.numComps = 1;
this.defaultColor = [0];
}
constructor.prototype = {
getRgb: function graycs_getRgb(color) {
var c = color[0];
return [c, c, c];
},
getRgbBuffer: function graycs_getRgbBuffer(input, bits) {
var scale = 255 / ((1 << bits) - 1);
var length = input.length;
var rgbBuf = new Uint8Array(length * 3);
for (var i = 0, j = 0; i < length; ++i) {
var c = (scale * input[i]) | 0;
rgbBuf[j++] = c;
rgbBuf[j++] = c;
rgbBuf[j++] = c;
}
return rgbBuf;
}
};
return constructor;
})();
var DeviceRgbCS = (function deviceRgbCS() {
function constructor() {
this.name = 'DeviceRGB';
this.numComps = 3;
this.defaultColor = [0, 0, 0];
}
constructor.prototype = {
getRgb: function rgbcs_getRgb(color) {
return color;
},
getRgbBuffer: function rgbcs_getRgbBuffer(input, bits) {
if (bits == 8)
return input;
var scale = 255 / ((1 << bits) - 1);
var i, length = input.length;
var rgbBuf = new Uint8Array(length);
for (i = 0; i < length; ++i)
rgbBuf[i] = (scale * input[i]) | 0;
return rgbBuf;
}
};
return constructor;
})();
var DeviceCmykCS = (function deviceCmykCS() {
function constructor() {
this.name = 'DeviceCMYK';
this.numComps = 4;
this.defaultColor = [0, 0, 0, 1];
}
constructor.prototype = {
getRgb: function cmykcs_getRgb(color) {
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;
// this is a matrix multiplication, unrolled for performance
// code is taken from the poppler implementation
x = c1 * m1 * y1 * k1; // 0 0 0 0
r = g = b = x;
x = c1 * m1 * y1 * k; // 0 0 0 1
r += 0.1373 * x;
g += 0.1216 * x;
b += 0.1255 * x;
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];
},
getRgbBuffer: function cmykcs_getRgbBuffer(colorBuf, bits) {
var scale = 1 / ((1 << bits) - 1);
var length = colorBuf.length / 4;
var rgbBuf = new Uint8Array(length * 3);
var rgbBufPos = 0;
var colorBufPos = 0;
for (var i = 0; i < length; i++) {
var cmyk = [];
for (var j = 0; j < 4; ++j)
cmyk.push(scale * colorBuf[colorBufPos++]);
var rgb = this.getRgb(cmyk);
for (var j = 0; j < 3; ++j)
rgbBuf[rgbBufPos++] = Math.round(rgb[j] * 255);
}
return rgbBuf;
}
};
return constructor;
})();

650
src/core.js Normal file
View File

@ -0,0 +1,650 @@
/* -*- 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 globalScope = (typeof window === 'undefined') ? this : window;
var ERRORS = 0, WARNINGS = 1, TODOS = 5;
var verbosity = WARNINGS;
// The global PDFJS object exposes the API
// In production, it will be declared outside a global wrapper
// In development, it will be declared here
if (!globalScope.PDFJS) {
globalScope.PDFJS = {};
}
// Temporarily disabling workers until 'localhost' FF bugfix lands:
// https://bugzilla.mozilla.org/show_bug.cgi?id=683280
globalScope.PDFJS.disableWorker = true;
// getPdf()
// Convenience function to perform binary Ajax GET
// Usage: getPdf('http://...', callback)
// getPdf({
// url:String ,
// [,progress:Function, error:Function]
// },
// callback)
function getPdf(arg, callback) {
var params = arg;
if (typeof arg === 'string')
params = { url: arg };
var xhr = new XMLHttpRequest();
xhr.open('GET', params.url);
xhr.mozResponseType = xhr.responseType = 'arraybuffer';
xhr.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
if ('progress' in params)
xhr.onprogress = params.progress || undefined;
if ('error' in params)
xhr.onerror = params.error || undefined;
xhr.onreadystatechange = function getPdfOnreadystatechange() {
if (xhr.readyState === 4 && xhr.status === xhr.expected) {
var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse ||
xhr.responseArrayBuffer || xhr.response);
callback(data);
}
};
xhr.send(null);
}
globalScope.PDFJS.getPdf = getPdf;
var Page = (function pagePage() {
function constructor(xref, pageNumber, pageDict, ref) {
this.pageNumber = pageNumber;
this.pageDict = pageDict;
this.stats = {
create: Date.now(),
compile: 0.0,
fonts: 0.0,
images: 0.0,
render: 0.0
};
this.xref = xref;
this.ref = ref;
}
constructor.prototype = {
getPageProp: function pageGetPageProp(key) {
return this.xref.fetchIfRef(this.pageDict.get(key));
},
inheritPageProp: function pageInheritPageProp(key) {
var dict = this.pageDict;
var obj = dict.get(key);
while (obj === undefined) {
dict = this.xref.fetchIfRef(dict.get('Parent'));
if (!dict)
break;
obj = dict.get(key);
}
return obj;
},
get content() {
return shadow(this, 'content', this.getPageProp('Contents'));
},
get resources() {
return shadow(this, 'resources', this.inheritPageProp('Resources'));
},
get mediaBox() {
var obj = this.inheritPageProp('MediaBox');
// Reset invalid media box to letter size.
if (!isArray(obj) || obj.length !== 4)
obj = [0, 0, 612, 792];
return shadow(this, 'mediaBox', obj);
},
get view() {
var obj = this.inheritPageProp('CropBox');
var view = {
x: 0,
y: 0,
width: this.width,
height: this.height
};
if (isArray(obj) && obj.length == 4) {
var tl = this.rotatePoint(obj[0], obj[1]);
var br = this.rotatePoint(obj[2], obj[3]);
view.x = Math.min(tl.x, br.x);
view.y = Math.min(tl.y, br.y);
view.width = Math.abs(tl.x - br.x);
view.height = Math.abs(tl.y - br.y);
}
return shadow(this, 'cropBox', view);
},
get annotations() {
return shadow(this, 'annotations', this.inheritPageProp('Annots'));
},
get width() {
var mediaBox = this.mediaBox;
var rotate = this.rotate;
var width;
if (rotate == 0 || rotate == 180) {
width = (mediaBox[2] - mediaBox[0]);
} else {
width = (mediaBox[3] - mediaBox[1]);
}
return shadow(this, 'width', width);
},
get height() {
var mediaBox = this.mediaBox;
var rotate = this.rotate;
var height;
if (rotate == 0 || rotate == 180) {
height = (mediaBox[3] - mediaBox[1]);
} else {
height = (mediaBox[2] - mediaBox[0]);
}
return shadow(this, 'height', height);
},
get rotate() {
var rotate = this.inheritPageProp('Rotate') || 0;
// Normalize rotation so it's a multiple of 90 and between 0 and 270
if (rotate % 90 != 0) {
rotate = 0;
} else if (rotate >= 360) {
rotate = rotate % 360;
} else if (rotate < 0) {
// The spec doesn't cover negatives, assume its counterclockwise
// rotation. The following is the other implementation of modulo.
rotate = ((rotate % 360) + 360) % 360;
}
return shadow(this, 'rotate', rotate);
},
startRenderingFromIRQueue: function pageStartRenderingFromIRQueue(
IRQueue, fonts) {
var self = this;
this.IRQueue = IRQueue;
var gfx = new CanvasGraphics(this.ctx, this.objs);
var startTime = Date.now();
var displayContinuation = function pageDisplayContinuation() {
// Always defer call to display() to work around bug in
// Firefox error reporting from XHR callbacks.
setTimeout(function pageSetTimeout() {
try {
self.display(gfx, self.callback);
} catch (e) {
if (self.callback) self.callback(e.toString());
throw e;
}
});
};
this.ensureFonts(fonts,
function pageStartRenderingFromIRQueueEnsureFonts() {
displayContinuation();
});
},
getIRQueue: function pageGetIRQueue(handler, dependency) {
if (this.IRQueue) {
// content was compiled
return this.IRQueue;
}
var xref = this.xref;
var content = xref.fetchIfRef(this.content);
var resources = xref.fetchIfRef(this.resources);
if (isArray(content)) {
// fetching items
var i, n = content.length;
for (i = 0; i < n; ++i)
content[i] = xref.fetchIfRef(content[i]);
content = new StreamsSequenceStream(content);
}
var pe = this.pe = new PartialEvaluator(
xref, handler, 'p' + this.pageNumber + '_');
var IRQueue = {};
return (this.IRQueue = pe.getIRQueue(content, resources, IRQueue,
dependency));
},
ensureFonts: function pageEnsureFonts(fonts, callback) {
// Convert the font names to the corresponding font obj.
for (var i = 0, ii = fonts.length; i < ii; i++) {
fonts[i] = this.objs.objs[fonts[i]].data;
}
// Load all the fonts
var fontObjs = FontLoader.bind(
fonts,
function pageEnsureFontsFontObjs(fontObjs) {
this.stats.fonts = Date.now();
callback.call(this);
}.bind(this),
this.objs
);
},
display: function pageDisplay(gfx, callback) {
var xref = this.xref;
var resources = xref.fetchIfRef(this.resources);
var mediaBox = xref.fetchIfRef(this.mediaBox);
assertWellFormed(isDict(resources), 'invalid page resources');
gfx.xref = xref;
gfx.res = resources;
gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1],
width: this.width,
height: this.height,
rotate: this.rotate });
var startIdx = 0;
var length = this.IRQueue.fnArray.length;
var IRQueue = this.IRQueue;
var self = this;
var startTime = Date.now();
function next() {
startIdx = gfx.executeIRQueue(IRQueue, startIdx, next);
if (startIdx == length) {
self.stats.render = Date.now();
if (callback) callback();
}
}
next();
},
rotatePoint: function pageRotatePoint(x, y, reverse) {
var rotate = reverse ? (360 - this.rotate) : this.rotate;
switch (rotate) {
case 180:
return {x: this.width - x, y: y};
case 90:
return {x: this.width - y, y: this.height - x};
case 270:
return {x: y, y: x};
case 360:
case 0:
default:
return {x: x, y: this.height - y};
}
},
getLinks: function pageGetLinks() {
var xref = this.xref;
var annotations = xref.fetchIfRef(this.annotations) || [];
var i, n = annotations.length;
var links = [];
for (i = 0; i < n; ++i) {
var annotation = xref.fetch(annotations[i]);
if (!isDict(annotation))
continue;
var subtype = annotation.get('Subtype');
if (!isName(subtype) || subtype.name != 'Link')
continue;
var rect = annotation.get('Rect');
var topLeftCorner = this.rotatePoint(rect[0], rect[1]);
var bottomRightCorner = this.rotatePoint(rect[2], rect[3]);
var link = {};
link.x = Math.min(topLeftCorner.x, bottomRightCorner.x);
link.y = Math.min(topLeftCorner.y, bottomRightCorner.y);
link.width = Math.abs(topLeftCorner.x - bottomRightCorner.x);
link.height = Math.abs(topLeftCorner.y - bottomRightCorner.y);
var a = this.xref.fetchIfRef(annotation.get('A'));
if (a) {
switch (a.get('S').name) {
case 'URI':
link.url = a.get('URI');
break;
case 'GoTo':
link.dest = a.get('D');
break;
default:
TODO('other link types');
}
} else if (annotation.has('Dest')) {
// simple destination link
var dest = annotation.get('Dest');
link.dest = isName(dest) ? dest.name : dest;
}
links.push(link);
}
return links;
},
startRendering: function pageStartRendering(ctx, callback) {
this.ctx = ctx;
this.callback = callback;
this.startRenderingTime = Date.now();
this.pdf.startRendering(this);
}
};
return constructor;
})();
/**
* The `PDFDocModel` holds all the data of the PDF file. Compared to the
* `PDFDoc`, this one doesn't have any job management code.
* Right now there exists one PDFDocModel on the main thread + one object
* for each worker. If there is no worker support enabled, there are two
* `PDFDocModel` objects on the main thread created.
* TODO: Refactor the internal object structure, such that there is no
* need for the `PDFDocModel` anymore and there is only one object on the
* main thread and not one entire copy on each worker instance.
*/
var PDFDocModel = (function pdfDoc() {
function constructor(arg, callback) {
if (isStream(arg))
init.call(this, arg);
else if (isArrayBuffer(arg))
init.call(this, new Stream(arg));
else
error('PDFDocModel: Unknown argument type');
}
function init(stream) {
assertWellFormed(stream.length > 0, 'stream must have data');
this.stream = stream;
this.setup();
}
function find(stream, needle, limit, backwards) {
var pos = stream.pos;
var end = stream.end;
var str = '';
if (pos + limit > end)
limit = end - pos;
for (var n = 0; n < limit; ++n)
str += stream.getChar();
stream.pos = pos;
var index = backwards ? str.lastIndexOf(needle) : str.indexOf(needle);
if (index == -1)
return false; /* not found */
stream.pos += index;
return true; /* found */
}
constructor.prototype = {
get linearization() {
var length = this.stream.length;
var linearization = false;
if (length) {
linearization = new Linearization(this.stream);
if (linearization.length != length)
linearization = false;
}
// shadow the prototype getter with a data property
return shadow(this, 'linearization', linearization);
},
get startXRef() {
var stream = this.stream;
var startXRef = 0;
var linearization = this.linearization;
if (linearization) {
// Find end of first obj.
stream.reset();
if (find(stream, 'endobj', 1024))
startXRef = stream.pos + 6;
} else {
// Find startxref at the end of the file.
var start = stream.end - 1024;
if (start < 0)
start = 0;
stream.pos = start;
if (find(stream, 'startxref', 1024, true)) {
stream.skip(9);
var ch;
do {
ch = stream.getChar();
} while (Lexer.isSpace(ch));
var str = '';
while ((ch - '0') <= 9) {
str += ch;
ch = stream.getChar();
}
startXRef = parseInt(str, 10);
if (isNaN(startXRef))
startXRef = 0;
}
}
// shadow the prototype getter with a data property
return shadow(this, 'startXRef', startXRef);
},
get mainXRefEntriesOffset() {
var mainXRefEntriesOffset = 0;
var linearization = this.linearization;
if (linearization)
mainXRefEntriesOffset = linearization.mainXRefEntriesOffset;
// shadow the prototype getter with a data property
return shadow(this, 'mainXRefEntriesOffset', mainXRefEntriesOffset);
},
// Find the header, remove leading garbage and setup the stream
// starting from the header.
checkHeader: function pdfDocCheckHeader() {
var stream = this.stream;
stream.reset();
if (find(stream, '%PDF-', 1024)) {
// Found the header, trim off any garbage before it.
stream.moveStart();
return;
}
// May not be a PDF file, continue anyway.
},
setup: function pdfDocSetup(ownerPassword, userPassword) {
this.checkHeader();
this.xref = new XRef(this.stream,
this.startXRef,
this.mainXRefEntriesOffset);
this.catalog = new Catalog(this.xref);
},
get numPages() {
var linearization = this.linearization;
var num = linearization ? linearization.numPages : this.catalog.numPages;
// shadow the prototype getter
return shadow(this, 'numPages', num);
},
getPage: function pdfDocGetPage(n) {
return this.catalog.getPage(n);
}
};
return constructor;
})();
var PDFDoc = (function pdfDoc() {
function constructor(arg, callback) {
var stream = null;
var data = null;
if (isStream(arg)) {
stream = arg;
data = arg.bytes;
} else if (isArrayBuffer(arg)) {
stream = new Stream(arg);
data = arg;
} else {
error('PDFDoc: Unknown argument type');
}
this.data = data;
this.stream = stream;
this.pdf = new PDFDocModel(stream);
this.catalog = this.pdf.catalog;
this.objs = new PDFObjects();
this.pageCache = [];
this.workerReadyPromise = new Promise('workerReady');
// If worker support isn't disabled explicit and the browser has worker
// support, create a new web worker and test if it/the browser fullfills
// all requirements to run parts of pdf.js in a web worker.
// Right now, the requirement is, that an Uint8Array is still an Uint8Array
// as it arrives on the worker. Chrome added this with version 15.
if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
var workerSrc = PDFJS.workerSrc;
if (typeof workerSrc === 'undefined') {
throw 'No PDFJS.workerSrc specified';
}
var worker = new Worker(workerSrc);
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) {
if (supportTypedArray) {
this.worker = worker;
this.setupMessageHandler(messageHandler);
} else {
this.setupFakeWorker();
}
}.bind(this));
var testObj = new Uint8Array(1);
messageHandler.send('test', testObj);
} else {
this.setupFakeWorker();
}
this.fontsLoading = {};
}
constructor.prototype = {
setupFakeWorker: function() {
// If we don't use a worker, just post/sendMessage to the main thread.
var fakeWorker = {
postMessage: function pdfDocPostMessage(obj) {
fakeWorker.onmessage({data: obj});
},
terminate: function pdfDocTerminate() {}
};
var messageHandler = new MessageHandler('main', fakeWorker);
this.setupMessageHandler(messageHandler);
// If the main thread is our worker, setup the handling for the messages
// the main thread sends to it self.
WorkerMessageHandler.setup(messageHandler);
},
setupMessageHandler: function(messageHandler) {
this.messageHandler = messageHandler;
messageHandler.on('page', function pdfDocPage(data) {
var pageNum = data.pageNum;
var page = this.pageCache[pageNum];
var depFonts = data.depFonts;
page.startRenderingFromIRQueue(data.IRQueue, depFonts);
}, this);
messageHandler.on('obj', function pdfDocObj(data) {
var id = data[0];
var type = data[1];
switch (type) {
case 'JpegStream':
var IR = data[2];
new JpegImage(id, IR, this.objs);
break;
case 'Font':
var name = data[2];
var file = data[3];
var properties = data[4];
if (file) {
var fontFileDict = new Dict();
fontFileDict.map = file.dict.map;
var fontFile = new Stream(file.bytes, file.start,
file.end - file.start, fontFileDict);
// Check if this is a FlateStream. Otherwise just use the created
// Stream one. This makes complex_ttf_font.pdf work.
var cmf = file.bytes[0];
if ((cmf & 0x0f) == 0x08) {
file = new FlateStream(fontFile);
} else {
file = fontFile;
}
}
// For now, resolve the font object here direclty. The real font
// object is then created in FontLoader.bind().
this.objs.resolve(id, {
name: name,
file: file,
properties: properties
});
break;
default:
throw 'Got unkown object type ' + type;
}
}, this);
messageHandler.on('font_ready', function pdfDocFontReady(data) {
var id = data[0];
var font = new FontShape(data[1]);
// If there is no string, then there is nothing to attach to the DOM.
if (!font.str) {
this.objs.resolve(id, font);
} else {
this.objs.setData(id, font);
}
}.bind(this));
setTimeout(function pdfDocFontReadySetTimeout() {
messageHandler.send('doc', this.data);
this.workerReadyPromise.resolve(true);
}.bind(this));
},
get numPages() {
return this.pdf.numPages;
},
startRendering: function pdfDocStartRendering(page) {
// The worker might not be ready to receive the page request yet.
this.workerReadyPromise.then(function pdfDocStartRenderingThen() {
this.messageHandler.send('page_request', page.pageNumber + 1);
}.bind(this));
},
getPage: function pdfDocGetPage(n) {
if (this.pageCache[n])
return this.pageCache[n];
var page = this.pdf.getPage(n);
// Add a reference to the objects such that Page can forward the reference
// to the CanvasGraphics and so on.
page.objs = this.objs;
page.pdf = this;
return (this.pageCache[n] = page);
},
destroy: function pdfDocDestroy() {
if (this.worker)
this.worker.terminate();
if (this.fontWorker)
this.fontWorker.terminate();
for (var n in this.pageCache)
delete this.pageCache[n];
delete this.data;
delete this.stream;
delete this.pdf;
delete this.catalog;
}
};
return constructor;
})();
globalScope.PDFJS.PDFDoc = PDFDoc;

View File

@ -338,7 +338,7 @@ var AES128Cipher = (function aes128Cipher() {
} }
function decryptBlock2(data) { function decryptBlock2(data) {
var i, j, sourceLength = data.length, var i, j, ii, sourceLength = data.length,
buffer = this.buffer, bufferLength = this.bufferPosition, buffer = this.buffer, bufferLength = this.bufferPosition,
result = [], iv = this.iv; result = [], iv = this.iv;
for (i = 0; i < sourceLength; ++i) { for (i = 0; i < sourceLength; ++i) {
@ -366,7 +366,7 @@ var AES128Cipher = (function aes128Cipher() {
return result[0]; return result[0];
// combining plain text blocks into one // combining plain text blocks into one
var output = new Uint8Array(16 * result.length); var output = new Uint8Array(16 * result.length);
for (i = 0, j = 0; i < result.length; ++i, j += 16) for (i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16)
output.set(result[i], j); output.set(result[i], j);
return output; return output;
} }
@ -595,3 +595,4 @@ var CipherTransformFactory = (function cipherTransformFactory() {
return constructor; return constructor;
})(); })();

921
src/evaluator.js Normal file
View File

@ -0,0 +1,921 @@
/* -*- 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 PartialEvaluator = (function partialEvaluator() {
function constructor(xref, handler, uniquePrefix) {
this.state = new EvalState();
this.stateStack = [];
this.xref = xref;
this.handler = handler;
this.uniquePrefix = uniquePrefix;
this.objIdCounter = 0;
}
var OP_MAP = {
// Graphics state
w: 'setLineWidth',
J: 'setLineCap',
j: 'setLineJoin',
M: 'setMiterLimit',
d: 'setDash',
ri: 'setRenderingIntent',
i: 'setFlatness',
gs: 'setGState',
q: 'save',
Q: 'restore',
cm: 'transform',
// Path
m: 'moveTo',
l: 'lineTo',
c: 'curveTo',
v: 'curveTo2',
y: 'curveTo3',
h: 'closePath',
re: 'rectangle',
S: 'stroke',
s: 'closeStroke',
f: 'fill',
F: 'fill',
'f*': 'eoFill',
B: 'fillStroke',
'B*': 'eoFillStroke',
b: 'closeFillStroke',
'b*': 'closeEOFillStroke',
n: 'endPath',
// Clipping
W: 'clip',
'W*': 'eoClip',
// Text
BT: 'beginText',
ET: 'endText',
Tc: 'setCharSpacing',
Tw: 'setWordSpacing',
Tz: 'setHScale',
TL: 'setLeading',
Tf: 'setFont',
Tr: 'setTextRenderingMode',
Ts: 'setTextRise',
Td: 'moveText',
TD: 'setLeadingMoveText',
Tm: 'setTextMatrix',
'T*': 'nextLine',
Tj: 'showText',
TJ: 'showSpacedText',
"'": 'nextLineShowText',
'"': 'nextLineSetSpacingShowText',
// Type3 fonts
d0: 'setCharWidth',
d1: 'setCharWidthAndBounds',
// Color
CS: 'setStrokeColorSpace',
cs: 'setFillColorSpace',
SC: 'setStrokeColor',
SCN: 'setStrokeColorN',
sc: 'setFillColor',
scn: 'setFillColorN',
G: 'setStrokeGray',
g: 'setFillGray',
RG: 'setStrokeRGBColor',
rg: 'setFillRGBColor',
K: 'setStrokeCMYKColor',
k: 'setFillCMYKColor',
// Shading
sh: 'shadingFill',
// Images
BI: 'beginInlineImage',
ID: 'beginImageData',
EI: 'endInlineImage',
// XObjects
Do: 'paintXObject',
// Marked content
MP: 'markPoint',
DP: 'markPointProps',
BMC: 'beginMarkedContent',
BDC: 'beginMarkedContentProps',
EMC: 'endMarkedContent',
// Compatibility
BX: 'beginCompat',
EX: 'endCompat'
};
constructor.prototype = {
getIRQueue: function partialEvaluatorGetIRQueue(stream, resources,
queue, dependency) {
var self = this;
var xref = this.xref;
var handler = this.handler;
var uniquePrefix = this.uniquePrefix;
function insertDependency(depList) {
fnArray.push('dependency');
argsArray.push(depList);
for (var i = 0, ii = depList.length; i < ii; i++) {
var dep = depList[i];
if (dependency.indexOf(dep) == -1) {
dependency.push(depList[i]);
}
}
}
function handleSetFont(fontName, fontRef) {
var loadedName = null;
var fontRes = resources.get('Font');
// TODO: TOASK: Is it possible to get here? If so, what does
// args[0].name should be like???
assert(fontRes, 'fontRes not available');
fontRes = xref.fetchIfRef(fontRes);
fontRef = fontRef || fontRes.get(fontName);
var font = xref.fetchIfRef(fontRef);
assertWellFormed(isDict(font));
if (!font.translated) {
font.translated = self.translateFont(font, xref, resources, handler,
uniquePrefix, dependency);
if (font.translated) {
// keep track of each font we translated so the caller can
// load them asynchronously before calling display on a page
loadedName = 'font_' + uniquePrefix + ++self.objIdCounter;
font.translated.properties.loadedName = loadedName;
font.loadedName = loadedName;
var translated = font.translated;
handler.send('obj', [
loadedName,
'Font',
translated.name,
translated.file,
translated.properties
]);
}
}
loadedName = loadedName || font.loadedName;
// Ensure the font is ready before the font is set
// and later on used for drawing.
// TODO: This should get insert to the IRQueue only once per
// page.
insertDependency([loadedName]);
return loadedName;
}
function buildPaintImageXObject(image, inline) {
var dict = image.dict;
var w = dict.get('Width', 'W');
var h = dict.get('Height', 'H');
if (image instanceof JpegStream) {
var objId = 'img_' + uniquePrefix + ++self.objIdCounter;
handler.send('obj', [objId, 'JpegStream', image.getIR()]);
// Add the dependency on the image object.
insertDependency([objId]);
// The normal fn.
fn = 'paintJpegXObject';
args = [objId, w, h];
return;
}
// Needs to be rendered ourself.
// Figure out if the image has an imageMask.
var imageMask = dict.get('ImageMask', 'IM') || false;
// If there is no imageMask, create the PDFImage and a lot
// of image processing can be done here.
if (!imageMask) {
var imageObj = new PDFImage(xref, resources, image, inline);
if (imageObj.imageMask) {
throw 'Can\'t handle this in the web worker :/';
}
var imgData = {
width: w,
height: h,
data: new Uint8Array(w * h * 4)
};
var pixels = imgData.data;
imageObj.fillRgbaBuffer(pixels, imageObj.decode);
fn = 'paintImageXObject';
args = [imgData];
return;
}
// This depends on a tmpCanvas beeing filled with the
// current fillStyle, such that processing the pixel
// data can't be done here. Instead of creating a
// complete PDFImage, only read the information needed
// for later.
fn = 'paintImageMaskXObject';
var width = dict.get('Width', 'W');
var height = dict.get('Height', 'H');
var bitStrideLength = (width + 7) >> 3;
var imgArray = image.getBytes(bitStrideLength * height);
var decode = dict.get('Decode', 'D');
var inverseDecode = !!decode && decode[0] > 0;
args = [imgArray, inverseDecode, width, height];
}
uniquePrefix = uniquePrefix || '';
if (!queue.argsArray) {
queue.argsArray = [];
}
if (!queue.fnArray) {
queue.fnArray = [];
}
var fnArray = queue.fnArray, argsArray = queue.argsArray;
var dependencyArray = dependency || [];
resources = xref.fetchIfRef(resources) || new Dict();
var xobjs = xref.fetchIfRef(resources.get('XObject')) || new Dict();
var patterns = xref.fetchIfRef(resources.get('Pattern')) || new Dict();
var parser = new Parser(new Lexer(stream), false);
var res = resources;
var args = [], obj;
var getObjBt = function getObjBt() {
parser = this.oldParser;
return { name: 'BT' };
};
var TILING_PATTERN = 1, SHADING_PATTERN = 2;
while (!isEOF(obj = parser.getObj())) {
if (isCmd(obj)) {
var cmd = obj.cmd;
var fn = OP_MAP[cmd];
if (!fn) {
// invalid content command, trying to recover
if (cmd.substr(-2) == 'BT') {
fn = OP_MAP[cmd.substr(0, cmd.length - 2)];
// feeding 'BT' on next interation
parser = {
getObj: getObjBt,
oldParser: parser
};
}
}
assertWellFormed(fn, 'Unknown command "' + cmd + '"');
// TODO figure out how to type-check vararg functions
if ((cmd == 'SCN' || cmd == 'scn') && !args[args.length - 1].code) {
// Use the IR version for setStroke/FillColorN.
fn += '_IR';
// compile tiling patterns
var patternName = args[args.length - 1];
// SCN/scn applies patterns along with normal colors
if (isName(patternName)) {
var pattern = xref.fetchIfRef(patterns.get(patternName.name));
if (pattern) {
var dict = isStream(pattern) ? pattern.dict : pattern;
var typeNum = dict.get('PatternType');
if (typeNum == TILING_PATTERN) {
// Create an IR of the pattern code.
var depIdx = dependencyArray.length;
var queueObj = {};
var codeIR = this.getIRQueue(pattern, dict.get('Resources'),
queueObj, dependencyArray);
// Add the dependencies that are required to execute the
// codeIR.
insertDependency(dependencyArray.slice(depIdx));
args = TilingPattern.getIR(codeIR, dict, args);
}
else if (typeNum == SHADING_PATTERN) {
var shading = xref.fetchIfRef(dict.get('Shading'));
var matrix = dict.get('Matrix');
var pattern = Pattern.parseShading(shading, matrix, xref, res,
null /*ctx*/);
args = pattern.getIR();
} else {
error('Unkown PatternType ' + typeNum);
}
}
}
} else if (cmd == 'Do' && !args[0].code) {
// eagerly compile XForm objects
var name = args[0].name;
var xobj = xobjs.get(name);
if (xobj) {
xobj = xref.fetchIfRef(xobj);
assertWellFormed(isStream(xobj), 'XObject should be a stream');
var type = xobj.dict.get('Subtype');
assertWellFormed(
isName(type),
'XObject should have a Name subtype'
);
if ('Form' == type.name) {
var matrix = xobj.dict.get('Matrix');
var bbox = xobj.dict.get('BBox');
fnArray.push('paintFormXObjectBegin');
argsArray.push([matrix, bbox]);
// This adds the IRQueue of the xObj to the current queue.
var depIdx = dependencyArray.length;
this.getIRQueue(xobj, xobj.dict.get('Resources'), queue,
dependencyArray);
// Add the dependencies that are required to execute the
// codeIR.
insertDependency(dependencyArray.slice(depIdx));
fn = 'paintFormXObjectEnd';
args = [];
} else if ('Image' == type.name) {
buildPaintImageXObject(xobj, false);
} else {
error('Unhandled XObject subtype ' + type.name);
}
}
} else if (cmd == 'Tf') { // eagerly collect all fonts
args[0] = handleSetFont(args[0].name);
} else if (cmd == 'EI') {
buildPaintImageXObject(args[0], true);
}
switch (fn) {
// Parse the ColorSpace data to a raw format.
case 'setFillColorSpace':
case 'setStrokeColorSpace':
args = [ColorSpace.parseToIR(args[0], xref, resources)];
break;
case 'shadingFill':
var shadingRes = xref.fetchIfRef(res.get('Shading'));
if (!shadingRes)
error('No shading resource found');
var shading = xref.fetchIfRef(shadingRes.get(args[0].name));
if (!shading)
error('No shading object found');
var shadingFill = Pattern.parseShading(shading, null, xref, res,
null);
var patternIR = shadingFill.getIR();
args = [patternIR];
fn = 'shadingFill';
break;
case 'setGState':
var dictName = args[0];
var extGState = xref.fetchIfRef(resources.get('ExtGState'));
if (!isDict(extGState) || !extGState.has(dictName.name))
break;
var gsState = xref.fetchIfRef(extGState.get(dictName.name));
// This array holds the converted/processed state data.
var gsStateObj = [];
gsState.forEach(
function canvasGraphicsSetGStateForEach(key, value) {
switch (key) {
case 'Type':
break;
case 'LW':
case 'LC':
case 'LJ':
case 'ML':
case 'D':
case 'RI':
case 'FL':
case 'CA':
case 'ca':
gsStateObj.push([key, value]);
break;
case 'Font':
gsStateObj.push([
'Font',
handleSetFont(null, value[0]),
value[1]
]);
break;
case 'OP':
case 'op':
case 'OPM':
case 'BG':
case 'BG2':
case 'UCR':
case 'UCR2':
case 'TR':
case 'TR2':
case 'HT':
case 'SM':
case 'SA':
case 'BM':
case 'SMask':
case 'AIS':
case 'TK':
TODO('graphic state operator ' + key);
break;
default:
warn('Unknown graphic state operator ' + key);
break;
}
}
);
args = [gsStateObj];
break;
} // switch
fnArray.push(fn);
argsArray.push(args);
args = [];
} else if (obj != null) {
assertWellFormed(args.length <= 33, 'Too many arguments');
args.push(obj);
}
}
return {
fnArray: fnArray,
argsArray: argsArray
};
},
extractEncoding: function partialEvaluatorExtractEncoding(dict,
xref,
properties) {
var type = properties.type, encoding;
if (properties.composite) {
var defaultWidth = xref.fetchIfRef(dict.get('DW')) || 1000;
properties.defaultWidth = defaultWidth;
var glyphsWidths = {};
var widths = xref.fetchIfRef(dict.get('W'));
if (widths) {
var start = 0, end = 0;
for (var i = 0, ii = widths.length; i < ii; i++) {
var code = widths[i];
if (isArray(code)) {
for (var j = 0, jj = code.length; j < jj; j++)
glyphsWidths[start++] = code[j];
start = 0;
} else if (start) {
var width = widths[++i];
for (var j = start; j <= code; j++)
glyphsWidths[j] = width;
start = 0;
} else {
start = code;
}
}
}
properties.widths = glyphsWidths;
// Glyph ids are big-endian 2-byte values
encoding = properties.encoding;
// CIDSystemInfo might help to match width and glyphs
var cidSystemInfo = dict.get('CIDSystemInfo');
if (isDict(cidSystemInfo)) {
properties.cidSystemInfo = {
registry: cidSystemInfo.get('Registry'),
ordering: cidSystemInfo.get('Ordering'),
supplement: cidSystemInfo.get('Supplement')
};
}
var cidToGidMap = dict.get('CIDToGIDMap');
if (!cidToGidMap || !isRef(cidToGidMap)) {
return Object.create(GlyphsUnicode);
}
// Extract the encoding from the CIDToGIDMap
var glyphsStream = xref.fetchIfRef(cidToGidMap);
var glyphsData = glyphsStream.getBytes(0);
// Set encoding 0 to later verify the font has an encoding
encoding[0] = { unicode: 0, width: 0 };
for (var j = 0, jj = glyphsData.length; j < jj; j++) {
var glyphID = (glyphsData[j++] << 8) | glyphsData[j];
if (glyphID == 0)
continue;
var code = j >> 1;
var width = glyphsWidths[code];
encoding[code] = {
unicode: glyphID,
width: isNum(width) ? width : defaultWidth
};
}
return Object.create(GlyphsUnicode);
}
var differences = properties.differences;
var map = properties.encoding;
var baseEncoding = null;
if (dict.has('Encoding')) {
encoding = xref.fetchIfRef(dict.get('Encoding'));
if (isDict(encoding)) {
var baseName = encoding.get('BaseEncoding');
if (baseName)
baseEncoding = Encodings[baseName.name].slice();
// Load the differences between the base and original
if (encoding.has('Differences')) {
var diffEncoding = encoding.get('Differences');
var index = 0;
for (var j = 0, jj = diffEncoding.length; j < jj; j++) {
var data = diffEncoding[j];
if (isNum(data))
index = data;
else
differences[index++] = data.name;
}
}
} else if (isName(encoding)) {
baseEncoding = Encodings[encoding.name].slice();
} else {
error('Encoding is not a Name nor a Dict');
}
}
if (!baseEncoding) {
switch (type) {
case 'TrueType':
baseEncoding = Encodings.WinAnsiEncoding.slice();
break;
case 'Type1':
case 'Type3':
baseEncoding = Encodings.StandardEncoding.slice();
break;
default:
warn('Unknown type of font: ' + type);
baseEncoding = [];
break;
}
}
// merge in the differences
var firstChar = properties.firstChar;
var lastChar = properties.lastChar;
var widths = properties.widths || [];
var glyphs = {};
for (var i = firstChar; i <= lastChar; i++) {
var glyph = differences[i];
var replaceGlyph = true;
if (!glyph) {
glyph = baseEncoding[i] || i;
replaceGlyph = false;
}
var index = GlyphsUnicode[glyph] || i;
var width = widths[i] || widths[glyph];
map[i] = {
unicode: index,
width: isNum(width) ? width : properties.defaultWidth
};
if (replaceGlyph || !glyphs[glyph])
glyphs[glyph] = map[i];
if (replaceGlyph || !glyphs[index])
glyphs[index] = map[i];
// If there is no file, the character mapping can't be modified
// but this is unlikely that there is any standard encoding with
// chars below 0x1f, so that's fine.
if (!properties.file)
continue;
if (index <= 0x1f || (index >= 127 && index <= 255))
map[i].unicode += kCmapGlyphOffset;
}
if (type == 'TrueType' && dict.has('ToUnicode') && differences) {
var cmapObj = dict.get('ToUnicode');
if (isRef(cmapObj)) {
cmapObj = xref.fetch(cmapObj);
}
if (isName(cmapObj)) {
error('ToUnicode file cmap translation not implemented');
} else if (isStream(cmapObj)) {
var tokens = [];
var token = '';
var beginArrayToken = {};
var cmap = cmapObj.getBytes(cmapObj.length);
for (var i = 0, ii = cmap.length; i < ii; i++) {
var byte = cmap[i];
if (byte == 0x20 || byte == 0x0D || byte == 0x0A ||
byte == 0x3C || byte == 0x5B || byte == 0x5D) {
switch (token) {
case 'usecmap':
error('usecmap is not implemented');
break;
case 'beginbfchar':
case 'beginbfrange':
case 'begincidchar':
case 'begincidrange':
token = '';
tokens = [];
break;
case 'endcidrange':
case 'endbfrange':
for (var j = 0, jj = tokens.length; j < jj; j += 3) {
var startRange = tokens[j];
var endRange = tokens[j + 1];
var code = tokens[j + 2];
while (startRange < endRange) {
var mapping = map[startRange] || {};
mapping.unicode = code++;
map[startRange] = mapping;
++startRange;
}
}
break;
case 'endcidchar':
case 'endbfchar':
for (var j = 0, jj = tokens.length; j < jj; j += 2) {
var index = tokens[j];
var code = tokens[j + 1];
var mapping = map[index] || {};
mapping.unicode = code;
map[index] = mapping;
}
break;
case '':
break;
default:
if (token[0] >= '0' && token[0] <= '9')
token = parseInt(token, 10); // a number
tokens.push(token);
token = '';
}
switch (byte) {
case 0x5B:
// begin list parsing
tokens.push(beginArrayToken);
break;
case 0x5D:
// collect array items
var items = [], item;
while (tokens.length &&
(item = tokens.pop()) != beginArrayToken)
items.unshift(item);
tokens.push(items);
break;
}
} else if (byte == 0x3E) {
if (token.length) {
// parsing hex number
tokens.push(parseInt(token, 16));
token = '';
}
} else {
token += String.fromCharCode(byte);
}
}
}
}
return glyphs;
},
getBaseFontMetricsAndMap: function getBaseFontMetricsAndMap(name) {
var map = {};
if (/^Symbol(-?(Bold|Italic))*$/.test(name)) {
// special case for symbols
var encoding = Encodings.symbolsEncoding.slice();
for (var i = 0, n = encoding.length, j; i < n; i++) {
if (!(j = encoding[i]))
continue;
map[i] = GlyphsUnicode[j] || 0;
}
}
var defaultWidth = 0;
var widths = Metrics[stdFontMap[name] || name];
if (isNum(widths)) {
defaultWidth = widths;
widths = null;
}
return {
defaultWidth: defaultWidth,
widths: widths || [],
map: map
};
},
translateFont: function partialEvaluatorTranslateFont(dict, xref, resources,
queue, handler, uniquePrefix, dependency) {
var baseDict = dict;
var type = dict.get('Subtype');
assertWellFormed(isName(type), 'invalid font Subtype');
var composite = false;
if (type.name == 'Type0') {
// If font is a composite
// - get the descendant font
// - set the type according to the descendant font
// - get the FontDescriptor from the descendant font
var df = dict.get('DescendantFonts');
if (!df)
return null;
if (isRef(df))
df = xref.fetch(df);
dict = xref.fetchIfRef(isRef(df) ? df : df[0]);
type = dict.get('Subtype');
assertWellFormed(isName(type), 'invalid font Subtype');
composite = true;
}
var descriptor = xref.fetchIfRef(dict.get('FontDescriptor'));
if (!descriptor) {
if (type.name == 'Type3') {
// FontDescriptor is only required for Type3 fonts when the document
// is a tagged pdf. Create a barbebones one to get by.
descriptor = new Dict();
descriptor.set('FontName', new Name(type.name));
} else {
// Before PDF 1.5 if the font was one of the base 14 fonts, having a
// FontDescriptor was not required.
// This case is here for compatibility.
var baseFontName = dict.get('BaseFont');
if (!isName(baseFontName))
return null;
// Using base font name as a font name.
baseFontName = baseFontName.name.replace(/[,_]/g, '-');
var metricsAndMap = this.getBaseFontMetricsAndMap(baseFontName);
var properties = {
type: type.name,
encoding: metricsAndMap.map,
differences: [],
widths: metricsAndMap.widths,
defaultWidth: metricsAndMap.defaultWidth,
firstChar: 0,
lastChar: 256
};
this.extractEncoding(dict, xref, properties);
return {
name: baseFontName,
dict: baseDict,
properties: properties
};
}
}
// According to the spec if 'FontDescriptor' is declared, 'FirstChar',
// 'LastChar' and 'Widths' should exists too, but some PDF encoders seems
// 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
// a variant.
var firstChar = xref.fetchIfRef(dict.get('FirstChar')) || 0;
var lastChar = xref.fetchIfRef(dict.get('LastChar')) || 256;
var defaultWidth = 0;
var glyphWidths = {};
var encoding = {};
var widths = xref.fetchIfRef(dict.get('Widths'));
if (widths) {
for (var i = 0, j = firstChar, ii = widths.length; i < ii; i++, j++)
glyphWidths[j] = widths[i];
defaultWidth = parseFloat(descriptor.get('MissingWidth')) || 0;
} else {
// Trying get the BaseFont metrics (see comment above).
var baseFontName = dict.get('BaseFont');
if (isName(baseFontName)) {
var metricsAndMap = this.getBaseFontMetricsAndMap(baseFontName.name);
glyphWidths = metricsAndMap.widths;
defaultWidth = metricsAndMap.defaultWidth;
encoding = metricsAndMap.map;
}
}
var fontName = xref.fetchIfRef(descriptor.get('FontName'));
assertWellFormed(isName(fontName), 'invalid font name');
var fontFile = descriptor.get('FontFile', 'FontFile2', 'FontFile3');
if (fontFile) {
fontFile = xref.fetchIfRef(fontFile);
if (fontFile.dict) {
var subtype = fontFile.dict.get('Subtype');
if (subtype)
subtype = subtype.name;
var length1 = fontFile.dict.get('Length1');
if (!isInt(length1))
length1 = xref.fetchIfRef(length1);
var length2 = fontFile.dict.get('Length2');
if (!isInt(length2))
length2 = xref.fetchIfRef(length2);
}
}
var properties = {
type: type.name,
subtype: subtype,
file: fontFile,
length1: length1,
length2: length2,
composite: composite,
fixedPitch: false,
fontMatrix: dict.get('FontMatrix') || IDENTITY_MATRIX,
firstChar: firstChar || 0,
lastChar: lastChar || 256,
bbox: descriptor.get('FontBBox'),
ascent: descriptor.get('Ascent'),
descent: descriptor.get('Descent'),
xHeight: descriptor.get('XHeight'),
capHeight: descriptor.get('CapHeight'),
defaultWidth: defaultWidth,
flags: descriptor.get('Flags'),
italicAngle: descriptor.get('ItalicAngle'),
differences: [],
widths: glyphWidths,
encoding: encoding,
coded: false
};
properties.glyphs = this.extractEncoding(dict, xref, properties);
if (type.name === 'Type3') {
properties.coded = true;
var charProcs = xref.fetchIfRef(dict.get('CharProcs'));
var fontResources = xref.fetchIfRef(dict.get('Resources')) || resources;
properties.resources = fontResources;
for (var key in charProcs.map) {
var glyphStream = xref.fetchIfRef(charProcs.map[key]);
var queueObj = {};
properties.glyphs[key].IRQueue = this.getIRQueue(glyphStream,
fontResources,
queueObj,
dependency);
}
}
return {
name: fontName.name,
dict: baseDict,
file: fontFile,
properties: properties
};
}
};
return constructor;
})();
var EvalState = (function evalState() {
function constructor() {
// Are soft masks and alpha values shapes or opacities?
this.alphaIsShape = false;
this.fontSize = 0;
this.textMatrix = IDENTITY_MATRIX;
this.leading = 0;
// Start of text line (in text coordinates)
this.lineX = 0;
this.lineY = 0;
// Character and word spacing
this.charSpacing = 0;
this.wordSpacing = 0;
this.textHScale = 1;
// Color spaces
this.fillColorSpace = null;
this.strokeColorSpace = null;
}
constructor.prototype = {
};
return constructor;
})();

View File

@ -2,6 +2,7 @@
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict'; 'use strict';
var isWorker = (typeof window == 'undefined'); var isWorker = (typeof window == 'undefined');
/** /**
@ -20,6 +21,271 @@ 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 Encodings = {
get ExpertEncoding() {
return shadow(this, 'ExpertEncoding', ['', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '',
'dollaroldstyle', 'dollarsuperior', 'ampersandsmall', 'Acutesmall',
'parenleftsuperior', 'parenrightsuperior', 'twodotenleader',
'onedotenleader', 'comma', 'hyphen', 'period', 'fraction',
'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle',
'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle',
'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon', 'commasuperior',
'threequartersemdash', 'periodsuperior', 'questionsmall', '',
'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '',
'', 'isuperior', '', '', 'lsuperior', 'msuperior', 'nsuperior',
'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff',
'fi', 'fl', 'ffi', 'ffl', 'parenleftinferior', '', 'parenrightinferior',
'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', 'Bsmall',
'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall',
'Jsmall', 'Ksmall', 'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall',
'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall',
'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah',
'Tildesmall', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '',
'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall',
'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '',
'figuredash', 'hypheninferior', '', '', 'Ogoneksmall', 'Ringsmall',
'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters',
'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths',
'seveneighths', 'onethird', 'twothirds', '', '', 'zerosuperior',
'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior',
'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior',
'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior',
'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior',
'seveninferior', 'eightinferior', 'nineinferior', 'centinferior',
'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall',
'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall',
'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall',
'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall',
'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall',
'Ogravesmall', 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall',
'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall',
'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall',
'Ydieresissmall'
]);
},
get MacExpertEncoding() {
return shadow(this, 'MacExpertEncoding', ['', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall',
'centoldstyle', 'dollaroldstyle', 'dollarsuperior', 'ampersandsmall',
'Acutesmall', 'parenleftsuperior', 'parenrightsuperior',
'twodotenleader', 'onedotenleader', 'comma', 'hyphen', 'period',
'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle',
'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle',
'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', 'semicolon',
'', 'threequartersemdash', '', 'questionsmall', '', '', '', '',
'Ethsmall', '', '', 'onequarter', 'onehalf', 'threequarters',
'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird',
'twothirds', '', '', '', '', '', '', 'ff', 'fi', 'fl', 'ffi', 'ffl',
'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall',
'hypheninferior', 'Gravesmall', 'Asmall', 'Bsmall', 'Csmall', 'Dsmall',
'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall',
'Lsmall', 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall',
'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', 'Xsmall', 'Ysmall',
'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '',
'asuperior', 'centsuperior', '', '', '', '', 'Aacutesmall',
'Agravesmall', 'Acircumflexsmall', 'Adieresissmall', 'Atildesmall',
'Aringsmall', 'Ccedillasmall', 'Eacutesmall', 'Egravesmall',
'Ecircumflexsmall', 'Edieresissmall', 'Iacutesmall', 'Igravesmall',
'Icircumflexsmall', 'Idieresissmall', 'Ntildesmall', 'Oacutesmall',
'Ogravesmall', 'Ocircumflexsmall', 'Odieresissmall', 'Otildesmall',
'Uacutesmall', 'Ugravesmall', 'Ucircumflexsmall', 'Udieresissmall', '',
'eightsuperior', 'fourinferior', 'threeinferior', 'sixinferior',
'eightinferior', 'seveninferior', 'Scaronsmall', '', 'centinferior',
'twoinferior', '', 'Dieresissmall', '', 'Caronsmall', 'osuperior',
'fiveinferior', '', 'commainferior', 'periodinferior', 'Yacutesmall', '',
'dollarinferior', '', 'Thornsmall', '', 'nineinferior', 'zeroinferior',
'Zcaronsmall', 'AEsmall', 'Oslashsmall', 'questiondownsmall',
'oneinferior', 'Lslashsmall', '', '', '', '', '', '', 'Cedillasmall', '',
'', '', '', '', 'OEsmall', 'figuredash', 'hyphensuperior', '', '', '',
'', 'exclamdownsmall', '', 'Ydieresissmall', '', 'onesuperior',
'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior',
'sixsuperior', 'sevensuperior', 'ninesuperior', 'zerosuperior', '',
'esuperior', 'rsuperior', 'tsuperior', '', '', 'isuperior', 'ssuperior',
'dsuperior', '', '', '', '', '', 'lsuperior', 'Ogoneksmall',
'Brevesmall', 'Macronsmall', 'bsuperior', 'nsuperior', 'msuperior',
'commasuperior', 'periodsuperior', 'Dotaccentsmall', 'Ringsmall'
]);
},
get MacRomanEncoding() {
return shadow(this, 'MacRomanEncoding', ['', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign',
'dollar', 'percent', 'ampersand', 'quotesingle', 'parenleft',
'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash',
'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question',
'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore',
'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'braceleft', 'bar', 'braceright', 'asciitilde', '', 'Adieresis', 'Aring',
'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute',
'agrave', 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla',
'eacute', 'egrave', 'ecircumflex', 'edieresis', 'iacute', 'igrave',
'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex',
'odieresis', 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis',
'dagger', 'degree', 'cent', 'sterling', 'section', 'bullet', 'paragraph',
'germandbls', 'registered', 'copyright', 'trademark', 'acute',
'dieresis', 'notequal', 'AE', 'Oslash', 'infinity', 'plusminus',
'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation',
'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega',
'ae', 'oslash', 'questiondown', 'exclamdown', 'logicalnot', 'radical',
'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright',
'ellipsis', 'space', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash',
'emdash', 'quotedblleft', 'quotedblright', 'quoteleft', 'quoteright',
'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', 'currency',
'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl',
'periodcentered', 'quotesinglbase', 'quotedblbase', 'perthousand',
'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute',
'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple',
'Ograve', 'Uacute', 'Ucircumflex', 'Ugrave', 'dotlessi', 'circumflex',
'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla',
'hungarumlaut', 'ogonek', 'caron'
]);
},
get StandardEncoding() {
return shadow(this, 'StandardEncoding', ['', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign',
'dollar', 'percent', 'ampersand', 'quoteright', 'parenleft',
'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash',
'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question',
'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore',
'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',
'braceleft', 'bar', 'braceright', 'asciitilde', '', '', 'exclamdown',
'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency',
'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft',
'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', 'daggerdbl',
'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase',
'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis',
'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex',
'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring',
'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', 'emdash', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '',
'ordfeminine', '', '', '', '', 'Lslash', 'Oslash', 'OE', 'ordmasculine',
'', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', 'lslash',
'oslash', 'oe', 'germandbls'
]);
},
get WinAnsiEncoding() {
return shadow(this, 'WinAnsiEncoding', ['', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign',
'dollar', 'percent', 'ampersand', 'quotesingle', 'parenleft',
'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash',
'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', 'question',
'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore',
'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'braceleft', 'bar', 'braceright', 'asciitilde', 'bullet', 'Euro',
'bullet', 'quotesinglbase', 'florin', 'quotedblbase', 'ellipsis',
'dagger', 'daggerdbl', 'circumflex', 'perthousand', 'Scaron',
'guilsinglleft', 'OE', 'bullet', 'Zcaron', 'bullet', 'bullet',
'quoteleft', 'quoteright', 'quotedblleft', 'quotedblright', 'bullet',
'endash', 'emdash', 'tilde', 'trademark', 'scaron', 'guilsinglright',
'oe', 'bullet', 'zcaron', 'Ydieresis', 'space', 'exclamdown', 'cent',
'sterling', 'currency', 'yen', 'brokenbar', 'section', 'dieresis',
'copyright', 'ordfeminine', 'guillemotleft', 'logicalnot', 'hyphen',
'registered', 'macron', 'degree', 'plusminus', 'twosuperior',
'threesuperior', 'acute', 'mu', 'paragraph', 'periodcentered',
'cedilla', 'onesuperior', 'ordmasculine', 'guillemotright', 'onequarter',
'onehalf', 'threequarters', 'questiondown', 'Agrave', 'Aacute',
'Acircumflex', 'Atilde', 'Adieresis', 'Aring', 'AE', 'Ccedilla',
'Egrave', 'Eacute', 'Ecircumflex', 'Edieresis', 'Igrave', 'Iacute',
'Icircumflex', 'Idieresis', 'Eth', 'Ntilde', 'Ograve', 'Oacute',
'Ocircumflex', 'Otilde', 'Odieresis', 'multiply', 'Oslash', 'Ugrave',
'Uacute', 'Ucircumflex', 'Udieresis', 'Yacute', 'Thorn', 'germandbls',
'agrave', 'aacute', 'acircumflex', 'atilde', 'adieresis', 'aring', 'ae',
'ccedilla', 'egrave', 'eacute', 'ecircumflex', 'edieresis', 'igrave',
'iacute', 'icircumflex', 'idieresis', 'eth', 'ntilde', 'ograve',
'oacute', 'ocircumflex', 'otilde', 'odieresis', 'divide', 'oslash',
'ugrave', 'uacute', 'ucircumflex', 'udieresis', 'yacute', 'thorn',
'ydieresis'
]);
},
get symbolsEncoding() {
return shadow(this, 'symbolsEncoding', ['', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', 'space', 'exclam', 'universal', 'numbersign',
'existential', 'percent', 'ampersand', 'suchthat', 'parenleft',
'parenright', 'asteriskmath', 'plus', 'comma', 'minus', 'period',
'slash', 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven',
'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater',
'question', 'congruent', 'Alpha', 'Beta', 'Chi', 'Delta', 'Epsilon',
'Phi', 'Gamma', 'Eta', 'Iota', 'theta1', 'Kappa', 'Lambda', 'Mu', 'Nu',
'Omicron', 'Pi', 'Theta', 'Rho', 'Sigma', 'Tau', 'Upsilon', 'sigma1',
'Omega', 'Xi', 'Psi', 'Zeta', 'bracketleft', 'therefore', 'bracketright',
'perpendicular', 'underscore', 'radicalex', 'alpha', 'beta', 'chi',
'delta', 'epsilon', 'phi', 'gamma', 'eta', 'iota', 'phi1', 'kappa',
'lambda', 'mu', 'nu', 'omicron', 'pi', 'theta', 'rho', 'sigma', 'tau',
'upsilon', 'omega1', 'omega', 'xi', 'psi', 'zeta', 'braceleft', 'bar',
'braceright', 'similar', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', 'Euro', 'Upsilon1', 'minute', 'lessequal', 'fraction',
'infinity', 'florin', 'club', 'diamond', 'heart', 'spade', 'arrowboth',
'arrowleft', 'arrowup', 'arrowright', 'arrowdown', 'degree', 'plusminus',
'second', 'greaterequal', 'multiply', 'proportional', 'partialdiff',
'bullet', 'divide', 'notequal', 'equivalence', 'approxequal', 'ellipsis',
'arrowvertex', 'arrowhorizex', 'carriagereturn', 'aleph', 'Ifraktur',
'Rfraktur', 'weierstrass', 'circlemultiply', 'circleplus', 'emptyset',
'intersection', 'union', 'propersuperset', 'reflexsuperset', 'notsubset',
'propersubset', 'reflexsubset', 'element', 'notelement', 'angle',
'gradient', 'registerserif', 'copyrightserif', 'trademarkserif',
'product', 'radical', 'dotmath', 'logicalnot', 'logicaland', 'logicalor',
'arrowdblboth', 'arrowdblleft', 'arrowdblup', 'arrowdblright',
'arrowdbldown', 'lozenge', 'angleleft', 'registersans', 'copyrightsans',
'trademarksans', 'summation', 'parenlefttp', 'parenleftex',
'parenleftbt', 'bracketlefttp', 'bracketleftex', 'bracketleftbt',
'bracelefttp', 'braceleftmid', 'braceleftbt', 'braceex', '',
'angleright', 'integral', 'integraltp', 'integralex', 'integralbt',
'parenrighttp', 'parenrightex', 'parenrightbt', 'bracketrighttp',
'bracketrightex', 'bracketrightbt', 'bracerighttp', 'bracerightmid',
'bracerightbt'
]);
},
get zapfDingbatsEncoding() {
return shadow(this, 'zapfDingbatsEncoding', ['', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', 'space', 'a1', 'a2', 'a202', 'a3', 'a4',
'a5', 'a119', 'a118', 'a117', 'a11', 'a12', 'a13', 'a14', 'a15', 'a16',
'a105', 'a17', 'a18', 'a19', 'a20', 'a21', 'a22', 'a23', 'a24', 'a25',
'a26', 'a27', 'a28', 'a6', 'a7', 'a8', 'a9', 'a10', 'a29', 'a30', 'a31',
'a32', 'a33', 'a34', 'a35', 'a36', 'a37', 'a38', 'a39', 'a40', 'a41',
'a42', 'a43', 'a44', 'a45', 'a46', 'a47', 'a48', 'a49', 'a50', 'a51',
'a52', 'a53', 'a54', 'a55', 'a56', 'a57', 'a58', 'a59', 'a60', 'a61',
'a62', 'a63', 'a64', 'a65', 'a66', 'a67', 'a68', 'a69', 'a70', 'a71',
'a72', 'a73', 'a74', 'a203', 'a75', 'a204', 'a76', 'a77', 'a78', 'a79',
'a81', 'a82', 'a83', 'a84', 'a97', 'a98', 'a99', 'a100', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
'', '', '', '', '', '', '', '', '', '', '', '', 'a101', 'a102', 'a103',
'a104', 'a106', 'a107', 'a108', 'a112', 'a111', 'a110', 'a109', 'a120',
'a121', 'a122', 'a123', 'a124', 'a125', 'a126', 'a127', 'a128', 'a129',
'a130', 'a131', 'a132', 'a133', 'a134', 'a135', 'a136', 'a137', 'a138',
'a139', 'a140', 'a141', 'a142', 'a143', 'a144', 'a145', 'a146', 'a147',
'a148', 'a149', 'a150', 'a151', 'a152', 'a153', 'a154', 'a155', 'a156',
'a157', 'a158', 'a159', 'a160', 'a161', 'a163', 'a164', 'a196', 'a165',
'a192', 'a166', 'a167', 'a168', 'a169', 'a170', 'a171', 'a172', 'a173',
'a162', 'a174', 'a175', 'a176', 'a177', 'a178', 'a179', 'a193', 'a180',
'a199', 'a181', 'a200', 'a182', '', 'a201', 'a183', 'a184', 'a197',
'a185', 'a194', 'a198', 'a186', 'a195', 'a187', 'a188', 'a189', 'a190',
'a191'
]);
}
};
/** /**
* Hold a map of decoded fonts and of the standard fourteen Type1 * Hold a map of decoded fonts and of the standard fourteen Type1
* fonts and their acronyms. * fonts and their acronyms.
@ -127,7 +393,7 @@ var FontLoader = {
bind: function fontLoaderBind(fonts, callback) { bind: function fontLoaderBind(fonts, callback) {
function checkFontsLoaded() { function checkFontsLoaded() {
for (var i = 0; i < objs.length; i++) { for (var i = 0, ii = objs.length; i < ii; i++) {
var fontObj = objs[i]; var fontObj = objs[i];
if (fontObj.loading) { if (fontObj.loading) {
return false; return false;
@ -143,10 +409,22 @@ var FontLoader = {
var rules = [], names = [], objs = []; var rules = [], names = [], objs = [];
for (var i = 0; i < fonts.length; i++) { for (var i = 0, ii = fonts.length; i < ii; i++) {
var font = fonts[i]; var font = fonts[i];
// If there is already a fontObj on the font, then it was loaded/attached
// to the page already and we don't have to do anything for this font
// here future.
if (font.fontObj) {
continue;
}
var obj = new Font(font.name, font.file, font.properties); var obj = new Font(font.name, font.file, font.properties);
// Store the fontObj on the font such that `setFont` in CanvasGraphics
// can reuse it later again.
font.fontObj = obj;
objs.push(obj); objs.push(obj);
var str = ''; var str = '';
@ -212,7 +490,7 @@ var FontLoader = {
'width: 10px; height: 10px;' + 'width: 10px; height: 10px;' +
'position: absolute; top: 0px; left: 0px;'); 'position: absolute; top: 0px; left: 0px;');
var html = ''; var html = '';
for (var i = 0; i < names.length; ++i) { for (var i = 0, ii = names.length; i < ii; ++i) {
html += '<span style="font-family:' + names[i] + '">Hi</span>'; html += '<span style="font-family:' + names[i] + '">Hi</span>';
} }
div.innerHTML = html; div.innerHTML = html;
@ -223,7 +501,7 @@ var FontLoader = {
'message', 'message',
function fontLoaderMessage(e) { function fontLoaderMessage(e) {
var fontNames = JSON.parse(e.data); var fontNames = JSON.parse(e.data);
for (var i = 0; i < objs.length; ++i) { for (var i = 0, ii = objs.length; i < ii; ++i) {
var font = objs[i]; var font = objs[i];
font.loading = false; font.loading = false;
} }
@ -239,13 +517,13 @@ var FontLoader = {
// pdfjsFontLoadFailed? // pdfjsFontLoadFailed?
var src = '<!DOCTYPE HTML><html><head>'; var src = '<!DOCTYPE HTML><html><head>';
src += '<style type="text/css">'; src += '<style type="text/css">';
for (var i = 0; i < rules.length; ++i) { for (var i = 0, ii = rules.length; i < ii; ++i) {
src += rules[i]; src += rules[i];
} }
src += '</style>'; src += '</style>';
src += '<script type="application/javascript">'; src += '<script type="application/javascript">';
var fontNamesArray = ''; var fontNamesArray = '';
for (var i = 0; i < names.length; ++i) { for (var i = 0, ii = names.length; i < ii; ++i) {
fontNamesArray += '"' + names[i] + '", '; fontNamesArray += '"' + names[i] + '", ';
} }
src += ' var fontNames=[' + fontNamesArray + '];\n'; src += ' var fontNames=[' + fontNamesArray + '];\n';
@ -253,7 +531,7 @@ var FontLoader = {
src += ' parent.postMessage(JSON.stringify(fontNames), "*");\n'; src += ' parent.postMessage(JSON.stringify(fontNames), "*");\n';
src += ' }'; src += ' }';
src += '</script></head><body>'; src += '</script></head><body>';
for (var i = 0; i < names.length; ++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>';
} }
src += '</body></html>'; src += '</body></html>';
@ -395,7 +673,7 @@ var UnicodeRanges = [
]; ];
function getUnicodeRangeFor(value) { function getUnicodeRangeFor(value) {
for (var i = 0; i < UnicodeRanges.length; i++) { for (var i = 0, ii = UnicodeRanges.length; i < ii; i++) {
var range = UnicodeRanges[i]; var range = UnicodeRanges[i];
if (value >= range.begin && value < range.end) if (value >= range.begin && value < range.end)
return i; return i;
@ -504,7 +782,7 @@ var Font = (function Font() {
function stringToArray(str) { function stringToArray(str) {
var array = []; var array = [];
for (var i = 0; i < str.length; ++i) for (var i = 0, ii = str.length; i < ii; ++i)
array[i] = str.charCodeAt(i); array[i] = str.charCodeAt(i);
return array; return array;
@ -512,7 +790,7 @@ var Font = (function Font() {
function arrayToString(arr) { function arrayToString(arr) {
var str = ''; var str = '';
for (var i = 0; i < arr.length; ++i) for (var i = 0, ii = arr.length; i < ii; ++i)
str += String.fromCharCode(arr[i]); str += String.fromCharCode(arr[i]);
return str; return str;
@ -838,11 +1116,11 @@ var Font = (function Font() {
// Mac want 1-byte per character strings while Windows want // Mac want 1-byte per character strings while Windows want
// 2-bytes per character, so duplicate the names table // 2-bytes per character, so duplicate the names table
var stringsUnicode = []; var stringsUnicode = [];
for (var i = 0; i < strings.length; i++) { for (var i = 0, ii = strings.length; i < ii; i++) {
var str = strings[i]; var str = strings[i];
var strUnicode = ''; var strUnicode = '';
for (var j = 0; j < str.length; j++) for (var j = 0, jj = str.length; j < jj; j++)
strUnicode += string16(str.charCodeAt(j)); strUnicode += string16(str.charCodeAt(j));
stringsUnicode.push(strUnicode); stringsUnicode.push(strUnicode);
} }
@ -860,9 +1138,9 @@ var Font = (function Font() {
// Build the name records field // Build the name records field
var strOffset = 0; var strOffset = 0;
for (var i = 0; i < platforms.length; i++) { for (var i = 0, ii = platforms.length; i < ii; i++) {
var strs = names[i]; var strs = names[i];
for (var j = 0; j < strs.length; j++) { for (var j = 0, jj = strs.length; j < jj; j++) {
var str = strs[j]; var str = strs[j];
var nameRecord = var nameRecord =
platforms[i] + // platform ID platforms[i] + // platform ID
@ -980,7 +1258,7 @@ var Font = (function Font() {
string32(table.offset); string32(table.offset);
} }
for (var i = 0; i < data.length; i++) for (var i = 0, ii = data.length; i < ii; i++)
cmap.data[i] = data.charCodeAt(i); cmap.data[i] = data.charCodeAt(i);
} }
@ -1067,7 +1345,7 @@ var Font = (function Font() {
if (numMissing > 0) { if (numMissing > 0) {
font.pos = (font.start ? font.start : 0) + metrics.offset; font.pos = (font.start ? font.start : 0) + metrics.offset;
var entries = ''; var entries = '';
for (var i = 0; i < hmtx.length; i++) for (var i = 0, ii = hmtx.length; i < ii; i++)
entries += String.fromCharCode(font.getByte()); entries += String.fromCharCode(font.getByte());
for (var i = 0; i < numMissing; i++) for (var i = 0; i < numMissing; i++)
entries += '\x00\x00'; entries += '\x00\x00';
@ -1271,18 +1549,18 @@ var Font = (function Font() {
}); });
// rewrite the tables but tweak offsets // rewrite the tables but tweak offsets
for (var i = 0; i < tables.length; i++) { for (var i = 0, ii = tables.length; i < ii; i++) {
var table = tables[i]; var table = tables[i];
var data = []; var data = [];
var tableData = table.data; var tableData = table.data;
for (var j = 0; j < tableData.length; j++) for (var j = 0, jj = tableData.length; j < jj; j++)
data.push(tableData[j]); data.push(tableData[j]);
createTableEntry(ttf, table.tag, data); createTableEntry(ttf, table.tag, data);
} }
// Add the table datas // Add the table datas
for (var i = 0; i < tables.length; i++) { for (var i = 0, ii = tables.length; i < ii; i++) {
var table = tables[i]; var table = tables[i];
var tableData = table.data; var tableData = table.data;
ttf.file += arrayToString(tableData); ttf.file += arrayToString(tableData);
@ -1297,7 +1575,7 @@ var Font = (function Font() {
convert: function font_convert(fontName, font, properties) { convert: function font_convert(fontName, font, properties) {
function isFixedPitch(glyphs) { function isFixedPitch(glyphs) {
for (var i = 0; i < glyphs.length - 1; i++) { for (var i = 0, ii = glyphs.length - 1; i < ii; i++) {
if (glyphs[i] != glyphs[i + 1]) if (glyphs[i] != glyphs[i + 1])
return false; return false;
} }
@ -1379,7 +1657,7 @@ var Font = (function Font() {
// Horizontal metrics // Horizontal metrics
'hmtx': (function fontFieldsHmtx() { 'hmtx': (function fontFieldsHmtx() {
var hmtx = '\x00\x00\x00\x00'; // Fake .notdef var hmtx = '\x00\x00\x00\x00'; // Fake .notdef
for (var i = 0; i < charstrings.length; i++) { for (var i = 0, ii = charstrings.length; i < ii; i++) {
hmtx += string16(charstrings[i].width) + string16(0); hmtx += string16(charstrings[i].width) + string16(0);
} }
return stringToArray(hmtx); return stringToArray(hmtx);
@ -1451,8 +1729,8 @@ var Font = (function Font() {
} }
encoding[0] = { unicode: 0, width: 0 }; encoding[0] = { unicode: 0, width: 0 };
var glyph = 1, i, j, k; var glyph = 1, i, j, k, cidLength, ii;
for (i = 0; i < cidToUnicode.length; ++i) { for (i = 0, ii = cidToUnicode.length; i < ii; ++i) {
var unicode = cidToUnicode[i]; var unicode = cidToUnicode[i];
var width; var width;
if (isArray(unicode)) { if (isArray(unicode)) {
@ -1509,12 +1787,11 @@ var Font = (function Font() {
var url = ('url(data:' + this.mimetype + ';base64,' + var url = ('url(data:' + this.mimetype + ';base64,' +
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 styleSheet = document.styleSheets[0];
if (!styleSheet) { document.documentElement.firstChild.appendChild(
document.documentElement.firstChild.appendChild( document.createElement('style'));
document.createElement('style'));
styleSheet = document.styleSheets[0]; var styleSheet = document.styleSheets[document.styleSheets.length - 1];
}
styleSheet.insertRule(rule, styleSheet.cssRules.length); styleSheet.insertRule(rule, styleSheet.cssRules.length);
return rule; return rule;
@ -1566,7 +1843,7 @@ var Font = (function Font() {
} }
} }
else { else {
for (var i = 0; i < chars.length; ++i) { for (var i = 0, ii = chars.length; i < ii; ++i) {
var charcode = chars.charCodeAt(i); var charcode = chars.charCodeAt(i);
var glyph = encoding[charcode]; var glyph = encoding[charcode];
if ('undefined' == typeof(glyph)) { if ('undefined' == typeof(glyph)) {
@ -1864,7 +2141,7 @@ var Type1Parser = function type1Parser() {
count++; count++;
var array = str.substr(start, count).split(' '); var array = str.substr(start, count).split(' ');
for (var i = 0; i < array.length; 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;
} }
@ -1889,7 +2166,7 @@ var Type1Parser = function type1Parser() {
this.extractFontProgram = function t1_extractFontProgram(stream) { this.extractFontProgram = function t1_extractFontProgram(stream) {
var eexec = decrypt(stream, kEexecEncryptionKey, 4); var eexec = decrypt(stream, kEexecEncryptionKey, 4);
var eexecStr = ''; var eexecStr = '';
for (var i = 0; i < eexec.length; i++) for (var i = 0, ii = eexec.length; i < ii; i++)
eexecStr += String.fromCharCode(eexec[i]); eexecStr += String.fromCharCode(eexec[i]);
var glyphsSection = false, subrsSection = false; var glyphsSection = false, subrsSection = false;
@ -2013,7 +2290,7 @@ var Type1Parser = function type1Parser() {
this.extractFontHeader = function t1_extractFontHeader(stream, properties) { this.extractFontHeader = function t1_extractFontHeader(stream, properties) {
var headerString = ''; var headerString = '';
for (var i = 0; i < stream.length; i++) for (var i = 0, ii = stream.length; i < ii; i++)
headerString += String.fromCharCode(stream[i]); headerString += String.fromCharCode(stream[i]);
var token = ''; var token = '';
@ -2040,7 +2317,7 @@ var Type1Parser = function type1Parser() {
var matrix = readNumberArray(headerString, i + 1); var matrix = readNumberArray(headerString, i + 1);
// The FontMatrix is in unitPerEm, so make it pixels // The FontMatrix is in unitPerEm, so make it pixels
for (var j = 0; j < matrix.length; j++) for (var j = 0, jj = matrix.length; j < jj; j++)
matrix[j] *= 1000; matrix[j] *= 1000;
// Make the angle into the right direction // Make the angle into the right direction
@ -2201,7 +2478,7 @@ CFF.prototype = {
} }
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
for (var j = 0; j < objects[i].length; j++) for (var j = 0, jj = objects[i].length; j < jj; j++)
data += isByte ? String.fromCharCode(objects[i][j] & 0xFF) : data += isByte ? String.fromCharCode(objects[i][j] & 0xFF) :
objects[i][j]; objects[i][j];
} }
@ -2229,7 +2506,7 @@ CFF.prototype = {
var charstrings = []; var charstrings = [];
var missings = []; var missings = [];
for (var i = 0; i < glyphs.length; i++) { for (var i = 0, ii = glyphs.length; i < ii; i++) {
var glyph = glyphs[i]; var glyph = glyphs[i];
var mapping = properties.glyphs[glyph.glyph]; var mapping = properties.glyphs[glyph.glyph];
if (!mapping) { if (!mapping) {
@ -2319,6 +2596,7 @@ CFF.prototype = {
}, },
flattenCharstring: function flattenCharstring(charstring, map) { flattenCharstring: function flattenCharstring(charstring, map) {
// charstring changes size - can't cache .length in loop
for (var i = 0; i < charstring.length; i++) { for (var i = 0; i < charstring.length; i++) {
var command = charstring[i]; var command = charstring[i];
if (command.charAt) { if (command.charAt) {
@ -2363,7 +2641,7 @@ CFF.prototype = {
'\x1c\x00\x00\x10'; // Encoding '\x1c\x00\x00\x10'; // Encoding
var boundingBox = properties.bbox; var boundingBox = properties.bbox;
for (var i = 0; i < boundingBox.length; i++) for (var i = 0, ii = boundingBox.length; i < ii; i++)
dict += self.encodeNumber(boundingBox[i]); dict += self.encodeNumber(boundingBox[i]);
dict += '\x05'; // FontBBox; dict += '\x05'; // FontBBox;
@ -2453,7 +2731,7 @@ CFF.prototype = {
if (isArray(value)) { if (isArray(value)) {
data += self.encodeNumber(value[0]); data += self.encodeNumber(value[0]);
for (var i = 1; i < value.length; i++) for (var i = 1, ii = value.length; i < ii; i++)
data += self.encodeNumber(value[i] - value[i - 1]); data += self.encodeNumber(value[i] - value[i - 1]);
} else { } else {
data += self.encodeNumber(value); data += self.encodeNumber(value);
@ -2474,7 +2752,7 @@ CFF.prototype = {
var cff = []; var cff = [];
for (var index in fields) { for (var index in fields) {
var field = fields[index]; var field = fields[index];
for (var i = 0; i < field.length; i++) for (var i = 0, ii = field.length; i < ii; i++)
cff.push(field.charCodeAt(i)); cff.push(field.charCodeAt(i));
} }
@ -2571,7 +2849,7 @@ var Type2CFF = (function type2CFF() {
// create the mapping between charstring and glyph id // create the mapping between charstring and glyph id
var glyphIds = []; var glyphIds = [];
for (var i = 0; i < charstrings.length; i++) for (var i = 0, ii = charstrings.length; i < ii; i++)
glyphIds.push(charstrings[i].gid); glyphIds.push(charstrings[i].gid);
this.charstrings = charstrings; this.charstrings = charstrings;
@ -2589,7 +2867,7 @@ var Type2CFF = (function type2CFF() {
var charstrings = []; var charstrings = [];
var firstChar = properties.firstChar; var firstChar = properties.firstChar;
var glyphMap = {}; var glyphMap = {};
for (var i = 0; i < charsets.length; i++) { for (var i = 0, ii = charsets.length; i < ii; i++) {
var glyph = charsets[i]; var glyph = charsets[i];
for (var charcode in encoding) { for (var charcode in encoding) {
if (encoding[charcode] == i) if (encoding[charcode] == i)
@ -2598,7 +2876,7 @@ var Type2CFF = (function type2CFF() {
} }
var differences = properties.differences; var differences = properties.differences;
for (var i = 0; i < differences.length; ++i) { for (var i = 0, ii = differences.length; i < ii; ++i) {
var glyph = differences[i]; var glyph = differences[i];
if (!glyph) if (!glyph)
continue; continue;
@ -2609,7 +2887,7 @@ var Type2CFF = (function type2CFF() {
} }
var glyphs = properties.glyphs; var glyphs = properties.glyphs;
for (var i = 1; i < charsets.length; i++) { for (var i = 1, ii = charsets.length; i < ii; i++) {
var glyph = charsets[i]; var glyph = charsets[i];
var code = glyphMap[glyph] || 0; var code = glyphMap[glyph] || 0;
@ -2619,7 +2897,8 @@ var Type2CFF = (function type2CFF() {
if (unicode <= 0x1f || (unicode >= 127 && unicode <= 255)) if (unicode <= 0x1f || (unicode >= 127 && unicode <= 255))
unicode += kCmapGlyphOffset; unicode += kCmapGlyphOffset;
var width = isNum(mapping.width) ? mapping.width : defaultWidth; var width = (mapping.hasOwnProperty('width') && isNum(mapping.width)) ?
mapping.width : defaultWidth;
properties.encoding[code] = { properties.encoding[code] = {
unicode: unicode, unicode: unicode,
width: width width: width
@ -2642,7 +2921,7 @@ var Type2CFF = (function type2CFF() {
// properties.glyphs[code] || properties.glyphs[glyph] // properties.glyphs[code] || properties.glyphs[glyph]
var nextUnusedUnicode = kCmapGlyphOffset + 0x0020; var nextUnusedUnicode = kCmapGlyphOffset + 0x0020;
var lastUnicode = charstrings[0].unicode, wasModified = false; var lastUnicode = charstrings[0].unicode, wasModified = false;
for (var i = 1; i < charstrings.length; ++i) { for (var i = 1, ii = charstrings.length; i < ii; ++i) {
if (lastUnicode != charstrings[i].unicode) { if (lastUnicode != charstrings[i].unicode) {
lastUnicode = charstrings[i].unicode; lastUnicode = charstrings[i].unicode;
continue; continue;
@ -2687,7 +2966,7 @@ var Type2CFF = (function type2CFF() {
var gid = 1; var gid = 1;
var baseEncoding = pos ? Encodings.ExpertEncoding.slice() : var baseEncoding = pos ? Encodings.ExpertEncoding.slice() :
Encodings.StandardEncoding.slice(); Encodings.StandardEncoding.slice();
for (var i = 0; i < charset.length; i++) { for (var i = 0, ii = charset.length; i < ii; i++) {
var index = baseEncoding.indexOf(charset[i]); var index = baseEncoding.indexOf(charset[i]);
if (index != -1) if (index != -1)
encoding[index] = gid++; encoding[index] = gid++;
@ -2838,16 +3117,16 @@ var Type2CFF = (function type2CFF() {
getStrings: function cff_getStrings(stringIndex) { getStrings: function cff_getStrings(stringIndex) {
function bytesToString(bytesArray) { function bytesToString(bytesArray) {
var str = ''; var str = '';
for (var i = 0, length = bytesArray.length; i < length; i++) for (var i = 0, ii = bytesArray.length; i < ii; i++)
str += String.fromCharCode(bytesArray[i]); str += String.fromCharCode(bytesArray[i]);
return str; return str;
} }
var stringArray = []; var stringArray = [];
for (var i = 0, length = CFFStrings.length; i < length; i++) for (var i = 0, ii = CFFStrings.length; i < ii; i++)
stringArray.push(CFFStrings[i]); stringArray.push(CFFStrings[i]);
for (var i = 0, length = stringIndex.length; i < length; i++) for (var i = 0, ii = stringIndex.length; i < ii; i++)
stringArray.push(bytesToString(stringIndex.get(i).data)); stringArray.push(bytesToString(stringIndex.get(i).data));
return stringArray; return stringArray;
@ -2993,3 +3272,4 @@ var Type2CFF = (function type2CFF() {
return constructor; return constructor;
})(); })();

308
src/function.js Normal file
View File

@ -0,0 +1,308 @@
/* -*- 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 PDFFunction = (function pdfFunction() {
var CONSTRUCT_SAMPLED = 0;
var CONSTRUCT_INTERPOLATED = 2;
var CONSTRUCT_STICHED = 3;
var CONSTRUCT_POSTSCRIPT = 4;
return {
getSampleArray: function pdfFunctionGetSampleArray(size, outputSize, bps,
str) {
var length = 1;
for (var i = 0, ii = size.length; i < ii; i++)
length *= size[i];
length *= outputSize;
var array = [];
var codeSize = 0;
var codeBuf = 0;
var strBytes = str.getBytes((length * bps + 7) / 8);
var strIdx = 0;
for (var i = 0; i < length; i++) {
while (codeSize < bps) {
codeBuf <<= 8;
codeBuf |= strBytes[strIdx++];
codeSize += 8;
}
codeSize -= bps;
array.push(codeBuf >> codeSize);
codeBuf &= (1 << codeSize) - 1;
}
return array;
},
getIR: function pdfFunctionGetIR(xref, fn) {
var dict = fn.dict;
if (!dict)
dict = fn;
var types = [this.constructSampled,
null,
this.constructInterpolated,
this.constructStiched,
this.constructPostScript];
var typeNum = dict.get('FunctionType');
var typeFn = types[typeNum];
if (!typeFn)
error('Unknown type of function');
return typeFn.call(this, fn, dict, xref);
},
fromIR: function pdfFunctionFromIR(IR) {
var type = IR[0];
switch (type) {
case CONSTRUCT_SAMPLED:
return this.constructSampledFromIR(IR);
case CONSTRUCT_INTERPOLATED:
return this.constructInterpolatedFromIR(IR);
case CONSTRUCT_STICHED:
return this.constructStichedFromIR(IR);
case CONSTRUCT_POSTSCRIPT:
default:
return this.constructPostScriptFromIR(IR);
}
},
parse: function pdfFunctionParse(xref, fn) {
var IR = this.getIR(xref, fn);
return this.fromIR(IR);
},
constructSampled: function pdfFunctionConstructSampled(str, dict) {
var domain = dict.get('Domain');
var range = dict.get('Range');
if (!domain || !range)
error('No domain or range');
var inputSize = domain.length / 2;
var outputSize = range.length / 2;
if (inputSize != 1)
error('No support for multi-variable inputs to functions: ' +
inputSize);
var size = dict.get('Size');
var bps = dict.get('BitsPerSample');
var order = dict.get('Order');
if (!order)
order = 1;
if (order !== 1)
error('No support for cubic spline interpolation: ' + order);
var encode = dict.get('Encode');
if (!encode) {
encode = [];
for (var i = 0; i < inputSize; ++i) {
encode.push(0);
encode.push(size[i] - 1);
}
}
var decode = dict.get('Decode');
if (!decode)
decode = range;
var samples = this.getSampleArray(size, outputSize, bps, str);
return [
CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size,
outputSize, bps, range
];
},
constructSampledFromIR: function pdfFunctionConstructSampledFromIR(IR) {
var inputSize = IR[1];
var domain = IR[2];
var encode = IR[3];
var decode = IR[4];
var samples = IR[5];
var size = IR[6];
var outputSize = IR[7];
var bps = IR[8];
var range = IR[9];
return function constructSampledFromIRResult(args) {
var clip = function constructSampledFromIRClip(v, min, max) {
if (v > max)
v = max;
else if (v < min)
v = min;
return v;
};
if (inputSize != args.length)
error('Incorrect number of arguments: ' + inputSize + ' != ' +
args.length);
for (var i = 0; i < inputSize; i++) {
var i2 = i * 2;
// clip to the domain
var v = clip(args[i], domain[i2], domain[i2 + 1]);
// encode
v = encode[i2] + ((v - domain[i2]) *
(encode[i2 + 1] - encode[i2]) /
(domain[i2 + 1] - domain[i2]));
// clip to the size
args[i] = clip(v, 0, size[i] - 1);
}
// interpolate to table
TODO('Multi-dimensional interpolation');
var floor = Math.floor(args[0]);
var ceil = Math.ceil(args[0]);
var scale = args[0] - floor;
floor *= outputSize;
ceil *= outputSize;
var output = [], v = 0;
for (var i = 0; i < outputSize; ++i) {
if (ceil == floor) {
v = samples[ceil + i];
} else {
var low = samples[floor + i];
var high = samples[ceil + i];
v = low * scale + high * (1 - scale);
}
var i2 = i * 2;
// decode
v = decode[i2] + (v * (decode[i2 + 1] - decode[i2]) /
((1 << bps) - 1));
// clip to the domain
output.push(clip(v, range[i2], range[i2 + 1]));
}
return output;
}
},
constructInterpolated:
function pdfFunctionConstructInterpolated(str, dict) {
var c0 = dict.get('C0') || [0];
var c1 = dict.get('C1') || [1];
var n = dict.get('N');
if (!isArray(c0) || !isArray(c1))
error('Illegal dictionary for interpolated function');
var length = c0.length;
var diff = [];
for (var i = 0; i < length; ++i)
diff.push(c1[i] - c0[i]);
return [CONSTRUCT_INTERPOLATED, c0, diff, n];
},
constructInterpolatedFromIR:
function pdfFunctionconstructInterpolatedFromIR(IR) {
var c0 = IR[1];
var diff = IR[2];
var n = IR[3];
var length = diff.length;
return function constructInterpolatedFromIRResult(args) {
var x = n == 1 ? args[0] : Math.pow(args[0], n);
var out = [];
for (var j = 0; j < length; ++j)
out.push(c0[j] + (x * diff[j]));
return out;
}
},
constructStiched: function pdfFunctionConstructStiched(fn, dict, xref) {
var domain = dict.get('Domain');
var range = dict.get('Range');
if (!domain)
error('No domain');
var inputSize = domain.length / 2;
if (inputSize != 1)
error('Bad domain for stiched function');
var fnRefs = dict.get('Functions');
var fns = [];
for (var i = 0, ii = fnRefs.length; i < ii; ++i)
fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i])));
var bounds = dict.get('Bounds');
var encode = dict.get('Encode');
return [CONSTRUCT_STICHED, domain, bounds, encode, fns];
},
constructStichedFromIR: function pdfFunctionConstructStichedFromIR(IR) {
var domain = IR[1];
var bounds = IR[2];
var encode = IR[3];
var fnsIR = IR[4];
var fns = [];
for (var i = 0, ii = fnsIR.length; i < ii; i++) {
fns.push(PDFFunction.fromIR(fnsIR[i]));
}
return function constructStichedFromIRResult(args) {
var clip = function constructStichedFromIRClip(v, min, max) {
if (v > max)
v = max;
else if (v < min)
v = min;
return v;
};
// clip to domain
var v = clip(args[0], domain[0], domain[1]);
// calulate which bound the value is in
for (var i = 0, ii = bounds.length; i < ii; ++i) {
if (v < bounds[i])
break;
}
// encode value into domain of function
var dmin = domain[0];
if (i > 0)
dmin = bounds[i - 1];
var dmax = domain[1];
if (i < bounds.length)
dmax = bounds[i];
var rmin = encode[2 * i];
var rmax = encode[2 * i + 1];
var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin);
// call the appropropriate function
return fns[i]([v2]);
};
},
constructPostScript: function pdfFunctionConstructPostScript() {
return [CONSTRUCT_POSTSCRIPT];
},
constructPostScriptFromIR: function pdfFunctionConstructPostScriptFromIR() {
TODO('unhandled type of function');
return function constructPostScriptFromIRResult() {
return [255, 105, 180];
};
}
};
})();

257
src/image.js Normal file
View File

@ -0,0 +1,257 @@
/* -*- 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 PDFImage = (function pdfImage() {
function constructor(xref, res, image, inline) {
this.image = image;
if (image.getParams) {
// JPX/JPEG2000 streams directly contain bits per component
// and color space mode information.
TODO('get params from actual stream');
// var bits = ...
// var colorspace = ...
}
// TODO cache rendered images?
var dict = image.dict;
this.width = dict.get('Width', 'W');
this.height = dict.get('Height', 'H');
if (this.width < 1 || this.height < 1)
error('Invalid image width: ' + this.width + ' or height: ' +
this.height);
this.interpolate = dict.get('Interpolate', 'I') || false;
this.imageMask = dict.get('ImageMask', 'IM') || false;
var bitsPerComponent = image.bitsPerComponent;
if (!bitsPerComponent) {
bitsPerComponent = dict.get('BitsPerComponent', 'BPC');
if (!bitsPerComponent) {
if (this.imageMask)
bitsPerComponent = 1;
else
error('Bits per component missing in image: ' + this.imageMask);
}
}
this.bpc = bitsPerComponent;
if (!this.imageMask) {
var colorSpace = dict.get('ColorSpace', 'CS');
if (!colorSpace) {
TODO('JPX images (which don"t require color spaces');
colorSpace = new Name('DeviceRGB');
}
this.colorSpace = ColorSpace.parse(colorSpace, xref, res);
this.numComps = this.colorSpace.numComps;
}
this.decode = dict.get('Decode', 'D');
var mask = xref.fetchIfRef(dict.get('Mask'));
var smask = xref.fetchIfRef(dict.get('SMask'));
if (mask) {
TODO('masked images');
} else if (smask) {
this.smask = new PDFImage(xref, res, smask);
}
}
constructor.prototype = {
getComponents: function getComponents(buffer, decodeMap) {
var bpc = this.bpc;
if (bpc == 8)
return buffer;
var width = this.width;
var height = this.height;
var numComps = this.numComps;
var length = width * height;
var bufferPos = 0;
var output = bpc <= 8 ? new Uint8Array(length) :
bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length);
var rowComps = width * numComps;
if (bpc == 1) {
var valueZero = 0, valueOne = 1;
if (decodeMap) {
valueZero = decodeMap[0] ? 1 : 0;
valueOne = decodeMap[1] ? 1 : 0;
}
var mask = 0;
var buf = 0;
for (var i = 0, ii = length; i < ii; ++i) {
if (i % rowComps == 0) {
mask = 0;
buf = 0;
} else {
mask >>= 1;
}
if (mask <= 0) {
buf = buffer[bufferPos++];
mask = 128;
}
output[i] = !(buf & mask) ? valueZero : valueOne;
}
} else {
if (decodeMap != null)
TODO('interpolate component values');
var bits = 0, buf = 0;
for (var i = 0, ii = length; i < ii; ++i) {
if (i % rowComps == 0) {
buf = 0;
bits = 0;
}
while (bits < bpc) {
buf = (buf << 8) | buffer[bufferPos++];
bits += 8;
}
var remainingBits = bits - bpc;
output[i] = buf >> remainingBits;
buf = buf & ((1 << remainingBits) - 1);
bits = remainingBits;
}
}
return output;
},
getOpacity: function getOpacity() {
var smask = this.smask;
var width = this.width;
var height = this.height;
var buf = new Uint8Array(width * height);
if (smask) {
if (smask.image.getImage) {
// smask is a DOM image
var tempCanvas = new ScratchCanvas(width, height);
var tempCtx = tempCanvas.getContext('2d');
var domImage = smask.image.getImage();
tempCtx.drawImage(domImage, 0, 0, domImage.width, domImage.height,
0, 0, width, height);
var data = tempCtx.getImageData(0, 0, width, height).data;
for (var i = 0, j = 0, ii = width * height; i < ii; ++i, j += 4)
buf[i] = data[j]; // getting first component value
return buf;
}
var sw = smask.width;
var sh = smask.height;
if (sw != this.width || sh != this.height)
error('smask dimensions do not match image dimensions: ' + sw +
' != ' + this.width + ', ' + sh + ' != ' + this.height);
smask.fillGrayBuffer(buf);
return buf;
} else {
for (var i = 0, ii = width * height; i < ii; ++i)
buf[i] = 255;
}
return buf;
},
applyStencilMask: function applyStencilMask(buffer, inverseDecode) {
var width = this.width, height = this.height;
var bitStrideLength = (width + 7) >> 3;
this.image.reset();
var imgArray = this.image.getBytes(bitStrideLength * height);
var imgArrayPos = 0;
var i, j, mask, buf;
// removing making non-masked pixels transparent
var bufferPos = 3; // alpha component offset
for (i = 0; i < height; i++) {
mask = 0;
for (j = 0; j < width; j++) {
if (!mask) {
buf = imgArray[imgArrayPos++];
mask = 128;
}
if (!(buf & mask) == inverseDecode) {
buffer[bufferPos] = 0;
}
bufferPos += 4;
mask >>= 1;
}
}
},
fillRgbaBuffer: function fillRgbaBuffer(buffer, decodeMap) {
var numComps = this.numComps;
var width = this.width;
var height = this.height;
var bpc = this.bpc;
// rows start at byte boundary;
var rowBytes = (width * numComps * bpc + 7) >> 3;
this.image.reset();
var imgArray = this.image.getBytes(height * rowBytes);
var comps = this.colorSpace.getRgbBuffer(
this.getComponents(imgArray, decodeMap), bpc);
var compsPos = 0;
var opacity = this.getOpacity();
var opacityPos = 0;
var length = width * height * 4;
for (var i = 0; i < length; i += 4) {
buffer[i] = comps[compsPos++];
buffer[i + 1] = comps[compsPos++];
buffer[i + 2] = comps[compsPos++];
buffer[i + 3] = opacity[opacityPos++];
}
},
fillGrayBuffer: function fillGrayBuffer(buffer) {
var numComps = this.numComps;
if (numComps != 1)
error('Reading gray scale from a color image: ' + numComps);
var width = this.width;
var height = this.height;
var bpc = this.bpc;
// rows start at byte boundary;
var rowBytes = (width * numComps * bpc + 7) >> 3;
this.image.reset();
var imgArray = this.image.getBytes(height * rowBytes);
var comps = this.getComponents(imgArray);
var length = width * height;
for (var i = 0; i < length; ++i)
buffer[i] = comps[i];
}
};
return constructor;
})();
var JpegImage = (function jpegImage() {
function JpegImage(objId, imageData, objs) {
var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
var img = new Image();
img.onload = (function jpegImageOnload() {
this.loaded = true;
objs.resolve(objId, this);
if (this.onLoad)
this.onLoad();
}).bind(this);
img.src = src;
this.domImage = img;
}
JpegImage.prototype = {
getImage: function jpegImageGetImage() {
return this.domImage;
}
};
return JpegImage;
})();

View File

@ -2941,3 +2941,4 @@ var Metrics = {
'a191': 918 'a191': 918
} }
}; };

742
src/obj.js Normal file
View File

@ -0,0 +1,742 @@
/* -*- 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 Name = (function nameName() {
function constructor(name) {
this.name = name;
}
constructor.prototype = {
};
return constructor;
})();
var Cmd = (function cmdCmd() {
function constructor(cmd) {
this.cmd = cmd;
}
constructor.prototype = {
};
return constructor;
})();
var Dict = (function dictDict() {
function constructor() {
this.map = Object.create(null);
}
constructor.prototype = {
get: function dictGet(key1, key2, key3) {
var value;
if (typeof (value = this.map[key1]) != 'undefined' || key1 in this.map ||
typeof key2 == 'undefined') {
return value;
}
if (typeof (value = this.map[key2]) != 'undefined' || key2 in this.map ||
typeof key3 == 'undefined') {
return value;
}
return this.map[key3] || null;
},
set: function dictSet(key, value) {
this.map[key] = value;
},
has: function dictHas(key) {
return key in this.map;
},
forEach: function dictForEach(callback) {
for (var key in this.map) {
callback(key, this.map[key]);
}
}
};
return constructor;
})();
var Ref = (function refRef() {
function constructor(num, gen) {
this.num = num;
this.gen = gen;
}
constructor.prototype = {
};
return constructor;
})();
// The reference is identified by number and generation,
// this structure stores only one instance of the reference.
var RefSet = (function refSet() {
function constructor() {
this.dict = {};
}
constructor.prototype = {
has: function refSetHas(ref) {
return !!this.dict['R' + ref.num + '.' + ref.gen];
},
put: function refSetPut(ref) {
this.dict['R' + ref.num + '.' + ref.gen] = ref;
}
};
return constructor;
})();
var Catalog = (function catalogCatalog() {
function constructor(xref) {
this.xref = xref;
var obj = xref.getCatalogObj();
assertWellFormed(isDict(obj), 'catalog object is not a dictionary');
this.catDict = obj;
}
constructor.prototype = {
get toplevelPagesDict() {
var pagesObj = this.catDict.get('Pages');
assertWellFormed(isRef(pagesObj), 'invalid top-level pages reference');
var xrefObj = this.xref.fetch(pagesObj);
assertWellFormed(isDict(xrefObj), 'invalid top-level pages dictionary');
// shadow the prototype getter
return shadow(this, 'toplevelPagesDict', xrefObj);
},
get documentOutline() {
var obj = this.catDict.get('Outlines');
var xref = this.xref;
var root = { items: [] };
if (isRef(obj)) {
obj = xref.fetch(obj).get('First');
var processed = new RefSet();
if (isRef(obj)) {
var queue = [{obj: obj, parent: root}];
// to avoid recursion keeping track of the items
// in the processed dictionary
processed.put(obj);
while (queue.length > 0) {
var i = queue.shift();
var outlineDict = xref.fetch(i.obj);
if (!outlineDict.has('Title'))
error('Invalid outline item');
var dest = outlineDict.get('A');
if (dest)
dest = xref.fetchIfRef(dest).get('D');
else if (outlineDict.has('Dest')) {
dest = outlineDict.get('Dest');
if (isName(dest))
dest = dest.name;
}
var title = xref.fetchIfRef(outlineDict.get('Title'));
var outlineItem = {
dest: dest,
title: stringToPDFString(title),
color: outlineDict.get('C') || [0, 0, 0],
count: outlineDict.get('Count'),
bold: !!(outlineDict.get('F') & 2),
italic: !!(outlineDict.get('F') & 1),
items: []
};
i.parent.items.push(outlineItem);
obj = outlineDict.get('First');
if (isRef(obj) && !processed.has(obj)) {
queue.push({obj: obj, parent: outlineItem});
processed.put(obj);
}
obj = outlineDict.get('Next');
if (isRef(obj) && !processed.has(obj)) {
queue.push({obj: obj, parent: i.parent});
processed.put(obj);
}
}
}
}
obj = root.items.length > 0 ? root.items : null;
return shadow(this, 'documentOutline', obj);
},
get numPages() {
var obj = this.toplevelPagesDict.get('Count');
assertWellFormed(
isInt(obj),
'page count in top level pages object is not an integer'
);
// shadow the prototype getter
return shadow(this, 'num', obj);
},
traverseKids: function catalogTraverseKids(pagesDict) {
var pageCache = this.pageCache;
var kids = pagesDict.get('Kids');
assertWellFormed(isArray(kids),
'page dictionary kids object is not an array');
for (var i = 0, ii = kids.length; i < ii; ++i) {
var kid = kids[i];
assertWellFormed(isRef(kid),
'page dictionary kid is not a reference');
var obj = this.xref.fetch(kid);
if (isDict(obj, 'Page') || (isDict(obj) && !obj.has('Kids'))) {
pageCache.push(new Page(this.xref, pageCache.length, obj, kid));
} else { // must be a child page dictionary
assertWellFormed(
isDict(obj),
'page dictionary kid reference points to wrong type of object'
);
this.traverseKids(obj);
}
}
},
get destinations() {
function fetchDestination(xref, ref) {
var dest = xref.fetchIfRef(ref);
return isDict(dest) ? dest.get('D') : dest;
}
var xref = this.xref;
var dests = {}, nameTreeRef, nameDictionaryRef;
var obj = this.catDict.get('Names');
if (obj)
nameTreeRef = xref.fetchIfRef(obj).get('Dests');
else if (this.catDict.has('Dests'))
nameDictionaryRef = this.catDict.get('Dests');
if (nameDictionaryRef) {
// reading simple destination dictionary
obj = xref.fetchIfRef(nameDictionaryRef);
obj.forEach(function catalogForEach(key, value) {
if (!value) return;
dests[key] = fetchDestination(xref, value);
});
}
if (nameTreeRef) {
// reading name tree
var processed = new RefSet();
processed.put(nameTreeRef);
var queue = [nameTreeRef];
while (queue.length > 0) {
var i, n;
obj = xref.fetch(queue.shift());
if (obj.has('Kids')) {
var kids = obj.get('Kids');
for (i = 0, n = kids.length; i < n; i++) {
var kid = kids[i];
if (processed.has(kid))
error('invalid destinations');
queue.push(kid);
processed.put(kid);
}
continue;
}
var names = obj.get('Names');
for (i = 0, n = names.length; i < n; i += 2) {
dests[names[i]] = fetchDestination(xref, names[i + 1]);
}
}
}
return shadow(this, 'destinations', dests);
},
getPage: function catalogGetPage(n) {
var pageCache = this.pageCache;
if (!pageCache) {
pageCache = this.pageCache = [];
this.traverseKids(this.toplevelPagesDict);
}
return this.pageCache[n - 1];
}
};
return constructor;
})();
var XRef = (function xRefXRef() {
function constructor(stream, startXRef, mainXRefEntriesOffset) {
this.stream = stream;
this.entries = [];
this.xrefstms = {};
var trailerDict = this.readXRef(startXRef);
// prepare the XRef cache
this.cache = [];
var encrypt = trailerDict.get('Encrypt');
if (encrypt) {
var fileId = trailerDict.get('ID');
this.encrypt = new CipherTransformFactory(this.fetch(encrypt),
fileId[0] /*, password */);
}
// get the root dictionary (catalog) object
if (!isRef(this.root = trailerDict.get('Root')))
error('Invalid root reference');
}
constructor.prototype = {
readXRefTable: function readXRefTable(parser) {
var obj;
while (true) {
if (isCmd(obj = parser.getObj(), 'trailer'))
break;
if (!isInt(obj))
error('Invalid XRef table');
var first = obj;
if (!isInt(obj = parser.getObj()))
error('Invalid XRef table');
var n = obj;
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 = {};
if (!isInt(obj = parser.getObj()))
error('Invalid XRef table: ' + first + ', ' + n);
entry.offset = obj;
if (!isInt(obj = parser.getObj()))
error('Invalid XRef table: ' + first + ', ' + n);
entry.gen = obj;
obj = parser.getObj();
if (isCmd(obj, 'n')) {
entry.uncompressed = true;
} else if (isCmd(obj, 'f')) {
entry.free = true;
} else {
error('Invalid XRef table: ' + first + ', ' + n);
}
if (!this.entries[i]) {
// In some buggy PDF files the xref table claims to start at 1
// instead of 0.
if (i == 1 && first == 1 &&
entry.offset == 0 && entry.gen == 65535 && entry.free) {
i = first = 0;
}
this.entries[i] = entry;
}
}
}
// read the trailer dictionary
var dict;
if (!isDict(dict = parser.getObj()))
error('Invalid XRef table');
// get the 'Prev' pointer
var prev;
obj = dict.get('Prev');
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
if (isInt(obj = dict.get('XRefStm'))) {
var pos = obj;
// ignore previously loaded xref streams (possible infinite recursion)
if (!(pos in this.xrefstms)) {
this.xrefstms[pos] = 1;
this.readXRef(pos);
}
}
return dict;
},
readXRefStream: function readXRefStream(stream) {
var streamParameters = stream.parameters;
var byteWidths = streamParameters.get('W');
var range = streamParameters.get('Index');
if (!range)
range = [0, streamParameters.get('Size')];
var i, j;
while (range.length > 0) {
var first = range[0], n = range[1];
if (!isInt(first) || !isInt(n))
error('Invalid XRef range fields: ' + first + ', ' + n);
var typeFieldWidth = byteWidths[0];
var offsetFieldWidth = byteWidths[1];
var generationFieldWidth = byteWidths[2];
if (!isInt(typeFieldWidth) || !isInt(offsetFieldWidth) ||
!isInt(generationFieldWidth)) {
error('Invalid XRef entry fields length: ' + first + ', ' + n);
}
for (i = 0; i < n; ++i) {
var type = 0, offset = 0, generation = 0;
for (j = 0; j < typeFieldWidth; ++j)
type = (type << 8) | stream.getByte();
// if type field is absent, its default value = 1
if (typeFieldWidth == 0)
type = 1;
for (j = 0; j < offsetFieldWidth; ++j)
offset = (offset << 8) | stream.getByte();
for (j = 0; j < generationFieldWidth; ++j)
generation = (generation << 8) | stream.getByte();
var entry = {};
entry.offset = offset;
entry.gen = generation;
switch (type) {
case 0:
entry.free = true;
break;
case 1:
entry.uncompressed = true;
break;
case 2:
break;
default:
error('Invalid XRef entry type: ' + type);
}
if (!this.entries[first + i])
this.entries[first + i] = entry;
}
range.splice(0, 2);
}
var prev = streamParameters.get('Prev');
if (isInt(prev))
this.readXRef(prev);
return streamParameters;
},
indexObjects: function indexObjects() {
// Simple scan through the PDF content to find objects,
// trailers and XRef streams.
function readToken(data, offset) {
var token = '', ch = data[offset];
while (ch !== 13 && ch !== 10) {
if (++offset >= data.length)
break;
token += String.fromCharCode(ch);
ch = data[offset];
}
return token;
}
function skipUntil(data, offset, what) {
var length = what.length, dataLength = data.length;
var skipped = 0;
// finding byte sequence
while (offset < dataLength) {
var i = 0;
while (i < length && data[offset + i] == what[i])
++i;
if (i >= length)
break; // sequence found
offset++;
skipped++;
}
return skipped;
}
var trailerBytes = new Uint8Array([116, 114, 97, 105, 108, 101, 114]);
var startxrefBytes = new Uint8Array([115, 116, 97, 114, 116, 120, 114,
101, 102]);
var endobjBytes = new Uint8Array([101, 110, 100, 111, 98, 106]);
var xrefBytes = new Uint8Array([47, 88, 82, 101, 102]);
var stream = this.stream;
stream.pos = 0;
var buffer = stream.getBytes();
var position = stream.start, length = buffer.length;
var trailers = [], xrefStms = [];
var state = 0;
var currentToken;
while (position < length) {
var ch = buffer[position];
if (ch === 32 || ch === 9 || ch === 13 || ch === 10) {
++position;
continue;
}
if (ch === 37) { // %-comment
do {
++position;
ch = buffer[position];
} while (ch !== 13 && ch !== 10);
continue;
}
var token = readToken(buffer, position);
var m;
if (token === 'xref') {
position += skipUntil(buffer, position, trailerBytes);
trailers.push(position);
position += skipUntil(buffer, position, startxrefBytes);
} else if ((m = /^(\d+)\s+(\d+)\s+obj\b/.exec(token))) {
this.entries[m[1]] = {
offset: position,
gen: m[2] | 0,
uncompressed: true
};
var contentLength = skipUntil(buffer, position, endobjBytes) + 7;
var content = buffer.subarray(position, position + contentLength);
// checking XRef stream suspect
// (it shall have '/XRef' and next char is not a letter)
var xrefTagOffset = skipUntil(content, 0, xrefBytes);
if (xrefTagOffset < contentLength &&
content[xrefTagOffset + 5] < 64) {
xrefStms.push(position);
this.xrefstms[position] = 1; // don't read it recursively
}
position += contentLength;
} else
position += token.length + 1;
}
// reading XRef streams
for (var i = 0, ii = xrefStms.length; i < ii; ++i) {
this.readXRef(xrefStms[i]);
}
// finding main trailer
var dict;
for (var i = 0, ii = trailers.length; i < ii; ++i) {
stream.pos = trailers[i];
var parser = new Parser(new Lexer(stream), true);
var obj = parser.getObj();
if (!isCmd(obj, 'trailer'))
continue;
// read the trailer dictionary
if (!isDict(dict = parser.getObj()))
continue;
// taking the first one with 'ID'
if (dict.has('ID'))
return dict;
}
// no tailer with 'ID', taking last one (if exists)
if (dict)
return dict;
// nothing helps
error('Invalid PDF structure');
return null;
},
readXRef: function readXref(startXRef) {
var stream = this.stream;
stream.pos = startXRef;
var parser = new Parser(new Lexer(stream), true);
var obj = parser.getObj();
// parse an old-style xref table
if (isCmd(obj, 'xref'))
return this.readXRefTable(parser);
// parse an xref stream
if (isInt(obj)) {
if (!isInt(parser.getObj()) ||
!isCmd(parser.getObj(), 'obj') ||
!isStream(obj = parser.getObj())) {
error('Invalid XRef stream');
}
return this.readXRefStream(obj);
}
return this.indexObjects();
},
getEntry: function xRefGetEntry(i) {
var e = this.entries[i];
if (e.free)
error('reading an XRef stream not implemented yet');
return e;
},
fetchIfRef: function xRefFetchIfRef(obj) {
if (!isRef(obj))
return obj;
return this.fetch(obj);
},
fetch: function xRefFetch(ref, suppressEncryption) {
var num = ref.num;
var e = this.cache[num];
if (e)
return e;
e = this.getEntry(num);
var gen = ref.gen;
var stream, parser;
if (e.uncompressed) {
if (e.gen != gen)
throw ('inconsistent generation in XRef');
stream = this.stream.makeSubStream(e.offset);
parser = new Parser(new Lexer(stream), true, this);
var obj1 = parser.getObj();
var obj2 = parser.getObj();
var obj3 = parser.getObj();
if (!isInt(obj1) || obj1 != num ||
!isInt(obj2) || obj2 != gen ||
!isCmd(obj3)) {
error('bad XRef entry');
}
if (!isCmd(obj3, 'obj')) {
// some bad pdfs use "obj1234" and really mean 1234
if (obj3.cmd.indexOf('obj') == 0) {
num = parseInt(obj3.cmd.substring(3), 10);
if (!isNaN(num))
return num;
}
error('bad XRef entry');
}
if (this.encrypt && !suppressEncryption) {
try {
e = parser.getObj(this.encrypt.createCipherTransform(num, gen));
} catch (ex) {
// almost all streams must be encrypted, but sometimes
// they are not probably due to some broken generators
// re-trying without encryption
return this.fetch(ref, true);
}
} else {
e = parser.getObj();
}
// Don't cache streams since they are mutable (except images).
if (!isStream(e) || e.getImage)
this.cache[num] = e;
return e;
}
// compressed entry
stream = this.fetch(new Ref(e.offset, 0));
if (!isStream(stream))
error('bad ObjStm stream');
var first = stream.parameters.get('First');
var n = stream.parameters.get('N');
if (!isInt(first) || !isInt(n)) {
error('invalid first and n parameters for ObjStm stream');
}
parser = new Parser(new Lexer(stream), false);
var i, entries = [], nums = [];
// read the object numbers to populate cache
for (i = 0; i < n; ++i) {
num = parser.getObj();
if (!isInt(num)) {
error('invalid object number in the ObjStm stream: ' + num);
}
nums.push(num);
var offset = parser.getObj();
if (!isInt(offset)) {
error('invalid object offset in the ObjStm stream: ' + offset);
}
}
// read stream objects for cache
for (i = 0; i < n; ++i) {
entries.push(parser.getObj());
this.cache[nums[i]] = entries[i];
}
e = entries[e.gen];
if (!e) {
error('bad XRef entry for compressed object');
}
return e;
},
getCatalogObj: function xRefGetCatalogObj() {
return this.fetch(this.root);
}
};
return constructor;
})();
/**
* A PDF document and page is built of many objects. E.g. there are objects
* for fonts, images, rendering code and such. These objects might get processed
* inside of a worker. The `PDFObjects` implements some basic functions to
* manage these objects.
*/
var PDFObjects = (function pdfObjects() {
function PDFObjects() {
this.objs = {};
}
PDFObjects.prototype = {
objs: null,
/**
* Internal function.
* Ensures there is an object defined for `objId`. Stores `data` on the
* object *if* it is created.
*/
ensureObj: function pdfObjectsEnsureObj(objId, data) {
if (this.objs[objId])
return this.objs[objId];
return this.objs[objId] = new Promise(objId, data);
},
/**
* If called *without* callback, this returns the data of `objId` but the
* object needs to be resolved. If it isn't, this function throws.
*
* If called *with* a callback, the callback is called with the data of the
* object once the object is resolved. That means, if you call this
* function and the object is already resolved, the callback gets called
* right away.
*/
get: function pdfObjectsGet(objId, callback) {
// If there is a callback, then the get can be async and the object is
// not required to be resolved right now
if (callback) {
this.ensureObj(objId).then(callback);
return null;
}
// If there isn't a callback, the user expects to get the resolved data
// directly.
var obj = this.objs[objId];
// If there isn't an object yet or the object isn't resolved, then the
// data isn't ready yet!
if (!obj || !obj.isResolved) {
throw 'Requesting object that isn\'t resolved yet ' + objId;
return null;
} else {
return obj.data;
}
},
/**
* Resolves the object `objId` with optional `data`.
*/
resolve: function pdfObjectsResolve(objId, data) {
var objs = this.objs;
// In case there is a promise already on this object, just resolve it.
if (objs[objId]) {
objs[objId].resolve(data);
} else {
this.ensureObj(objId, data);
}
},
onData: function pdfObjectsOnData(objId, callback) {
this.ensureObj(objId).onData(callback);
},
isResolved: function pdfObjectsIsResolved(objId) {
var objs = this.objs;
if (!objs[objId]) {
return false;
} else {
return objs[objId].isResolved;
}
},
hasData: function pdfObjectsHasData(objId) {
var objs = this.objs;
if (!objs[objId]) {
return false;
} else {
return objs[objId].hasData;
}
},
/**
* Sets the data of an object but *doesn't* resolve it.
*/
setData: function pdfObjectsSetData(objId, data) {
// Watchout! If you call `this.ensureObj(objId, data)` you're going to
// create a *resolved* promise which shouldn't be the case!
this.ensureObj(objId).data = data;
}
};
return PDFObjects;
})();

636
src/parser.js Normal file
View File

@ -0,0 +1,636 @@
/* -*- 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 EOF = {};
function isEOF(v) {
return v == EOF;
}
var Parser = (function parserParser() {
function constructor(lexer, allowStreams, xref) {
this.lexer = lexer;
this.allowStreams = allowStreams;
this.xref = xref;
this.inlineImg = 0;
this.refill();
}
constructor.prototype = {
refill: function parserRefill() {
this.buf1 = this.lexer.getObj();
this.buf2 = this.lexer.getObj();
},
shift: function parserShift() {
if (isCmd(this.buf2, 'ID')) {
this.buf1 = this.buf2;
this.buf2 = null;
// skip byte after ID
this.lexer.skip();
} else {
this.buf1 = this.buf2;
this.buf2 = this.lexer.getObj();
}
},
getObj: function parserGetObj(cipherTransform) {
if (isCmd(this.buf1, 'BI')) { // inline image
this.shift();
return this.makeInlineImage(cipherTransform);
}
if (isCmd(this.buf1, '[')) { // array
this.shift();
var array = [];
while (!isCmd(this.buf1, ']') && !isEOF(this.buf1))
array.push(this.getObj());
if (isEOF(this.buf1))
error('End of file inside array');
this.shift();
return array;
}
if (isCmd(this.buf1, '<<')) { // dictionary or stream
this.shift();
var dict = new Dict();
while (!isCmd(this.buf1, '>>') && !isEOF(this.buf1)) {
if (!isName(this.buf1)) {
error('Dictionary key must be a name object');
} else {
var key = this.buf1.name;
this.shift();
if (isEOF(this.buf1))
break;
dict.set(key, this.getObj(cipherTransform));
}
}
if (isEOF(this.buf1))
error('End of file inside dictionary');
// stream objects are not allowed inside content streams or
// object streams
if (isCmd(this.buf2, 'stream')) {
return this.allowStreams ?
this.makeStream(dict, cipherTransform) : dict;
}
this.shift();
return dict;
}
if (isInt(this.buf1)) { // indirect reference or integer
var num = this.buf1;
this.shift();
if (isInt(this.buf1) && isCmd(this.buf2, 'R')) {
var ref = new Ref(num, this.buf1);
this.shift();
this.shift();
return ref;
}
return num;
}
if (isString(this.buf1)) { // string
var str = this.buf1;
this.shift();
if (cipherTransform)
str = cipherTransform.decryptString(str);
return str;
}
// simple object
var obj = this.buf1;
this.shift();
return obj;
},
makeInlineImage: function parserMakeInlineImage(cipherTransform) {
var lexer = this.lexer;
var stream = lexer.stream;
// parse dictionary
var dict = new Dict();
while (!isCmd(this.buf1, 'ID') && !isEOF(this.buf1)) {
if (!isName(this.buf1)) {
error('Dictionary key must be a name object');
} else {
var key = this.buf1.name;
this.shift();
if (isEOF(this.buf1))
break;
dict.set(key, this.getObj(cipherTransform));
}
}
// parse image stream
var startPos = stream.pos;
// searching for the /\sEI\s/
var state = 0, ch;
while (state != 4 && (ch = stream.getByte()) != null) {
switch (ch) {
case 0x20:
case 0x0D:
case 0x0A:
state = state === 3 ? 4 : 1;
break;
case 0x45:
state = state === 1 ? 2 : 0;
break;
case 0x49:
state = state === 2 ? 3 : 0;
break;
default:
state = 0;
break;
}
}
// TODO improve the small images performance to remove the limit
var inlineImgLimit = 500;
if (++this.inlineImg >= inlineImgLimit) {
if (this.inlineImg === inlineImgLimit)
warn('Too many inline images');
this.shift();
return null;
}
var length = (stream.pos - 4) - startPos;
var imageStream = stream.makeSubStream(startPos, length, dict);
if (cipherTransform)
imageStream = cipherTransform.createStream(imageStream);
imageStream = this.filter(imageStream, dict, length);
imageStream.parameters = dict;
this.buf2 = new Cmd('EI');
this.shift();
return imageStream;
},
makeStream: function parserMakeStream(dict, cipherTransform) {
var lexer = this.lexer;
var stream = lexer.stream;
// get stream start position
lexer.skipToNextLine();
var pos = stream.pos;
// get length
var length = dict.get('Length');
var xref = this.xref;
if (xref)
length = xref.fetchIfRef(length);
if (!isInt(length)) {
error('Bad ' + length + ' attribute in stream');
length = 0;
}
// skip over the stream data
stream.pos = pos + length;
this.shift(); // '>>'
this.shift(); // 'stream'
if (!isCmd(this.buf1, 'endstream'))
error('Missing endstream');
this.shift();
stream = stream.makeSubStream(pos, length, dict);
if (cipherTransform)
stream = cipherTransform.createStream(stream);
stream = this.filter(stream, dict, length);
stream.parameters = dict;
return stream;
},
filter: function parserFilter(stream, dict, length) {
var filter = dict.get('Filter', 'F');
var params = dict.get('DecodeParms', 'DP');
if (isName(filter))
return this.makeFilter(stream, filter.name, length, params);
if (isArray(filter)) {
var filterArray = filter;
var paramsArray = params;
for (var i = 0, ii = filterArray.length; i < ii; ++i) {
filter = filterArray[i];
if (!isName(filter))
error('Bad filter name: ' + filter);
else {
params = null;
if (isArray(paramsArray) && (i in paramsArray))
params = paramsArray[i];
stream = this.makeFilter(stream, filter.name, length, params);
// after the first stream the length variable is invalid
length = null;
}
}
}
return stream;
},
makeFilter: function parserMakeFilter(stream, name, length, params) {
if (name == 'FlateDecode' || name == 'Fl') {
if (params) {
return new PredictorStream(new FlateStream(stream), params);
}
return new FlateStream(stream);
} else if (name == 'LZWDecode' || name == 'LZW') {
var earlyChange = 1;
if (params) {
if (params.has('EarlyChange'))
earlyChange = params.get('EarlyChange');
return new PredictorStream(
new LZWStream(stream, earlyChange), params);
}
return new LZWStream(stream, earlyChange);
} else if (name == 'DCTDecode' || name == 'DCT') {
var bytes = stream.getBytes(length);
return new JpegStream(bytes, stream.dict);
} else if (name == 'ASCII85Decode' || name == 'A85') {
return new Ascii85Stream(stream);
} else if (name == 'ASCIIHexDecode' || name == 'AHx') {
return new AsciiHexStream(stream);
} else if (name == 'CCITTFaxDecode' || name == 'CCF') {
return new CCITTFaxStream(stream, params);
} else {
TODO('filter "' + name + '" not supported yet');
}
return stream;
}
};
return constructor;
})();
var Lexer = (function lexer() {
function constructor(stream) {
this.stream = stream;
}
constructor.isSpace = function lexerIsSpace(ch) {
return ch == ' ' || ch == '\t' || ch == '\x0d' || ch == '\x0a';
};
// A '1' in this array means the character is white space. A '1' or
// '2' means the character ends a name or command.
var specialChars = [
1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, // 0x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1x
1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, // 2x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, // 3x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 5x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, // 7x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9x
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ax
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // bx
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // cx
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // dx
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // ex
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // fx
];
function toHexDigit(ch) {
if (ch >= '0' && ch <= '9')
return ch.charCodeAt(0) - 48;
ch = ch.toUpperCase();
if (ch >= 'A' && ch <= 'F')
return ch.charCodeAt(0) - 55;
return -1;
}
constructor.prototype = {
getNumber: function lexerGetNumber(ch) {
var floating = false;
var str = ch;
var stream = this.stream;
for (;;) {
ch = stream.lookChar();
if (ch == '.' && !floating) {
str += ch;
floating = true;
} else if (ch == '-') {
// ignore minus signs in the middle of numbers to match
// Adobe's behavior
warn('Badly formated number');
} else if (ch >= '0' && ch <= '9') {
str += ch;
} else if (ch == 'e' || ch == 'E') {
floating = true;
} else {
// the last character doesn't belong to us
break;
}
stream.skip();
}
var value = parseFloat(str);
if (isNaN(value))
error('Invalid floating point number: ' + value);
return value;
},
getString: function lexerGetString() {
var numParen = 1;
var done = false;
var str = '';
var stream = this.stream;
var ch;
do {
ch = stream.getChar();
switch (ch) {
case undefined:
warn('Unterminated string');
done = true;
break;
case '(':
++numParen;
str += ch;
break;
case ')':
if (--numParen == 0) {
done = true;
} else {
str += ch;
}
break;
case '\\':
ch = stream.getChar();
switch (ch) {
case undefined:
warn('Unterminated string');
done = true;
break;
case 'n':
str += '\n';
break;
case 'r':
str += '\r';
break;
case 't':
str += '\t';
break;
case 'b':
str += '\b';
break;
case 'f':
str += '\f';
break;
case '\\':
case '(':
case ')':
str += ch;
break;
case '0': case '1': case '2': case '3':
case '4': case '5': case '6': case '7':
var x = ch - '0';
ch = stream.lookChar();
if (ch >= '0' && ch <= '7') {
stream.skip();
x = (x << 3) + (ch - '0');
ch = stream.lookChar();
if (ch >= '0' && ch <= '7') {
stream.skip();
x = (x << 3) + (ch - '0');
}
}
str += String.fromCharCode(x);
break;
case '\r':
ch = stream.lookChar();
if (ch == '\n')
stream.skip();
break;
case '\n':
break;
default:
str += ch;
}
break;
default:
str += ch;
}
} while (!done);
return str;
},
getName: function lexerGetName(ch) {
var str = '';
var stream = this.stream;
while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) {
stream.skip();
if (ch == '#') {
ch = stream.lookChar();
var x = toHexDigit(ch);
if (x != -1) {
stream.skip();
var x2 = toHexDigit(stream.getChar());
if (x2 == -1)
error('Illegal digit in hex char in name: ' + x2);
str += String.fromCharCode((x << 4) | x2);
} else {
str += '#';
str += ch;
}
} else {
str += ch;
}
}
if (str.length > 128)
error('Warning: name token is longer than allowed by the spec: ' +
str.length);
return new Name(str);
},
getHexString: function lexerGetHexString(ch) {
var str = '';
var stream = this.stream;
for (;;) {
ch = stream.getChar();
if (ch == '>') {
break;
}
if (!ch) {
warn('Unterminated hex string');
break;
}
if (specialChars[ch.charCodeAt(0)] != 1) {
var x, x2;
if ((x = toHexDigit(ch)) == -1)
error('Illegal character in hex string: ' + ch);
ch = stream.getChar();
while (specialChars[ch.charCodeAt(0)] == 1)
ch = stream.getChar();
if ((x2 = toHexDigit(ch)) == -1)
error('Illegal character in hex string: ' + ch);
str += String.fromCharCode((x << 4) | x2);
}
}
return str;
},
getObj: function lexerGetObj() {
// skip whitespace and comments
var comment = false;
var stream = this.stream;
var ch;
while (true) {
if (!(ch = stream.getChar()))
return EOF;
if (comment) {
if (ch == '\r' || ch == '\n')
comment = false;
} else if (ch == '%') {
comment = true;
} else if (specialChars[ch.charCodeAt(0)] != 1) {
break;
}
}
// start reading token
switch (ch) {
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '+': case '-': case '.':
return this.getNumber(ch);
case '(':
return this.getString();
case '/':
return this.getName(ch);
// array punctuation
case '[':
case ']':
return new Cmd(ch);
// hex string or dict punctuation
case '<':
ch = stream.lookChar();
if (ch == '<') {
// dict punctuation
stream.skip();
return new Cmd('<<');
}
return this.getHexString(ch);
// dict punctuation
case '>':
ch = stream.lookChar();
if (ch == '>') {
stream.skip();
return new Cmd('>>');
}
case '{':
case '}':
return new Cmd(ch);
// fall through
case ')':
error('Illegal character: ' + ch);
return Error;
}
// command
var str = ch;
while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) {
stream.skip();
if (str.length == 128) {
error('Command token too long: ' + str.length);
break;
}
str += ch;
}
if (str == 'true')
return true;
if (str == 'false')
return false;
if (str == 'null')
return null;
return new Cmd(str);
},
skipToNextLine: function lexerSkipToNextLine() {
var stream = this.stream;
while (true) {
var ch = stream.getChar();
if (!ch || ch == '\n')
return;
if (ch == '\r') {
if ((ch = stream.lookChar()) == '\n')
stream.skip();
return;
}
}
},
skip: function lexerSkip() {
this.stream.skip();
}
};
return constructor;
})();
var Linearization = (function linearizationLinearization() {
function constructor(stream) {
this.parser = new Parser(new Lexer(stream), false);
var obj1 = this.parser.getObj();
var obj2 = this.parser.getObj();
var obj3 = this.parser.getObj();
this.linDict = this.parser.getObj();
if (isInt(obj1) && isInt(obj2) && isCmd(obj3, 'obj') &&
isDict(this.linDict)) {
var obj = this.linDict.get('Linearized');
if (!(isNum(obj) && obj > 0))
this.linDict = null;
}
}
constructor.prototype = {
getInt: function linearizationGetInt(name) {
var linDict = this.linDict;
var obj;
if (isDict(linDict) &&
isInt(obj = linDict.get(name)) &&
obj > 0) {
return obj;
}
error('"' + name + '" field in linearization table is invalid');
return 0;
},
getHint: function linearizationGetHint(index) {
var linDict = this.linDict;
var obj1, obj2;
if (isDict(linDict) &&
isArray(obj1 = linDict.get('H')) &&
obj1.length >= 2 &&
isInt(obj2 = obj1[index]) &&
obj2 > 0) {
return obj2;
}
error('Hints table in linearization table is invalid: ' + index);
return 0;
},
get length() {
if (!isDict(this.linDict))
return 0;
return this.getInt('L');
},
get hintsOffset() {
return this.getHint(0);
},
get hintsLength() {
return this.getHint(1);
},
get hintsOffset2() {
return this.getHint(2);
},
get hintsLenth2() {
return this.getHint(3);
},
get objectNumberFirst() {
return this.getInt('O');
},
get endFirst() {
return this.getInt('E');
},
get numPages() {
return this.getInt('N');
},
get mainXRefEntriesOffset() {
return this.getInt('T');
},
get pageFirst() {
return this.getInt('P');
}
};
return constructor;
})();

290
src/pattern.js Normal file
View File

@ -0,0 +1,290 @@
/* -*- 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 Pattern = (function patternPattern() {
// Constructor should define this.getPattern
function constructor() {
error('should not call Pattern constructor');
}
constructor.prototype = {
// Input: current Canvas context
// Output: the appropriate fillStyle or strokeStyle
getPattern: function pattern_getStyle(ctx) {
error('Should not call Pattern.getStyle: ' + ctx);
}
};
constructor.shadingFromIR = function pattern_shadingFromIR(ctx, raw) {
return Shadings[raw[0]].fromIR(ctx, raw);
}
constructor.parseShading = function pattern_shading(shading, matrix,
xref, res, ctx) {
var dict = isStream(shading) ? shading.dict : shading;
var type = dict.get('ShadingType');
switch (type) {
case 2:
case 3:
// both radial and axial shadings are handled by RadialAxial shading
return new Shadings.RadialAxial(dict, matrix, xref, res, ctx);
default:
return new Shadings.Dummy();
}
};
return constructor;
})();
var Shadings = {};
// Radial and axial shading have very similar implementations
// If needed, the implementations can be broken into two classes
Shadings.RadialAxial = (function radialAxialShading() {
function constructor(dict, matrix, xref, res, ctx) {
this.matrix = matrix;
this.coordsArr = dict.get('Coords');
this.shadingType = dict.get('ShadingType');
this.type = 'Pattern';
this.ctx = ctx;
var cs = dict.get('ColorSpace', 'CS');
cs = ColorSpace.parse(cs, xref, res);
this.cs = cs;
var t0 = 0.0, t1 = 1.0;
if (dict.has('Domain')) {
var domainArr = dict.get('Domain');
t0 = domainArr[0];
t1 = domainArr[1];
}
var extendStart = false, extendEnd = false;
if (dict.has('Extend')) {
var extendArr = dict.get('Extend');
extendStart = extendArr[0];
extendEnd = extendArr[1];
TODO('Support extend');
}
this.extendStart = extendStart;
this.extendEnd = extendEnd;
var fnObj = dict.get('Function');
fnObj = xref.fetchIfRef(fnObj);
if (isArray(fnObj))
error('No support for array of functions');
else if (!isPDFFunction(fnObj))
error('Invalid function');
var fn = PDFFunction.parse(xref, fnObj);
// 10 samples seems good enough for now, but probably won't work
// if there are sharp color changes. Ideally, we would implement
// the spec faithfully and add lossless optimizations.
var step = (t1 - t0) / 10;
var diff = t1 - t0;
var colorStops = [];
for (var i = t0; i <= t1; i += step) {
var color = fn([i]);
var rgbColor = Util.makeCssRgb.apply(this, cs.getRgb(color));
colorStops.push([(i - t0) / diff, rgbColor]);
}
this.colorStops = colorStops;
}
constructor.fromIR = function radialAxialShadingGetIR(ctx, raw) {
var type = raw[1];
var colorStops = raw[2];
var p0 = raw[3];
var p1 = raw[4];
var r0 = raw[5];
var r1 = raw[6];
var curMatrix = ctx.mozCurrentTransform;
if (curMatrix) {
var userMatrix = ctx.mozCurrentTransformInverse;
p0 = Util.applyTransform(p0, curMatrix);
p0 = Util.applyTransform(p0, userMatrix);
p1 = Util.applyTransform(p1, curMatrix);
p1 = Util.applyTransform(p1, userMatrix);
}
if (type == 2)
var grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]);
else if (type == 3)
var grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1);
for (var i = 0, ii = colorStops.length; i < ii; ++i) {
var c = colorStops[i];
grad.addColorStop(c[0], c[1]);
}
return grad;
}
constructor.prototype = {
getIR: function radialAxialShadingGetIR() {
var coordsArr = this.coordsArr;
var type = this.shadingType;
if (type == 2) {
var p0 = [coordsArr[0], coordsArr[1]];
var p1 = [coordsArr[2], coordsArr[3]];
var r0 = null;
var r1 = null;
} else if (type == 3) {
var p0 = [coordsArr[0], coordsArr[1]];
var p1 = [coordsArr[3], coordsArr[4]];
var r0 = coordsArr[2];
var r1 = coordsArr[5];
} else {
error('getPattern type unknown: ' + type);
}
var matrix = this.matrix;
if (matrix) {
p0 = Util.applyTransform(p0, matrix);
p1 = Util.applyTransform(p1, matrix);
}
return ['RadialAxial', type, this.colorStops, p0, p1, r0, r1];
}
};
return constructor;
})();
Shadings.Dummy = (function dummyShading() {
function constructor() {
this.type = 'Pattern';
}
constructor.fromIR = function dummyShadingFromIR() {
return 'hotpink';
}
constructor.prototype = {
getIR: function dummyShadingGetIR() {
return ['Dummy'];
}
};
return constructor;
})();
var TilingPattern = (function tilingPattern() {
var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2;
function TilingPattern(IR, color, ctx, objs) {
var IRQueue = IR[2];
this.matrix = IR[3];
var bbox = IR[4];
var xstep = IR[5];
var ystep = IR[6];
var paintType = IR[7];
TODO('TilingType');
this.curMatrix = ctx.mozCurrentTransform;
this.invMatrix = ctx.mozCurrentTransformInverse;
this.ctx = ctx;
this.type = 'Pattern';
var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
var topLeft = [x0, y0];
// we want the canvas to be as large as the step size
var botRight = [x0 + xstep, y0 + ystep];
var width = botRight[0] - topLeft[0];
var height = botRight[1] - topLeft[1];
// TODO: hack to avoid OOM, we would idealy compute the tiling
// pattern to be only as large as the acual size in device space
// This could be computed with .mozCurrentTransform, but still
// needs to be implemented
while (Math.abs(width) > 512 || Math.abs(height) > 512) {
width = 512;
height = 512;
}
var tmpCanvas = new ScratchCanvas(width, height);
// set the new canvas element context as the graphics context
var tmpCtx = tmpCanvas.getContext('2d');
var graphics = new CanvasGraphics(tmpCtx, objs);
switch (paintType) {
case PAINT_TYPE_COLORED:
tmpCtx.fillStyle = ctx.fillStyle;
tmpCtx.strokeStyle = ctx.strokeStyle;
break;
case PAINT_TYPE_UNCOLORED:
color = Util.makeCssRgb.apply(this, color);
tmpCtx.fillStyle = color;
tmpCtx.strokeStyle = color;
break;
default:
error('Unsupported paint type: ' + paintType);
}
var scale = [width / xstep, height / ystep];
this.scale = scale;
// transform coordinates to pattern space
var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]];
var tmpScale = [scale[0], 0, 0, scale[1], 0, 0];
graphics.transform.apply(graphics, tmpScale);
graphics.transform.apply(graphics, tmpTranslate);
if (bbox && isArray(bbox) && 4 == bbox.length) {
var bboxWidth = bbox[2] - bbox[0];
var bboxHeight = bbox[3] - bbox[1];
graphics.rectangle(bbox[0], bbox[1], bboxWidth, bboxHeight);
graphics.clip();
graphics.endPath();
}
graphics.executeIRQueue(IRQueue);
this.canvas = tmpCanvas;
}
TilingPattern.getIR = function tiling_getIR(codeIR, dict, args) {
var matrix = dict.get('Matrix');
var bbox = dict.get('BBox');
var xstep = dict.get('XStep');
var ystep = dict.get('YStep');
var paintType = dict.get('PaintType');
return [
'TilingPattern', args, codeIR, matrix, bbox, xstep, ystep, paintType
];
}
TilingPattern.prototype = {
getPattern: function tiling_getPattern() {
var matrix = this.matrix;
var curMatrix = this.curMatrix;
var ctx = this.ctx;
if (curMatrix)
ctx.setTransform.apply(ctx, curMatrix);
if (matrix)
ctx.transform.apply(ctx, matrix);
var scale = this.scale;
ctx.scale(1 / scale[0], 1 / scale[1]);
return ctx.createPattern(this.canvas, 'repeat');
}
};
return TilingPattern;
})();

14
src/pdf.js Normal file
View File

@ -0,0 +1,14 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
var PDFJS = {};
(function pdfjsWrapper() {
// Use strict in our context only - users might not want it
'use strict';
// Files are inserted below - see Makefile
/* PDFJSSCRIPT_INCLUDE_ALL */
}).call((typeof window === 'undefined') ? this : window);

2076
src/stream.js Normal file

File diff suppressed because it is too large Load Diff

286
src/util.js Normal file
View File

@ -0,0 +1,286 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
function log(msg) {
if (console && console.log)
console.log(msg);
else if (print)
print(msg);
}
function warn(msg) {
if (verbosity >= WARNINGS)
log('Warning: ' + msg);
}
function backtrace() {
try {
throw new Error();
} catch (e) {
return e.stack ? e.stack.split('\n').slice(2).join('\n') : '';
}
}
function error(msg) {
log('Error: ' + msg);
log(backtrace());
throw new Error(msg);
}
function TODO(what) {
if (verbosity >= TODOS)
log('TODO: ' + what);
}
function malformed(msg) {
error('Malformed PDF: ' + msg);
}
function assert(cond, msg) {
if (!cond)
error(msg);
}
// In a well-formed PDF, |cond| holds. If it doesn't, subsequent
// behavior is undefined.
function assertWellFormed(cond, msg) {
if (!cond)
malformed(msg);
}
function shadow(obj, prop, value) {
Object.defineProperty(obj, prop, { value: value,
enumerable: true,
configurable: true,
writable: false });
return value;
}
function bytesToString(bytes) {
var str = '';
var length = bytes.length;
for (var n = 0; n < length; ++n)
str += String.fromCharCode(bytes[n]);
return str;
}
function stringToBytes(str) {
var length = str.length;
var bytes = new Uint8Array(length);
for (var n = 0; n < length; ++n)
bytes[n] = str.charCodeAt(n) & 0xFF;
return bytes;
}
var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
var Util = (function utilUtil() {
function constructor() {}
constructor.makeCssRgb = function makergb(r, g, b) {
var ri = (255 * r) | 0, gi = (255 * g) | 0, bi = (255 * b) | 0;
return 'rgb(' + ri + ',' + gi + ',' + bi + ')';
};
constructor.makeCssCmyk = function makecmyk(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;
return 'rgb(' + ri + ',' + gi + ',' + bi + ')';
};
constructor.applyTransform = function apply(p, m) {
var xt = p[0] * m[0] + p[1] * m[2] + m[4];
var yt = p[0] * m[1] + p[1] * m[3] + m[5];
return [xt, yt];
};
return constructor;
})();
var PDFStringTranslateTable = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014,
0x2013, 0x192, 0x2044, 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C,
0x201D, 0x2018, 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x141, 0x152, 0x160,
0x178, 0x17D, 0x131, 0x142, 0x153, 0x161, 0x17E, 0, 0x20AC
];
function stringToPDFString(str) {
var i, n = str.length, str2 = '';
if (str[0] === '\xFE' && str[1] === '\xFF') {
// UTF16BE BOM
for (i = 2; i < n; i += 2)
str2 += String.fromCharCode(
(str.charCodeAt(i) << 8) | str.charCodeAt(i + 1));
} else {
for (i = 0; i < n; ++i) {
var code = PDFStringTranslateTable[str.charCodeAt(i)];
str2 += code ? String.fromCharCode(code) : str.charAt(i);
}
}
return str2;
}
function isBool(v) {
return typeof v == 'boolean';
}
function isInt(v) {
return typeof v == 'number' && ((v | 0) == v);
}
function isNum(v) {
return typeof v == 'number';
}
function isString(v) {
return typeof v == 'string';
}
function isNull(v) {
return v === null;
}
function isName(v) {
return v instanceof Name;
}
function isCmd(v, cmd) {
return v instanceof Cmd && (!cmd || v.cmd == cmd);
}
function isDict(v, type) {
return v instanceof Dict && (!type || v.get('Type').name == type);
}
function isArray(v) {
return v instanceof Array;
}
function isStream(v) {
return typeof v == 'object' && v != null && ('getChar' in v);
}
function isArrayBuffer(v) {
return typeof v == 'object' && v != null && ('byteLength' in v);
}
function isRef(v) {
return v instanceof Ref;
}
function isPDFFunction(v) {
var fnDict;
if (typeof v != 'object')
return false;
else if (isDict(v))
fnDict = v;
else if (isStream(v))
fnDict = v.dict;
else
return false;
return fnDict.has('FunctionType');
}
/**
* 'Promise' object.
* Each object that is stored in PDFObjects is based on a Promise object that
* contains the status of the object and the data. There migth be situations,
* where a function want to use the value of an object, but it isn't ready at
* that time. To get a notification, once the object is ready to be used, s.o.
* can add a callback using the `then` method on the promise that then calls
* the callback once the object gets resolved.
* A promise can get resolved only once and only once the data of the promise
* can be set. If any of these happens twice or the data is required before
* it was set, an exception is throw.
*/
var Promise = (function promise() {
var EMPTY_PROMISE = {};
/**
* If `data` is passed in this constructor, the promise is created resolved.
* If there isn't data, it isn't resolved at the beginning.
*/
function Promise(name, data) {
this.name = name;
// If you build a promise and pass in some data it's already resolved.
if (data != null) {
this.isResolved = true;
this._data = data;
this.hasData = true;
} else {
this.isResolved = false;
this._data = EMPTY_PROMISE;
}
this.callbacks = [];
};
Promise.prototype = {
hasData: false,
set data(value) {
if (value === undefined) {
return;
}
if (this._data !== EMPTY_PROMISE) {
throw 'Promise ' + this.name +
': Cannot set the data of a promise twice';
}
this._data = value;
this.hasData = true;
if (this.onDataCallback) {
this.onDataCallback(value);
}
},
get data() {
if (this._data === EMPTY_PROMISE) {
throw 'Promise ' + this.name + ': Cannot get data that isn\'t set';
}
return this._data;
},
onData: function promiseOnData(callback) {
if (this._data !== EMPTY_PROMISE) {
callback(this._data);
} else {
this.onDataCallback = callback;
}
},
resolve: function promiseResolve(data) {
if (this.isResolved) {
throw 'A Promise can be resolved only once ' + this.name;
}
this.isResolved = true;
this.data = data;
var callbacks = this.callbacks;
for (var i = 0, ii = callbacks.length; i < ii; i++) {
callbacks[i].call(null, data);
}
},
then: function promiseThen(callback) {
if (!callback) {
throw 'Requiring callback' + this.name;
}
// If the promise is already resolved, call the callback directly.
if (this.isResolved) {
var data = this.data;
callback.call(null, data);
} else {
this.callbacks.push(callback);
}
}
};
return Promise;
})();

View File

@ -1,8 +1,6 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
var CFFEncodingMap = { var CFFEncodingMap = {
'0': '-reserved-', '0': '-reserved-',
'1': 'hstem', '1': 'hstem',

View File

@ -1,8 +1,6 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
/* /*
* The Type2 reader code below is only used for debugging purpose since Type2 * The Type2 reader code below is only used for debugging purpose since Type2
* is only a CharString format and is never used directly as a Font file. * is only a CharString format and is never used directly as a Font file.

193
src/worker.js Normal file
View File

@ -0,0 +1,193 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
function MessageHandler(name, comObj) {
this.name = name;
this.comObj = comObj;
var ah = this.actionHandler = {};
ah['console_log'] = [function ahConsoleLog(data) {
console.log.apply(console, data);
}];
ah['console_error'] = [function ahConsoleError(data) {
console.error.apply(console, data);
}];
comObj.onmessage = function messageHandlerComObjOnMessage(event) {
var data = event.data;
if (data.action in ah) {
var action = ah[data.action];
action[0].call(action[1], data.data);
} else {
throw 'Unkown action from worker: ' + data.action;
}
};
}
MessageHandler.prototype = {
on: function messageHandlerOn(actionName, handler, scope) {
var ah = this.actionHandler;
if (ah[actionName]) {
throw 'There is already an actionName called "' + actionName + '"';
}
ah[actionName] = [handler, scope];
},
send: function messageHandlerSend(actionName, data) {
this.comObj.postMessage({
action: actionName,
data: data
});
}
};
var WorkerMessageHandler = {
setup: function wphSetup(handler) {
var pdfDoc = null;
handler.on('test', function wphSetupTest(data) {
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) {
// Create only the model of the PDFDoc, which is enough for
// processing the content of the pdf.
pdfDoc = new PDFDocModel(new Stream(data));
});
handler.on('page_request', function wphSetupPageRequest(pageNum) {
pageNum = parseInt(pageNum);
var page = pdfDoc.getPage(pageNum);
// The following code does quite the same as
// Page.prototype.startRendering, but stops at one point and sends the
// result back to the main thread.
var gfx = new CanvasGraphics(null);
var start = Date.now();
var dependency = [];
// Pre compile the pdf page and fetch the fonts/images.
var IRQueue = page.getIRQueue(handler, dependency);
console.log('page=%d - getIRQueue: time=%dms, len=%d', pageNum,
Date.now() - start, IRQueue.fnArray.length);
// Filter the dependecies for fonts.
var fonts = {};
for (var i = 0, ii = dependency.length; i < ii; i++) {
var dep = dependency[i];
if (dep.indexOf('font_') == 0) {
fonts[dep] = true;
}
}
handler.send('page', {
pageNum: pageNum,
IRQueue: IRQueue,
depFonts: Object.keys(fonts)
});
}, this);
handler.on('font', function wphSetupFont(data) {
var objId = data[0];
var name = data[1];
var file = data[2];
var properties = data[3];
var font = {
name: name,
file: file,
properties: properties
};
// Some fonts don't have a file, e.g. the build in ones like Arial.
if (file) {
var fontFileDict = new Dict();
fontFileDict.map = file.dict.map;
var fontFile = new Stream(file.bytes, file.start,
file.end - file.start, fontFileDict);
// Check if this is a FlateStream. Otherwise just use the created
// Stream one. This makes complex_ttf_font.pdf work.
var cmf = file.bytes[0];
if ((cmf & 0x0f) == 0x08) {
font.file = new FlateStream(fontFile);
} else {
font.file = fontFile;
}
}
var obj = new Font(font.name, font.file, font.properties);
var str = '';
var objData = obj.data;
if (objData) {
var length = objData.length;
for (var j = 0; j < length; ++j)
str += String.fromCharCode(objData[j]);
}
obj.str = str;
// Remove the data array form the font object, as it's not needed
// anymore as we sent over the ready str.
delete obj.data;
handler.send('font_ready', [objId, obj]);
});
}
};
var consoleTimer = {};
var workerConsole = {
log: function log() {
var args = Array.prototype.slice.call(arguments);
postMessage({
action: 'console_log',
data: args
});
},
error: function error() {
var args = Array.prototype.slice.call(arguments);
postMessage({
action: 'console_error',
data: args
});
},
time: function time(name) {
consoleTimer[name] = Date.now();
},
timeEnd: function timeEnd(name) {
var time = consoleTimer[name];
if (time == null) {
throw 'Unkown timer name ' + name;
}
this.log('Timer:', name, Date.now() - time);
}
};
// Worker thread?
if (typeof window === 'undefined') {
globalScope.console = workerConsole;
var handler = new MessageHandler('worker_processor', this);
WorkerMessageHandler.setup(handler);
}

52
src/worker_loader.js Normal file
View File

@ -0,0 +1,52 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
function onMessageLoader(evt) {
// Reset the `onmessage` function as it was only set to call
// this function the first time a message is passed to the worker
// but shouldn't get called anytime afterwards.
this.onmessage = null;
if (evt.data.action !== 'workerSrc') {
throw 'Worker expects first message to be `workerSrc`';
}
// 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'
];
// Load all the files.
for (var i = 0; i < files.length; i++) {
importScripts(dir + files[i]);
}
}
this.onmessage = onMessageLoader;

View File

@ -63,6 +63,11 @@ function cleanup() {
} }
function nextTask() { function nextTask() {
// If there is a pdfDoc on the last task executed, destroy it to free memory.
if (task && task.pdfDoc) {
task.pdfDoc.destroy();
delete task.pdfDoc;
}
cleanup(); cleanup();
if (currentTaskIdx == manifest.length) { if (currentTaskIdx == manifest.length) {
@ -77,11 +82,11 @@ function nextTask() {
getPdf(task.file, function nextTaskGetPdf(data) { getPdf(task.file, function nextTaskGetPdf(data) {
var failure; var failure;
try { try {
task.pdfDoc = new PDFDoc(data); task.pdfDoc = new PDFJS.PDFDoc(data);
} catch (e) { } catch (e) {
failure = 'load PDF doc : ' + e.toString(); failure = 'load PDF doc : ' + e.toString();
} }
task.pageNum = 1; task.pageNum = task.firstPage || 1;
nextPage(task, failure); nextPage(task, failure);
}); });
} }

View File

@ -12,4 +12,6 @@
!rotation.pdf !rotation.pdf
!simpletype3font.pdf !simpletype3font.pdf
!sizes.pdf !sizes.pdf
!close-path-bug.pdf
!alphatrans.pdf

BIN
test/pdfs/alphatrans.pdf Normal file

Binary file not shown.

View File

@ -1 +1 @@
http://www.wrapon.com/docs/PIPEHEATCABLE.PDF https://wrap-on.com/docs/PIPEHEATCABLE.PDF

View File

@ -0,0 +1,69 @@
%PDF-1.4
1 0 obj
<</Type /Catalog/Outlines 2 0 R/Pages 3 0 R>>
endobj
2 0 obj
<</Type /Outlines/Count 0>>
endobj
3 0 obj
<</Type /Pages/Kids [4 0 R]/Count 1>>
endobj
4 0 obj
<</Type /Page/Parent 3 0 R/MediaBox [0 0 612 792]/Contents 5 0 R/Resources << /ProcSet 6 0 R >>>>
endobj
5 0 obj
<< /Length 885 >>
stream
% Draw a black line segment, using the default line width.
150 250 m
150 350 l
S
% Draw a thicker, dashed line segment.
4 w % Set line width to 4 points
[4 6] 0 d % Set dash pattern to 4 units on, 6 units off
150 250 m
400 250 l
S
[] 0 d % Reset dash pattern to a solid line
1 w % Reset line width to 1 unit
% Draw a rectangle with a 1unit red border, filled with light blue.
1.0 0.0 0.0 RG % Red for stroke color
0.5 0.75 1.0 rg % Light blue for fill color
200 300 50 75 re
B
% Draw a curve filled with gray and with a colored border.
0.5 0.1 0.2 RG
0.7 g
300 300 m
300 400 400 400 400 300 c
b
endstream
endobj
6 0 obj
[/PDF]
endobj
xref
0 7
0000000000 65535 f
0000000009 00000 n
0000000074 00000 n
0000000120 00000 n
0000000179 00000 n
0000000300 00000 n
0000001532 00000 n
trailer
<</Size 7/Root 1 0 R>>
startxref
1556
%%EOF

View File

@ -1,4 +1,4 @@
import json, platform, os, shutil, sys, subprocess, tempfile, threading, time, urllib, urllib2 import json, platform, os, shutil, sys, subprocess, tempfile, threading, time, urllib, urllib2, hashlib
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import SocketServer import SocketServer
from optparse import OptionParser from optparse import OptionParser
@ -316,9 +316,31 @@ def downloadLinkedPDFs(manifestList):
print 'done' print 'done'
def verifyPDFs(manifestList):
error = False
for item in manifestList:
f = item['file']
if os.access(f, os.R_OK):
fileMd5 = hashlib.md5(open(f).read()).hexdigest()
if 'md5' not in item:
print 'ERROR: Missing md5 for file "' + f + '".',
print 'Hash for current file is "' + fileMd5 + '"'
error = True
continue
md5 = item['md5']
if fileMd5 != md5:
print 'ERROR: MD5 of file "' + f + '" does not match file.',
print 'Expected "' + md5 + '" computed "' + fileMd5 + '"'
error = True
continue
else:
print 'ERROR: Unable to open file for reading "' + f + '".'
error = True
return not error
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('../pdf.js') and os.path.isdir('../.git') assert not ANAL 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.'
@ -342,6 +364,9 @@ def setUp(options):
downloadLinkedPDFs(manifestList) downloadLinkedPDFs(manifestList)
if not verifyPDFs(manifestList):
raise Exception('ERROR: failed to verify pdfs.')
for b in testBrowsers: for b in testBrowsers:
State.taskResults[b.name] = { } State.taskResults[b.name] = { }
for item in manifestList: for item in manifestList:
@ -506,17 +531,17 @@ def maybeUpdateRefImages(options, browser):
print ' Yes! The references in tmp/ can be synced with ref/.' print ' Yes! The references in tmp/ can be synced with ref/.'
if options.reftest: if options.reftest:
startReftest(browser, options) startReftest(browser, options)
if options.noPrompts or not prompt('Would you like to update the master copy in ref/?'): if options.noPrompts or prompt('Would you like to update the master copy in ref/?'):
print ' OK, not updating.'
else:
sys.stdout.write(' Updating ref/ ... ') sys.stdout.write(' Updating ref/ ... ')
# XXX unclear what to do on errors here ... # XXX unclear what to do on errors here ...
# NB: do *NOT* pass --delete to rsync. That breaks this # NB: do *NOT* pass --delete to rsync. That breaks this
# entire scheme. # entire scheme.
subprocess.check_call(( 'rsync', '-arv', 'tmp/', 'ref/' )) subprocess.check_call(( 'rsync', '-arvq', 'tmp/', 'ref/' ))
print 'done' print 'done'
else:
print ' OK, not updating.'
def startReftest(browser, options): def startReftest(browser, options):
url = "http://%s:%s" % (SERVER_HOST, options.port) url = "http://%s:%s" % (SERVER_HOST, options.port)

View File

@ -1,151 +1,178 @@
[ [
{ "id": "tracemonkey-eq", { "id": "tracemonkey-eq",
"file": "pdfs/tracemonkey.pdf", "file": "pdfs/tracemonkey.pdf",
"md5": "9a192d8b1a7dc652a19835f6f08098bd",
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "tracemonkey-fbf", { "id": "tracemonkey-fbf",
"file": "pdfs/tracemonkey.pdf", "file": "pdfs/tracemonkey.pdf",
"md5": "9a192d8b1a7dc652a19835f6f08098bd",
"rounds": 2, "rounds": 2,
"type": "fbf" "type": "fbf"
}, },
{ "id": "html5-canvas-cheat-sheet-load", { "id": "html5-canvas-cheat-sheet-load",
"file": "pdfs/canvas.pdf", "file": "pdfs/canvas.pdf",
"md5": "59510028561daf62e00bf9f6f066b033",
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "intelisa-load", { "id": "intelisa-load",
"file": "pdfs/intelisa.pdf", "file": "pdfs/intelisa.pdf",
"md5": "f3ed5487d1afa34d8b77c0c734a95c79",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "pdfspec-load", { "id": "pdfspec-load",
"file": "pdfs/pdf.pdf", "file": "pdfs/pdf.pdf",
"md5": "dbdb23c939d2be09b43126c3c56060c7",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "shavian-load", { "id": "shavian-load",
"file": "pdfs/shavian.pdf", "file": "pdfs/shavian.pdf",
"md5": "4fabf0a03e82693007435020bc446f9b",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "sizes", { "id": "sizes",
"file": "pdfs/sizes.pdf", "file": "pdfs/sizes.pdf",
"md5": "c101ba7b44aee36048e1ac7b98f302ea",
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "openweb-cover", { "id": "openweb-cover",
"file": "pdfs/openweb_tm-PRINT.pdf", "file": "pdfs/openweb_tm-PRINT.pdf",
"md5": "53f611dfc19ddfb50554c21c4af465c0",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "plusminus", { "id": "plusminus",
"file": "pdfs/Test-plusminus.pdf", "file": "pdfs/Test-plusminus.pdf",
"md5": "1ec7ade5b95ac9aaba3a618af28d34c7",
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "openoffice-pdf", { "id": "openoffice-pdf",
"file": "pdfs/DiwanProfile.pdf", "file": "pdfs/DiwanProfile.pdf",
"md5": "55d0c6a1a6d26c9ec9dcecaa7a471e0e",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "openofficecidtruetype-pdf", { "id": "openofficecidtruetype-pdf",
"file": "pdfs/arial_unicode_en_cidfont.pdf", "file": "pdfs/arial_unicode_en_cidfont.pdf",
"md5": "03591cdf20214fb0b2dd5e5c3dd32d8c",
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "openofficearabiccidtruetype-pdf", { "id": "openofficearabiccidtruetype-pdf",
"file": "pdfs/arial_unicode_ab_cidfont.pdf", "file": "pdfs/arial_unicode_ab_cidfont.pdf",
"md5": "35090fa7d29e7196ae3421812e554988",
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "arabiccidtruetype-pdf", { "id": "arabiccidtruetype-pdf",
"file": "pdfs/ArabicCIDTrueType.pdf", "file": "pdfs/ArabicCIDTrueType.pdf",
"md5": "d66dbd18bdb572d3ac8b88b32de2ece6",
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "complexttffont-pdf", { "id": "complexttffont-pdf",
"file": "pdfs/complex_ttf_font.pdf", "file": "pdfs/complex_ttf_font.pdf",
"md5": "76de93f9116b01b693bf8583b3e76d91",
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "thuluthfont-pdf", { "id": "thuluthfont-pdf",
"file": "pdfs/ThuluthFeatures.pdf", "file": "pdfs/ThuluthFeatures.pdf",
"md5": "b7e18bf7a3d6a9c82aefa12d721072fc",
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "wnv_chinese-pdf", { "id": "wnv_chinese-pdf",
"file": "pdfs/wnv_chinese.pdf", "file": "pdfs/wnv_chinese.pdf",
"md5": "db682638e68391125e8982d3c984841e",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "i9-pdf", { "id": "i9-pdf",
"file": "pdfs/i9.pdf", "file": "pdfs/i9.pdf",
"md5": "ba7cd54fdff083bb389295bc0415f6c5",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "hmm-pdf", { "id": "hmm-pdf",
"file": "pdfs/hmm.pdf", "file": "pdfs/hmm.pdf",
"md5": "e08467e60101ee5f4a59716e86db6dc9",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "rotation", { "id": "rotation",
"file": "pdfs/rotation.pdf", "file": "pdfs/rotation.pdf",
"md5": "4fb25ada00ce7528569d9791c14decf5",
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "ecma262-pdf", { "id": "ecma262-pdf",
"file": "pdfs/ecma262.pdf", "file": "pdfs/ecma262.pdf",
"md5": "763ead98f535578842891e5574e0af0f",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "jai-pdf", { "id": "jai-pdf",
"file": "pdfs/jai.pdf", "file": "pdfs/jai.pdf",
"md5": "1f5dd128c3757420a881a155f2f8ace3",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "cable", { "id": "cable",
"file": "pdfs/cable.pdf", "file": "pdfs/cable.pdf",
"md5": "09a41b9a759d60c698228224ab85b46d",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "pdkids", { "id": "pdkids",
"file": "pdfs/pdkids.pdf", "file": "pdfs/pdkids.pdf",
"md5": "278982bf016dbe46d2066f9245d9b3e6",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "artofwar", { "id": "artofwar",
"file": "pdfs/artofwar.pdf", "file": "pdfs/artofwar.pdf",
"md5": "7bdd51c327b74f1f7abdd90eedb2f912",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "wdsg_fitc", { "id": "wdsg_fitc",
"file": "pdfs/wdsg_fitc.pdf", "file": "pdfs/wdsg_fitc.pdf",
"md5": "5bb1c2b83705d4cdfc43197ee74f07f9",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "unix01", { "id": "unix01",
"file": "pdfs/unix01.pdf", "file": "pdfs/unix01.pdf",
"md5": "2742999f0bf9b9c035dbb0736096e220",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "fit11-talk", { "id": "fit11-talk",
"file": "pdfs/fit11-talk.pdf", "file": "pdfs/fit11-talk.pdf",
"md5": "eb7b224107205db4fea9f7df0185f77d",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"skipPages": [12,31], "skipPages": [12,31],
@ -153,48 +180,56 @@
}, },
{ "id": "fips197", { "id": "fips197",
"file": "pdfs/fips197.pdf", "file": "pdfs/fips197.pdf",
"md5": "374800cf78ce4b4abd02cd10a856b57f",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "txt2pdf", { "id": "txt2pdf",
"file": "pdfs/txt2pdf.pdf", "file": "pdfs/txt2pdf.pdf",
"md5": "02cefa0f5e8d96313bb05163b2f88c8c",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "f1040", { "id": "f1040",
"file": "pdfs/f1040.pdf", "file": "pdfs/f1040.pdf",
"md5": "7323b50c6d28d959b8b4b92c469b2469",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "hudsonsurvey", { "id": "hudsonsurvey",
"file": "pdfs/hudsonsurvey.pdf", "file": "pdfs/hudsonsurvey.pdf",
"md5": "bf0e6576a7b6c2fe7485bce1b78e006f",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "extgstate", { "id": "extgstate",
"file": "pdfs/extgstate.pdf", "file": "pdfs/extgstate.pdf",
"md5": "001bb4ec04463a01d93aad748361f049",
"link": false, "link": false,
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "usmanm-bad", { "id": "usmanm-bad",
"file": "pdfs/usmanm-bad.pdf", "file": "pdfs/usmanm-bad.pdf",
"md5": "38afb822433aaf07fc8f54807cd4f61a",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "vesta-bad", { "id": "vesta-bad",
"file": "pdfs/vesta.pdf", "file": "pdfs/vesta.pdf",
"md5": "0afebc109b7c17b95619ea3fab5eafe6",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "load" "type": "load"
}, },
{ "id": "ibwa-bad", { "id": "ibwa-bad",
"file": "pdfs/ibwa-bad.pdf", "file": "pdfs/ibwa-bad.pdf",
"md5": "6ca059d32b74ac2688ae06f727fee755",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"skipPages": [ 16 ], "skipPages": [ 16 ],
@ -202,18 +237,34 @@
}, },
{ "id": "tcpdf_033", { "id": "tcpdf_033",
"file": "pdfs/tcpdf_033.pdf", "file": "pdfs/tcpdf_033.pdf",
"md5": "861294df58d185aae80919173f2732ff",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "pal-o47", { "id": "pal-o47",
"file": "pdfs/pal-o47.pdf", "file": "pdfs/pal-o47.pdf",
"md5": "81ae15e539e89f0f0b41169d923b611b",
"link": true, "link": true,
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
}, },
{ "id": "simpletype3font", { "id": "simpletype3font",
"file": "pdfs/simpletype3font.pdf", "file": "pdfs/simpletype3font.pdf",
"md5": "b374c7543920840c61999e9e86939f99",
"link": false,
"rounds": 1,
"type": "eq"
},
{ "id": "close-path-bug",
"file": "pdfs/close-path-bug.pdf",
"md5": "48dd17ef58393857d2d038d33699cac5",
"rounds": 1,
"type": "eq"
},
{ "id": "alphatrans",
"file": "pdfs/alphatrans.pdf",
"md5": "5ca2d3da0c5f20b3a5a14e895ad24b65",
"link": false, "link": false,
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"

View File

@ -3,14 +3,29 @@
<head> <head>
<title>pdf.js test slave</title> <title>pdf.js test slave</title>
<style type="text/css"></style> <style type="text/css"></style>
<script type="text/javascript" src="/pdf.js"></script> <script type="text/javascript" src="/src/core.js"></script>
<script type="text/javascript" src="/fonts.js"></script> <script type="text/javascript" src="/src/util.js"></script>
<script type="text/javascript" src="/crypto.js"></script> <script type="text/javascript" src="/src/canvas.js"></script>
<script type="text/javascript" src="/glyphlist.js"></script> <script type="text/javascript" src="/src/obj.js"></script>
<script type="text/javascript" src="/metrics.js"></script> <script type="text/javascript" src="/src/function.js"></script>
<script type="text/javascript" src="/charsets.js"></script> <script type="text/javascript" src="/src/charsets.js"></script>
<script type="text/javascript" src="/cidmaps.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="driver.js"></script> <script type="text/javascript" src="driver.js"></script>
<script type="text/javascript">
PDFJS.workerSrc = '/src/worker_loader.js';
</script>
</head> </head>
<body> <body>

1
web/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
viewer-production.html

View File

@ -83,6 +83,21 @@
}; };
})(); })();
// Object.keys() ?
(function checkObjectKeysCompatibility() {
if (typeof Object.keys !== 'undefined')
return;
Object.keys = function objectKeys(obj) {
var result = [];
for (var i in obj) {
if (obj.hasOwnProperty(i))
result.push(i);
}
return result;
};
})();
// No XMLHttpRequest.response ? // No XMLHttpRequest.response ?
(function checkXMLHttpRequestResponseCompatibility() { (function checkXMLHttpRequestResponseCompatibility() {
var xhrPrototype = XMLHttpRequest.prototype; var xhrPrototype = XMLHttpRequest.prototype;

View File

@ -3,7 +3,7 @@
<head> <head>
<meta charset='utf-8'> <meta charset='utf-8'>
<title>andreasgal/pdf.js @ GitHub</title> <title>mozilla/pdf.js @ GitHub</title>
<style type="text/css"> <style type="text/css">
body { body {
@ -31,18 +31,18 @@
</head> </head>
<body> <body>
<a href="http://github.com/andreasgal/pdf.js"><img style="position: absolute; top: 0; right: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a> <a href="http://github.com/mozilla/pdf.js"><img style="position: absolute; top: 0; right: 0; border: 0;" src="http://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub" /></a>
<div id="container"> <div id="container">
<div class="download"> <div class="download">
<a href="http://github.com/andreasgal/pdf.js/zipball/master"> <a href="http://github.com/mozilla/pdf.js/zipball/master">
<img border="0" width="90" src="http://github.com/images/modules/download/zip.png"></a> <img border="0" width="90" src="http://github.com/images/modules/download/zip.png"></a>
<a href="http://github.com/andreasgal/pdf.js/tarball/master"> <a href="http://github.com/mozilla/pdf.js/tarball/master">
<img border="0" width="90" src="http://github.com/images/modules/download/tar.png"></a> <img border="0" width="90" src="http://github.com/images/modules/download/tar.png"></a>
</div> </div>
<h1><a href="http://github.com/andreasgal/pdf.js">pdf.js</a> <h1><a href="http://github.com/mozilla/pdf.js">pdf.js</a>
<span class="small">by <a href="http://github.com/andreasgal">andreasgal</a></span></h1> <span class="small">by <a href="http://github.com/andreasgal">andreasgal</a></span></h1>
<div class="description"> <div class="description">
@ -69,16 +69,16 @@
<h2>Download</h2> <h2>Download</h2>
<p> <p>
You can download this project in either You can download this project in either
<a href="http://github.com/andreasgal/pdf.js/zipball/master">zip</a> or <a href="http://github.com/mozilla/pdf.js/zipball/master">zip</a> or
<a href="http://github.com/andreasgal/pdf.js/tarball/master">tar</a> formats. <a href="http://github.com/mozilla/pdf.js/tarball/master">tar</a> formats.
</p> </p>
<p>You can also clone the project with <a href="http://git-scm.com">Git</a> <p>You can also clone the project with <a href="http://git-scm.com">Git</a>
by running: by running:
<pre>$ git clone git://github.com/andreasgal/pdf.js</pre> <pre>$ git clone git://github.com/mozilla/pdf.js</pre>
</p> </p>
<div class="footer"> <div class="footer">
get the source code on GitHub : <a href="http://github.com/andreasgal/pdf.js">andreasgal/pdf.js</a> get the source code on GitHub : <a href="http://github.com/mozilla/pdf.js">mozilla/pdf.js</a>
</div> </div>
</div> </div>

6
web/viewer-snippet.html Normal file
View File

@ -0,0 +1,6 @@
<!-- This snippet is used in production, see Makefile -->
<script type="text/javascript" src="../build/pdf.js"></script>
<script type="text/javascript">
// This specifies the location of the pdf.js file.
PDFJS.workerSrc = "../build/pdf.js";
</script>

View File

@ -18,11 +18,11 @@ body {
background: -moz-linear-gradient(center bottom, #eee 0%, #fff 100%); background: -moz-linear-gradient(center bottom, #eee 0%, #fff 100%);
background: -webkit-gradient(linear, left bottom, left top, color-stop(0.0, #ddd), color-stop(1.0, #fff)); background: -webkit-gradient(linear, left bottom, left top, color-stop(0.0, #ddd), color-stop(1.0, #fff));
border-bottom: 1px solid #666; border-bottom: 1px solid #666;
padding: 4px; padding: 3px;
position: fixed; position: fixed;
left: 0px; left: 0px;
top: 0px; top: 0px;
height: 40px; height: 24px;
width: 100%; width: 100%;
z-index: 1; z-index: 1;
white-space:nowrap; white-space:nowrap;
@ -33,22 +33,23 @@ body {
display: inline; display: inline;
border-left: 1px solid #d3d3d3; border-left: 1px solid #d3d3d3;
border-right: 1px solid #fff; border-right: 1px solid #fff;
height: 32px; height: 16px;
width:0px; width:0px;
margin: 4px; margin: 4px;
} }
#controls > a > img { #controls > a > img {
margin: 2px; margin: 4px;
height: 16px;
} }
#controls > button { #controls > button {
line-height: 32px; line-height: 16px;
} }
#controls > button > img { #controls > button > img {
width: 32px; width: 16px;
height: 32px; height: 16px;
} }
#controls > button[disabled] > img { #controls > button[disabled] > img {
@ -60,7 +61,7 @@ body {
} }
#fileInput { #fileInput {
line-height: 32px; line-height: 16px;
} }
span#info { span#info {
@ -215,6 +216,7 @@ canvas {
box-shadow: 0px 4px 10px #000; box-shadow: 0px 4px 10px #000;
-moz-box-shadow: 0px 4px 10px #000; -moz-box-shadow: 0px 4px 10px #000;
-webkit-box-shadow: 0px 4px 10px #000; -webkit-box-shadow: 0px 4px 10px #000;
background-color: white;
} }
.page > a { .page > a {

View File

@ -5,25 +5,41 @@
<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_INCLUDE_BUILD -->
<script type="text/javascript" src="../src/core.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/util.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/canvas.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/obj.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/function.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/charsets.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/cidmaps.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/colorspace.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/crypto.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/evaluator.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/fonts.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/glyphlist.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/image.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/metrics.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/parser.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../src/pattern.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">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>
<script type="text/javascript" src="../pdf.js"></script>
<script type="text/javascript" src="../fonts.js"></script>
<script type="text/javascript" src="../crypto.js"></script>
<script type="text/javascript" src="../glyphlist.js"></script>
<script type="text/javascript" src="../metrics.js"></script>
<script type="text/javascript" src="../charsets.js"></script>
<script type="text/javascript" src="../cidmaps.js"></script>
</head> </head>
<body> <body>
<div id="controls"> <div id="controls">
<button id="previous" onclick="PDFView.page--;" oncontextmenu="return false;"> <button id="previous" onclick="PDFView.page--;" oncontextmenu="return false;">
<img src="images/go-up.svg" align="top" height="32"/> <img src="images/go-up.svg" align="top" height="16"/>
Previous Previous
</button> </button>
<button id="next" onclick="PDFView.page++;" oncontextmenu="return false;"> <button id="next" onclick="PDFView.page++;" oncontextmenu="return false;">
<img src="images/go-down.svg" align="top" height="32"/> <img src="images/go-down.svg" align="top" height="16"/>
Next Next
</button> </button>
@ -37,10 +53,10 @@
<div class="separator"></div> <div class="separator"></div>
<button id="zoomOut" title="Zoom Out" onclick="PDFView.zoomOut();" oncontextmenu="return false;"> <button id="zoomOut" title="Zoom Out" onclick="PDFView.zoomOut();" oncontextmenu="return false;">
<img src="images/zoom-out.svg" align="top" height="32"/> <img src="images/zoom-out.svg" align="top" height="16"/>
</button> </button>
<button id="zoomIn" title="Zoom In" onclick="PDFView.zoomIn();" oncontextmenu="return false;"> <button id="zoomIn" title="Zoom In" onclick="PDFView.zoomIn();" oncontextmenu="return false;">
<img src="images/zoom-in.svg" align="top" height="32"/> <img src="images/zoom-in.svg" align="top" height="16"/>
</button> </button>
<div class="separator"></div> <div class="separator"></div>
@ -60,12 +76,12 @@
<div class="separator"></div> <div class="separator"></div>
<button id="print" onclick="window.print();" oncontextmenu="return false;"> <button id="print" onclick="window.print();" oncontextmenu="return false;">
<img src="images/document-print.svg" align="top" height="32"/> <img src="images/document-print.svg" align="top" height="16"/>
Print Print
</button> </button>
<button id="download" title="Download" onclick="PDFView.download();" oncontextmenu="return false;"> <button id="download" title="Download" onclick="PDFView.download();" oncontextmenu="return false;">
<img src="images/download.svg" align="top" height="32"/> <img src="images/download.svg" align="top" height="16"/>
Download Download
</button> </button>
@ -75,8 +91,8 @@
<div class="separator"></div> <div class="separator"></div>
<a href="#" id="viewBookmark" title="Current View (bookmark or copy the location)"> <a href="#" id="viewBookmark" title="Bookmark (or copy) current location">
<img src="images/bookmark.svg" alt="Bookmark" align="top" height="32"/> <img src="images/bookmark.svg" alt="Bookmark" align="top" height="16"/>
</a> </a>
<span id="info">--</span> <span id="info">--</span>
@ -92,15 +108,15 @@
</div> </div>
<div id="sidebarControls"> <div id="sidebarControls">
<button id="thumbsSwitch" title="Show Thumbnails" onclick="PDFView.switchSidebarView('thumbs')" data-selected> <button id="thumbsSwitch" title="Show Thumbnails" onclick="PDFView.switchSidebarView('thumbs')" data-selected>
<img src="images/nav-thumbs.svg" align="top" height="32" alt="Thumbs" /> <img src="images/nav-thumbs.svg" align="top" height="16" alt="Thumbs" />
</button> </button>
<button id="outlineSwitch" title="Show Document Outline" onclick="PDFView.switchSidebarView('outline')" disabled> <button id="outlineSwitch" title="Show Document Outline" onclick="PDFView.switchSidebarView('outline')" disabled>
<img src="images/nav-outline.svg" align="top" height="32" alt="Document Outline" /> <img src="images/nav-outline.svg" align="top" height="16" alt="Document Outline" />
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div id="loading">Loading... 0%</div> <div id="loading">Loading... 0%</div>
<div id="viewer"></div> <div id="viewer"></div>
</body> </body>

View File

@ -38,10 +38,10 @@ var PDFView = {
var pages = this.pages; var pages = this.pages;
for (var i = 0; i < pages.length; i++) for (var i = 0; i < pages.length; i++)
pages[i].update(val * kCssUnits); pages[i].update(val * kCssUnits);
this.currentScale = val;
this.pages[this.page - 1].scrollIntoView(); if (this.currentScale != val)
this.pages[this.page - 1].draw(); this.pages[this.page - 1].scrollIntoView();
this.currentScale = val;
var event = document.createEvent('UIEvents'); var event = document.createEvent('UIEvents');
event.initUIEvent('scalechange', false, false, window, 0); event.initUIEvent('scalechange', false, false, window, 0);
@ -107,6 +107,10 @@ var PDFView = {
if (updateViewarea.inProgress) if (updateViewarea.inProgress)
return; return;
// Avoid scrolling the first page during loading
if (this.loading && val == 1)
return;
pages[val - 1].scrollIntoView(); pages[val - 1].scrollIntoView();
}, },
@ -117,17 +121,20 @@ var PDFView = {
open: function pdfViewOpen(url, scale) { open: function pdfViewOpen(url, scale) {
document.title = this.url = url; document.title = this.url = url;
getPdf( var self = this;
PDFJS.getPdf(
{ {
url: url, url: url,
progress: function getPdfProgress(evt) { progress: function getPdfProgress(evt) {
if (evt.lengthComputable) if (evt.lengthComputable)
PDFView.progress(evt.loaded / evt.total); self.progress(evt.loaded / evt.total);
}, },
error: PDFView.error error: self.error
}, },
function getPdfLoad(data) { function getPdfLoad(data) {
PDFView.load(data, scale); self.loading = true;
self.load(data, scale);
self.loading = false;
}); });
}, },
@ -202,7 +209,7 @@ var PDFView = {
while (container.hasChildNodes()) while (container.hasChildNodes())
container.removeChild(container.lastChild); container.removeChild(container.lastChild);
var pdf = new PDFDoc(data); var pdf = new PDFJS.PDFDoc(data);
var pagesCount = pdf.numPages; var pagesCount = pdf.numPages;
document.getElementById('numPages').innerHTML = pagesCount; document.getElementById('numPages').innerHTML = pagesCount;
document.getElementById('pageNumber').max = pagesCount; document.getElementById('pageNumber').max = pagesCount;
@ -267,7 +274,7 @@ var PDFView = {
var currentPage = this.pages[pageNumber - 1]; var currentPage = this.pages[pageNumber - 1];
currentPage.scrollIntoView(dest); currentPage.scrollIntoView(dest);
} else } else
this.page = page; // simple page this.page = params.page; // simple page
return; return;
} }
} else if (/^\d+$/.test(hash)) // page number } else if (/^\d+$/.test(hash)) // page number
@ -466,11 +473,11 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
var canvas = document.createElement('canvas'); var canvas = document.createElement('canvas');
canvas.id = 'page' + this.id; canvas.id = 'page' + this.id;
canvas.mozOpaque = true; canvas.mozOpaque = true;
div.appendChild(canvas);
var scale = this.scale; var scale = this.scale;
canvas.width = pageWidth * scale; canvas.width = pageWidth * scale;
canvas.height = pageHeight * scale; canvas.height = pageHeight * scale;
div.appendChild(canvas);
var ctx = canvas.getContext('2d'); var ctx = canvas.getContext('2d');
ctx.save(); ctx.save();
@ -598,6 +605,10 @@ window.addEventListener('load', function webViewerLoad(evt) {
document.getElementById('fileInput').value = null; document.getElementById('fileInput').value = null;
}, true); }, true);
window.addEventListener('unload', function webViewerUnload(evt) {
window.scrollTo(0, 0);
}, true);
function updateViewarea() { function updateViewarea() {
var visiblePages = PDFView.getVisiblePages(); var visiblePages = PDFView.getVisiblePages();
for (var i = 0; i < visiblePages.length; i++) { for (var i = 0; i < visiblePages.length; i++) {

View File

@ -1,45 +0,0 @@
<html>
<head>
<title>Simple pdf.js page worker viewer</title>
<script type="text/javascript" src="../fonts.js"></script>
<script type="text/javascript" src="../glyphlist.js"></script>
<script type="text/javascript" src="../pdf.js"></script>
<script type="text/javascript" src="../worker/client.js"></script>
<script>
var pdfDoc;
window.onload = function webViewerWorkerOnload() {
window.canvas = document.getElementById("canvas");
window.ctx = canvas.getContext("2d");
pdfDoc = new WorkerPDFDoc(window.canvas);
pdfDoc.onChangePage = function webViewerWorkerOnChangePage(numPage) {
document.getElementById("pageNumber").value = numPage;
}
pdfDoc.open("compressed.tracemonkey-pldi-09.pdf", function webViewerWorkerOpen() {
document.getElementById("numPages").innerHTML = "/" + pdfDoc.numPages;
})
}
</script>
<link rel="stylesheet" href="viewer.css"></link>
</head>
<body>
<div id="controls">
<input type="file" style="float: right; margin: auto 32px;" onChange="load(this.value.toString());"></input>
<!-- This only opens supported PDFs from the source path...
-- Can we use JSONP to overcome the same-origin restrictions? -->
<button onclick="pdfDoc.prevPage();">Previous</button>
<button onclick="pdfDoc.nextPage();">Next</button>
<input type="text" id="pageNumber" onchange="pdfDoc.showPage(this.value);"
value="1" size="4"></input>
<span id="numPages">--</span>
<span id="info"></span>
</div>
<div id="viewer">
<canvas id="canvas"></canvas>
</div>
</body>
</html>

View File

@ -1,252 +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';
var JpegStreamProxyCounter = 0;
// WebWorker Proxy for JpegStream.
var JpegStreamProxy = (function() {
function constructor(bytes, dict) {
this.id = JpegStreamProxyCounter++;
this.dict = dict;
// Tell the main thread to create an image.
postMessage({
action: 'jpeg_stream',
data: {
id: this.id,
raw: bytesToString(bytes)
}
});
}
constructor.prototype = {
getImage: function() {
return this;
},
getChar: function() {
error('internal error: getChar is not valid on JpegStream');
}
};
return constructor;
})();
// Really simple GradientProxy. There is currently only one active gradient at
// the time, meaning you can't create a gradient, create a second one and then
// use the first one again. As this isn't used in pdf.js right now, it's okay.
function GradientProxy(cmdQueue, x0, y0, x1, y1) {
cmdQueue.push(['$createLinearGradient', [x0, y0, x1, y1]]);
this.addColorStop = function(i, rgba) {
cmdQueue.push(['$addColorStop', [i, rgba]]);
};
}
// Really simple PatternProxy.
var patternProxyCounter = 0;
function PatternProxy(cmdQueue, object, kind) {
this.id = patternProxyCounter++;
if (!(object instanceof CanvasProxy)) {
throw 'unkown type to createPattern';
}
// Flush the object here to ensure it's available on the main thread.
// TODO: Make some kind of dependency management, such that the object
// gets flushed only if needed.
object.flush();
cmdQueue.push(['$createPatternFromCanvas', [this.id, object.id, kind]]);
}
var canvasProxyCounter = 0;
function CanvasProxy(width, height) {
this.id = canvasProxyCounter++;
// The `stack` holds the rendering calls and gets flushed to the main thead.
var cmdQueue = this.cmdQueue = [];
// Dummy context that gets exposed.
var ctx = {};
this.getContext = function(type) {
if (type != '2d') {
throw 'CanvasProxy can only provide a 2d context.';
}
return ctx;
};
// Expose only the minimum of the canvas object - there is no dom to do
// more here.
this.width = width;
this.height = height;
ctx.canvas = this;
// Setup function calls to `ctx`.
var ctxFunc = [
'createRadialGradient',
'arcTo',
'arc',
'fillText',
'strokeText',
'createImageData',
'drawWindow',
'save',
'restore',
'scale',
'rotate',
'translate',
'transform',
'setTransform',
'clearRect',
'fillRect',
'strokeRect',
'beginPath',
'closePath',
'moveTo',
'lineTo',
'quadraticCurveTo',
'bezierCurveTo',
'rect',
'fill',
'stroke',
'clip',
'measureText',
'isPointInPath',
// These functions are necessary to track the rendering currentX state.
// The exact values can be computed on the main thread only, as the
// worker has no idea about text width.
'$setCurrentX',
'$addCurrentX',
'$saveCurrentX',
'$restoreCurrentX',
'$showText',
'$setFont'
];
function buildFuncCall(name) {
return function() {
// console.log("funcCall", name)
cmdQueue.push([name, Array.prototype.slice.call(arguments)]);
};
}
var name;
for (var i = 0; i < ctxFunc.length; i++) {
name = ctxFunc[i];
ctx[name] = buildFuncCall(name);
}
// Some function calls that need more work.
ctx.createPattern = function(object, kind) {
return new PatternProxy(cmdQueue, object, kind);
};
ctx.createLinearGradient = function(x0, y0, x1, y1) {
return new GradientProxy(cmdQueue, x0, y0, x1, y1);
};
ctx.getImageData = function(x, y, w, h) {
return {
width: w,
height: h,
data: Uint8ClampedArray(w * h * 4)
};
};
ctx.putImageData = function(data, x, y, width, height) {
cmdQueue.push(['$putImageData', [data, x, y, width, height]]);
};
ctx.drawImage = function(image, x, y, width, height,
sx, sy, swidth, sheight) {
if (image instanceof CanvasProxy) {
// Send the image/CanvasProxy to the main thread.
image.flush();
cmdQueue.push(['$drawCanvas', [image.id, x, y, sx, sy, swidth, sheight]]);
} else if (image instanceof JpegStreamProxy) {
cmdQueue.push(['$drawImage', [image.id, x, y, sx, sy, swidth, sheight]]);
} else {
throw 'unkown type to drawImage';
}
};
// Setup property access to `ctx`.
var ctxProp = {
// "canvas"
'globalAlpha': '1',
'globalCompositeOperation': 'source-over',
'strokeStyle': '#000000',
'fillStyle': '#000000',
'lineWidth': '1',
'lineCap': 'butt',
'lineJoin': 'miter',
'miterLimit': '10',
'shadowOffsetX': '0',
'shadowOffsetY': '0',
'shadowBlur': '0',
'shadowColor': 'rgba(0, 0, 0, 0)',
'font': '10px sans-serif',
'textAlign': 'start',
'textBaseline': 'alphabetic',
'mozTextStyle': '10px sans-serif',
'mozImageSmoothingEnabled': 'true'
};
function buildGetter(name) {
return function() {
return ctx['$' + name];
};
}
function buildSetter(name) {
return function(value) {
cmdQueue.push(['$', name, value]);
return (ctx['$' + name] = value);
};
}
// Setting the value to `stroke|fillStyle` needs special handling, as it
// might gets an gradient/pattern.
function buildSetterStyle(name) {
return function(value) {
if (value instanceof GradientProxy) {
cmdQueue.push(['$' + name + 'Gradient']);
} else if (value instanceof PatternProxy) {
cmdQueue.push(['$' + name + 'Pattern', [value.id]]);
} else {
cmdQueue.push(['$', name, value]);
return (ctx['$' + name] = value);
}
};
}
for (var name in ctxProp) {
ctx['$' + name] = ctxProp[name];
ctx.__defineGetter__(name, buildGetter(name));
// Special treatment for `fillStyle` and `strokeStyle`: The passed style
// might be a gradient. Need to check for that.
if (name == 'fillStyle' || name == 'strokeStyle') {
ctx.__defineSetter__(name, buildSetterStyle(name));
} else {
ctx.__defineSetter__(name, buildSetter(name));
}
}
}
/**
* Sends the current cmdQueue of the CanvasProxy over to the main thread and
* resets the cmdQueue.
*/
CanvasProxy.prototype.flush = function() {
postMessage({
action: 'canvas_proxy_cmd_queue',
data: {
id: this.id,
cmdQueue: this.cmdQueue,
width: this.width,
height: this.height
}
});
this.cmdQueue.length = 0;
};

View File

@ -1,423 +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';
var consoleUtils = (function() {
var consoleTimer = {};
var obj = {};
obj.time = function(name) {
consoleTimer[name] = Date.now();
};
obj.timeEnd = function(name) {
var time = consoleTimer[name];
if (time == null) {
throw 'Unkown timer name ' + name;
}
console.log('Timer:', name, Date.now() - time);
};
return obj;
})();
function FontWorker() {
this.worker = new Worker('../worker/font.js');
this.fontsWaiting = 0;
this.fontsWaitingCallbacks = [];
// Listen to the WebWorker for data and call actionHandler on it.
this.worker.onmessage = function(event) {
var data = event.data;
var actionHandler = this.actionHandler;
if (data.action in actionHandler) {
actionHandler[data.action].call(this, data.data);
} else {
throw 'Unkown action from worker: ' + data.action;
}
}.bind(this);
this.$handleFontLoadedCallback = this.handleFontLoadedCallback.bind(this);
}
FontWorker.prototype = {
handleFontLoadedCallback: function() {
// Decrease the number of fonts wainting to be loaded.
this.fontsWaiting--;
// If all fonts are available now, then call all the callbacks.
if (this.fontsWaiting == 0) {
var callbacks = this.fontsWaitingCallbacks;
for (var i = 0; i < callbacks.length; i++) {
callbacks[i]();
}
this.fontsWaitingCallbacks.length = 0;
}
},
actionHandler: {
'log': function(data) {
console.log.apply(console, data);
},
'fonts': function(data) {
// console.log("got processed fonts from worker", Object.keys(data));
for (var name in data) {
// Update the encoding property.
var font = Fonts.lookup(name);
font.properties = {
encoding: data[name].encoding
};
// Call `Font.prototype.bindDOM` to make the font get loaded
// on the page.
Font.prototype.bindDOM.call(
font,
data[name].str,
// IsLoadedCallback.
this.$handleFontLoadedCallback
);
}
}
},
ensureFonts: function(data, callback) {
var font;
var notLoaded = [];
for (var i = 0; i < data.length; i++) {
font = data[i];
if (Fonts[font.name]) {
continue;
}
// Register the font but don't pass in any real data. The idea is to
// store as less data as possible to reduce memory usage.
Fonts.registerFont(font.name, Object.create(null), Object.create(null));
// Mark this font to be handled later.
notLoaded.push(font);
// Increate the number of fonts to wait for.
this.fontsWaiting++;
}
consoleUtils.time('ensureFonts');
// If there are fonts, that need to get loaded, tell the FontWorker to get
// started and push the callback on the waiting-callback-stack.
if (notLoaded.length != 0) {
console.log('fonts -> FontWorker');
// Send the worker the fonts to work on.
this.worker.postMessage({
action: 'fonts',
data: notLoaded
});
if (callback) {
this.fontsWaitingCallbacks.push(callback);
}
}
// All fonts are present? Well, then just call the callback if there is one.
else {
if (callback) {
callback();
}
}
}
};
function WorkerPDFDoc(canvas) {
var timer = null;
this.ctx = canvas.getContext('2d');
this.canvas = canvas;
this.worker = new Worker('../worker/pdf.js');
this.fontWorker = new FontWorker();
this.waitingForFonts = false;
this.waitingForFontsCallback = [];
this.numPage = 1;
this.numPages = null;
var imagesList = {};
var canvasList = {
0: canvas
};
var patternList = {};
var gradient;
var currentX = 0;
var currentXStack = [];
var ctxSpecial = {
'$setCurrentX': function(value) {
currentX = value;
},
'$addCurrentX': function(value) {
currentX += value;
},
'$saveCurrentX': function() {
currentXStack.push(currentX);
},
'$restoreCurrentX': function() {
currentX = currentXStack.pop();
},
'$showText': function(y, text) {
text = Fonts.charsToUnicode(text);
this.translate(currentX, -1 * y);
this.fillText(text, 0, 0);
currentX += this.measureText(text).width;
},
'$putImageData': function(imageData, x, y) {
var imgData = this.getImageData(0, 0,
imageData.width, imageData.height);
// Store the .data property to avaid property lookups.
var imageRealData = imageData.data;
var imgRealData = imgData.data;
// Copy over the imageData.
var len = imageRealData.length;
while (len--)
imgRealData[len] = imageRealData[len];
this.putImageData(imgData, x, y);
},
'$drawImage': function(id, x, y, sx, sy, swidth, sheight) {
var image = imagesList[id];
if (!image) {
throw 'Image not found: ' + id;
}
this.drawImage(image, x, y, image.width, image.height,
sx, sy, swidth, sheight);
},
'$drawCanvas': function(id, x, y, sx, sy, swidth, sheight) {
var canvas = canvasList[id];
if (!canvas) {
throw 'Canvas not found';
}
if (sheight != null) {
this.drawImage(canvas, x, y, canvas.width, canvas.height,
sx, sy, swidth, sheight);
} else {
this.drawImage(canvas, x, y, canvas.width, canvas.height);
}
},
'$createLinearGradient': function(x0, y0, x1, y1) {
gradient = this.createLinearGradient(x0, y0, x1, y1);
},
'$createPatternFromCanvas': function(patternId, canvasId, kind) {
var canvas = canvasList[canvasId];
if (!canvas) {
throw 'Canvas not found';
}
patternList[patternId] = this.createPattern(canvas, kind);
},
'$addColorStop': function(i, rgba) {
gradient.addColorStop(i, rgba);
},
'$fillStyleGradient': function() {
this.fillStyle = gradient;
},
'$fillStylePattern': function(id) {
var pattern = patternList[id];
if (!pattern) {
throw 'Pattern not found';
}
this.fillStyle = pattern;
},
'$strokeStyleGradient': function() {
this.strokeStyle = gradient;
},
'$strokeStylePattern': function(id) {
var pattern = patternList[id];
if (!pattern) {
throw 'Pattern not found';
}
this.strokeStyle = pattern;
},
'$setFont': function(name, size) {
this.font = size + 'px "' + name + '"';
Fonts.setActive(name, size);
}
};
function renderProxyCanvas(canvas, cmdQueue) {
var ctx = canvas.getContext('2d');
var cmdQueueLength = cmdQueue.length;
for (var i = 0; i < cmdQueueLength; i++) {
var opp = cmdQueue[i];
if (opp[0] == '$') {
ctx[opp[1]] = opp[2];
} else if (opp[0] in ctxSpecial) {
ctxSpecial[opp[0]].apply(ctx, opp[1]);
} else {
ctx[opp[0]].apply(ctx, opp[1]);
}
}
}
/**
* Functions to handle data sent by the WebWorker.
*/
var actionHandler = {
'log': function(data) {
console.log.apply(console, data);
},
'pdf_num_pages': function(data) {
this.numPages = parseInt(data, 10);
if (this.loadCallback) {
this.loadCallback();
}
},
'font': function(data) {
var base64 = window.btoa(data.raw);
// Add the @font-face rule to the document
var url = 'url(data:' + data.mimetype + ';base64,' + base64 + ');';
var rule = ("@font-face { font-family:'" + data.fontName +
"';src:" + url + '}');
var styleSheet = document.styleSheets[0];
styleSheet.insertRule(rule, styleSheet.cssRules.length);
// Just adding the font-face to the DOM doesn't make it load. It
// seems it's loaded once Gecko notices it's used. Therefore,
// add a div on the page using the loaded font.
var div = document.createElement('div');
var style = 'font-family:"' + data.fontName +
'";position: absolute;top:-99999;left:-99999;z-index:-99999';
div.setAttribute('style', style);
document.body.appendChild(div);
},
'setup_page': function(data) {
var size = data.split(',');
var canvas = this.canvas, ctx = this.ctx;
canvas.width = parseInt(size[0], 10);
canvas.height = parseInt(size[1], 10);
},
'fonts': function(data) {
this.waitingForFonts = true;
this.fontWorker.ensureFonts(data, function() {
this.waitingForFonts = false;
var callbacks = this.waitingForFontsCallback;
for (var i = 0; i < callbacks.length; i++) {
callbacks[i]();
}
this.waitingForFontsCallback.length = 0;
}.bind(this));
},
'jpeg_stream': function(data) {
var img = new Image();
img.src = 'data:image/jpeg;base64,' + window.btoa(data.raw);
imagesList[data.id] = img;
},
'canvas_proxy_cmd_queue': function(data) {
var id = data.id;
var cmdQueue = data.cmdQueue;
// Check if there is already a canvas with the given id. If not,
// create a new canvas.
if (!canvasList[id]) {
var newCanvas = document.createElement('canvas');
newCanvas.width = data.width;
newCanvas.height = data.height;
canvasList[id] = newCanvas;
}
var renderData = function() {
if (id == 0) {
consoleUtils.time('main canvas rendering');
var ctx = this.ctx;
ctx.save();
ctx.fillStyle = 'rgb(255, 255, 255)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
}
renderProxyCanvas(canvasList[id], cmdQueue);
if (id == 0) {
consoleUtils.timeEnd('main canvas rendering');
consoleUtils.timeEnd('>>> total page display time:');
}
}.bind(this);
if (this.waitingForFonts) {
if (id == 0) {
console.log('want to render, but not all fonts are there', id);
this.waitingForFontsCallback.push(renderData);
} else {
// console.log("assume canvas doesn't have fonts", id);
renderData();
}
} else {
renderData();
}
}
};
// Listen to the WebWorker for data and call actionHandler on it.
this.worker.addEventListener('message', function(event) {
var data = event.data;
if (data.action in actionHandler) {
actionHandler[data.action].call(this, data.data);
} else {
throw 'Unkown action from worker: ' + data.action;
}
}.bind(this));
}
WorkerPDFDoc.prototype = {
open: function(url, callback) {
var req = new XMLHttpRequest();
req.open('GET', url);
req.mozResponseType = req.responseType = 'arraybuffer';
req.expected = (document.URL.indexOf('file:') == 0) ? 0 : 200;
req.onreadystatechange = function() {
if (req.readyState == 4 && req.status == req.expected) {
var data = req.mozResponseArrayBuffer || req.mozResponse ||
req.responseArrayBuffer || req.response;
this.loadCallback = callback;
this.worker.postMessage(data);
this.showPage(this.numPage);
}
}.bind(this);
req.send(null);
},
showPage: function(numPage) {
this.numPage = parseInt(numPage, 10);
console.log('=== start rendering page ' + numPage + ' ===');
consoleUtils.time('>>> total page display time:');
this.worker.postMessage(numPage);
if (this.onChangePage) {
this.onChangePage(numPage);
}
},
nextPage: function() {
if (this.numPage != this.numPages)
this.showPage(++this.numPage);
},
prevPage: function() {
if (this.numPage != 1)
this.showPage(--this.numPage);
}
};

View File

@ -1,28 +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';
var consoleTimer = {};
var console = {
log: function log() {
var args = Array.prototype.slice.call(arguments);
postMessage({
action: 'log',
data: args
});
},
time: function(name) {
consoleTimer[name] = Date.now();
},
timeEnd: function(name) {
var time = consoleTimer[name];
if (time == null) {
throw 'Unkown timer name ' + name;
}
this.log('Timer:', name, Date.now() - time);
}
};

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';
importScripts('console.js');
importScripts('../pdf.js');
importScripts('../fonts.js');
importScripts('../glyphlist.js');
function fontDataToString(font) {
// Doing postMessage on objects make them lose their "shape". This adds the
// "shape" for all required objects agains, such that the encoding works as
// expected.
var fontFileDict = new Dict();
fontFileDict.map = font.file.dict.map;
var fontFile = new Stream(font.file.bytes, font.file.start,
font.file.end - font.file.start, fontFileDict);
font.file = new FlateStream(fontFile);
// This will encode the font.
var fontObj = new Font(font.name, font.file, font.properties);
// Create string that is used for css later.
var str = '';
var data = fontObj.data;
var length = data.length;
for (var j = 0; j < length; j++)
str += String.fromCharCode(data[j]);
return {
str: str,
encoding: font.properties.encoding
};
}
/**
* Functions to handle data sent by the MainThread.
*/
var actionHandler = {
'fonts': function(data) {
var fontData;
var result = {};
for (var i = 0; i < data.length; i++) {
fontData = data[i];
result[fontData.name] = fontDataToString(fontData);
}
postMessage({
action: 'fonts',
data: result
});
}
};
// Listen to the MainThread for data and call actionHandler on it.
addEventListener('message', function(event) {
var data = event.data;
if (data.action in actionHandler) {
actionHandler[data.action].call(this, data.data);
} else {
throw 'Unkown action from worker: ' + data.action;
}
});

View File

@ -1,97 +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';
var consoleTimer = {};
var console = {
log: function log() {
var args = Array.prototype.slice.call(arguments);
postMessage({
action: 'log',
data: args
});
}
};
var consoleUtils = {
time: function(name) {
consoleTimer[name] = Date.now();
},
timeEnd: function(name) {
var time = consoleTimer[name];
if (time == null) {
throw 'Unkown timer name ' + name;
}
console.log('Timer:', name, Date.now() - time);
}
};
//
importScripts('console.js');
importScripts('canvas.js');
importScripts('../pdf.js');
importScripts('../fonts.js');
importScripts('../crypto.js');
importScripts('../glyphlist.js');
// Use the JpegStreamProxy proxy.
JpegStream = JpegStreamProxy;
// Create the WebWorkerProxyCanvas.
var canvas = new CanvasProxy(1224, 1584);
// Listen for messages from the main thread.
var pdfDocument = null;
addEventListener('message', function(event) {
var data = event.data;
// If there is no pdfDocument yet, then the sent data is the PDFDocument.
if (!pdfDocument) {
pdfDocument = new PDFDoc(data);
postMessage({
action: 'pdf_num_pages',
data: pdfDocument.numPages
});
return;
}
// User requested to render a certain page.
else {
consoleUtils.time('compile');
// Let's try to render the first page...
var page = pdfDocument.getPage(parseInt(data, 10));
var pdfToCssUnitsCoef = 96.0 / 72.0;
var pageWidth = (page.mediaBox[2] - page.mediaBox[0]) * pdfToCssUnitsCoef;
var pageHeight = (page.mediaBox[3] - page.mediaBox[1]) * pdfToCssUnitsCoef;
postMessage({
action: 'setup_page',
data: pageWidth + ',' + pageHeight
});
// Set canvas size.
canvas.width = pageWidth;
canvas.height = pageHeight;
// page.compile will collect all fonts for us, once we have loaded them
// we can trigger the actual page rendering with page.display
var fonts = [];
var gfx = new CanvasGraphics(canvas.getContext('2d'), CanvasProxy);
page.compile(gfx, fonts);
consoleUtils.timeEnd('compile');
// Send fonts to the main thread.
consoleUtils.time('fonts');
postMessage({
action: 'fonts',
data: fonts
});
consoleUtils.timeEnd('fonts');
consoleUtils.time('display');
page.display(gfx);
canvas.flush();
consoleUtils.timeEnd('display');
}
});