Merge branch 'master' of git://github.com/mozilla/pdf.js.git into textsearch-1

Conflicts:
	web/viewer.js
This commit is contained in:
notmasteryet 2011-12-30 20:04:08 -06:00
commit 7b479c352c
19 changed files with 1525 additions and 102 deletions

View File

@ -9,6 +9,7 @@
Yury Delendik
Kalervo Kujala
Adil Allawi <@ironymark>
Jakob Miland <saebekassebil@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),

View File

@ -129,7 +129,7 @@ browser-test:
#
# <http://code.google.com/closure/utilities/docs/linter_howto.html>
SRC_DIRS := . src utils web test examples/helloworld extensions/firefox \
extensions/firefox/components extensions/chrome
extensions/firefox/components extensions/chrome test/unit
GJSLINT_FILES = $(foreach DIR,$(SRC_DIRS),$(wildcard $(DIR)/*.js))
lint:
gjslint --nojsdoc $(GJSLINT_FILES)

View File

@ -1,6 +1,6 @@
# pdf.js
## Overview

141
examples/acroforms/forms.js Normal file
View File

@ -0,0 +1,141 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
//
// Basic AcroForms input controls rendering
//
'use strict';
var formFields = {};
function setupForm(div, content, scale) {
function bindInputItem(input, item) {
if (input.name in formFields) {
var value = formFields[input.name];
if (input.type == 'checkbox')
input.checked = value;
else if (!input.type || input.type == 'text')
input.value = value;
}
input.onchange = function pageViewSetupInputOnBlur() {
if (input.type == 'checkbox')
formFields[input.name] = input.checked;
else if (!input.type || input.type == 'text')
formFields[input.name] = input.value;
};
}
function createElementWithStyle(tagName, item) {
var element = document.createElement(tagName);
element.style.left = (item.x * scale) + 'px';
element.style.top = (item.y * scale) + 'px';
element.style.width = Math.ceil(item.width * scale) + 'px';
element.style.height = Math.ceil(item.height * scale) + 'px';
return element;
}
function assignFontStyle(element, item) {
var fontStyles = '';
if ('fontSize' in item)
fontStyles += 'font-size: ' + Math.round(item.fontSize * scale) + 'px;';
switch (item.textAlignment) {
case 0:
fontStyles += 'text-align: left;';
break;
case 1:
fontStyles += 'text-align: center;';
break;
case 2:
fontStyles += 'text-align: right;';
break;
}
element.setAttribute('style', element.getAttribute('style') + fontStyles);
}
var items = content.getAnnotations();
for (var i = 0; i < items.length; i++) {
var item = items[i];
switch (item.type) {
case 'Widget':
if (item.fieldType != 'Tx' && item.fieldType != 'Btn' &&
item.fieldType != 'Ch')
break;
var inputDiv = createElementWithStyle('div', item);
inputDiv.className = 'inputHint';
div.appendChild(inputDiv);
var input;
if (item.fieldType == 'Tx') {
input = createElementWithStyle('input', item);
}
if (item.fieldType == 'Btn') {
input = createElementWithStyle('input', item);
if (item.flags & 32768) {
input.type = 'radio';
// radio button is not supported
} else if (item.flags & 65536) {
input.type = 'button';
// pushbutton is not supported
} else {
input.type = 'checkbox';
}
}
if (item.fieldType == 'Ch') {
input = createElementWithStyle('select', item);
// select box is not supported
}
input.className = 'inputControl';
input.name = item.fullName;
input.title = item.alternativeText;
assignFontStyle(input, item);
bindInputItem(input, item);
div.appendChild(input);
break;
}
}
}
function renderPage(div, pdf, pageNumber, callback) {
var page = pdf.getPage(pageNumber);
var scale = 1.5;
var pageDisplayWidth = page.width * scale;
var pageDisplayHeight = page.height * scale;
var pageDivHolder = document.createElement('div');
pageDivHolder.className = 'pdfpage';
pageDivHolder.style.width = pageDisplayWidth + 'px';
pageDivHolder.style.height = pageDisplayHeight + 'px';
div.appendChild(pageDivHolder);
// Prepare canvas using PDF page dimensions
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
canvas.width = pageDisplayWidth;
canvas.height = pageDisplayHeight;
pageDivHolder.appendChild(canvas);
// Render PDF page into canvas context
page.startRendering(context, callback);
// Prepare and populate form elements layer
var formDiv = document.createElement('div');
pageDivHolder.appendChild(formDiv);
setupForm(formDiv, page, scale);
}
PDFJS.getPdf(pdfWithFormsPath, function getPdfForm(data) {
// Instantiate PDFDoc with PDF data
var pdf = new PDFJS.PDFDoc(data);
// Rendering all pages starting from first
var viewer = document.getElementById('viewer');
var pageNumber = 1;
renderPage(viewer, pdf, pageNumber++, function pageRenderingComplete() {
if (pageNumber > pdf.numPages)
return; // All pages rendered
// Continue rendering of the next page
renderPage(viewer, pdf, pageNumber++, pageRenderingComplete);
});
});

View File

@ -0,0 +1,52 @@
<!doctype html>
<html>
<head>
<!-- In production, only one script (pdf.js) is necessary -->
<!-- In production, change the content of PDFJS.workerSrc below -->
<script type="text/javascript" src="../../src/core.js"></script>
<script type="text/javascript" src="../../src/util.js"></script>
<script type="text/javascript" src="../../src/canvas.js"></script>
<script type="text/javascript" src="../../src/obj.js"></script>
<script type="text/javascript" src="../../src/function.js"></script>
<script type="text/javascript" src="../../src/charsets.js"></script>
<script type="text/javascript" src="../../src/cidmaps.js"></script>
<script type="text/javascript" src="../../src/colorspace.js"></script>
<script type="text/javascript" src="../../src/crypto.js"></script>
<script type="text/javascript" src="../../src/evaluator.js"></script>
<script type="text/javascript" src="../../src/fonts.js"></script>
<script type="text/javascript" src="../../src/glyphlist.js"></script>
<script type="text/javascript" src="../../src/image.js"></script>
<script type="text/javascript" src="../../src/metrics.js"></script>
<script type="text/javascript" src="../../src/parser.js"></script>
<script type="text/javascript" src="../../src/pattern.js"></script>
<script type="text/javascript" src="../../src/stream.js"></script>
<script type="text/javascript" src="../../src/worker.js"></script>
<script type="text/javascript" src="../../external/jpgjs/jpg.js"></script>
<script type="text/javascript">
// 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';
// Specify the PDF with AcroForm here
var pdfWithFormsPath = '../../test/pdfs/f1040.pdf';
</script>
<style>
.pdfpage { position:relative; top: 0; left: 0; border: solid 1px black; margin: 10px; }
.pdfpage > canvas { position: absolute; top: 0; left: 0; }
.pdfpage > div { position: absolute; top: 0; left: 0; }
.inputControl { background: transparent; border: 0px none; position: absolute; margin: auto; }
.inputControl[type='checkbox'] { margin: 0px; }
.inputHint { opacity: 0.2; background: #ccc; position: absolute; }
</style>
<script type="text/javascript" src="forms.js"></script>
</head>
<body>
<div id="viewer"></div>
</body>
</html>

View File

@ -672,6 +672,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
ctx.translate(current.x, current.y);
ctx.scale(textHScale, 1);
ctx.lineWidth /= current.textMatrix[0];
if (textSelection) {
this.save();
@ -708,6 +709,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
} else {
ctx.save();
this.applyTextTransforms();
ctx.lineWidth /= current.textMatrix[0] * fontMatrix[0];
if (textSelection)
text.geom = this.getTextGeometry();

View File

@ -306,46 +306,125 @@ var Page = (function PageClosure() {
}
},
getLinks: function pageGetLinks() {
var links = [];
var annotations = pageGetAnnotations();
var i, n = annotations.length;
for (i = 0; i < n; ++i) {
if (annotations[i].type != 'Link')
continue;
links.push(annotations[i]);
}
return links;
},
getAnnotations: function pageGetAnnotations() {
var xref = this.xref;
function getInheritableProperty(annotation, name) {
var item = annotation;
while (item && !item.has(name)) {
item = xref.fetchIfRef(item.get('Parent'));
}
if (!item)
return null;
return item.get(name);
}
var annotations = xref.fetchIfRef(this.annotations) || [];
var i, n = annotations.length;
var links = [];
var items = [];
for (i = 0; i < n; ++i) {
var annotation = xref.fetch(annotations[i]);
var annotationRef = annotations[i];
var annotation = xref.fetch(annotationRef);
if (!isDict(annotation))
continue;
var subtype = annotation.get('Subtype');
if (!isName(subtype) || subtype.name != 'Link')
if (!isName(subtype))
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');
var item = {};
item.type = subtype.name;
item.x = Math.min(topLeftCorner.x, bottomRightCorner.x);
item.y = Math.min(topLeftCorner.y, bottomRightCorner.y);
item.width = Math.abs(topLeftCorner.x - bottomRightCorner.x);
item.height = Math.abs(topLeftCorner.y - bottomRightCorner.y);
switch (subtype.name) {
case 'Link':
var a = this.xref.fetchIfRef(annotation.get('A'));
if (a) {
switch (a.get('S').name) {
case 'URI':
item.url = a.get('URI');
break;
case 'GoTo':
item.dest = a.get('D');
break;
default:
TODO('other link types');
}
} else if (annotation.has('Dest')) {
// simple destination link
var dest = annotation.get('Dest');
item.dest = isName(dest) ? dest.name : dest;
}
break;
case 'Widget':
var fieldType = getInheritableProperty(annotation, 'FT');
if (!isName(fieldType))
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;
item.fieldType = fieldType.name;
// Building the full field name by collecting the field and
// its ancestors 'T' properties and joining them using '.'.
var fieldName = [];
var namedItem = annotation, ref = annotationRef;
while (namedItem) {
var parentRef = namedItem.get('Parent');
var parent = xref.fetchIfRef(parentRef);
var name = namedItem.get('T');
if (name)
fieldName.unshift(stringToPDFString(name));
else {
// The field name is absent, that means more than one field
// with the same name may exist. Replacing the empty name
// with the '`' plus index in the parent's 'Kids' array.
// This is not in the PDF spec but necessary to id the
// the input controls.
var kids = xref.fetchIfRef(parent.get('Kids'));
var j, jj;
for (j = 0, jj = kids.length; j < jj; j++) {
if (kids[j].num == ref.num && kids[j].gen == ref.gen)
break;
}
fieldName.unshift('`' + j);
}
namedItem = parent;
ref = parentRef;
}
item.fullName = fieldName.join('.');
var alternativeText = stringToPDFString(annotation.get('TU') || '');
item.alternativeText = alternativeText;
var da = getInheritableProperty(annotation, 'DA') || '';
var m = /([\d\.]+)\sTf/.exec(da);
if (m)
item.fontSize = parseFloat(m[1]);
item.textAlignment = getInheritableProperty(annotation, 'Q');
item.flags = getInheritableProperty(annotation, 'Ff') || 0;
break;
case 'Text':
var content = annotation.get('Contents');
var title = annotation.get('T');
item.content = stringToPDFString(content || '');
item.title = stringToPDFString(title || '');
item.name = annotation.get('Name').name;
break;
default:
TODO('unimplemented annotation type: ' + subtype.name);
break;
}
links.push(link);
items.push(item);
}
return links;
return items;
},
startRendering: function pageStartRendering(ctx, callback, textLayer) {
this.ctx = ctx;
@ -384,6 +463,7 @@ var PDFDocModel = (function PDFDocModelClosure() {
assertWellFormed(stream.length > 0, 'stream must have data');
this.stream = stream;
this.setup();
this.acroForm = this.xref.fetchIfRef(this.catalog.catDict.get('AcroForm'));
}
function find(stream, needle, limit, backwards) {
@ -479,6 +559,13 @@ var PDFDocModel = (function PDFDocModelClosure() {
this.startXRef,
this.mainXRefEntriesOffset);
this.catalog = new Catalog(this.xref);
if (this.xref.trailer && this.xref.trailer.has('ID')) {
var fileID = '';
this.xref.trailer.get('ID')[0].split('').forEach(function(el) {
fileID += Number(el.charCodeAt(0)).toString(16);
});
this.fileID = fileID;
}
},
get numPages() {
var linearization = this.linearization;
@ -486,6 +573,22 @@ var PDFDocModel = (function PDFDocModelClosure() {
// shadow the prototype getter
return shadow(this, 'numPages', num);
},
getFingerprint: function pdfDocGetFingerprint() {
if (this.fileID) {
return this.fileID;
} else {
// If we got no fileID, then we generate one,
// from the first 100 bytes of PDF
var data = this.stream.bytes.subarray(0, 100);
var hash = calculateMD5(data, 0, data.length);
var strHash = '';
for (var i = 0, length = hash.length; i < length; i++) {
strHash += Number(hash[i]).toString(16);
}
return strHash;
}
},
getPage: function pdfDocGetPage(n) {
return this.catalog.getPage(n);
}
@ -512,7 +615,7 @@ var PDFDoc = (function PDFDocClosure() {
this.data = data;
this.stream = stream;
this.pdf = new PDFDocModel(stream);
this.fingerprint = this.pdf.getFingerprint();
this.catalog = this.pdf.catalog;
this.objs = new PDFObjects();
@ -531,36 +634,35 @@ var PDFDoc = (function PDFDocClosure() {
throw 'No PDFJS.workerSrc specified';
}
var worker;
try {
worker = new Worker(workerSrc);
} catch (e) {
// Some versions of FF can't create a worker on localhost, see:
// https://bugzilla.mozilla.org/show_bug.cgi?id=683280
globalScope.PDFJS.disableWorker = true;
this.setupFakeWorker();
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 {
globalScope.PDFJS.disableWorker = true;
this.setupFakeWorker();
}
}.bind(this));
var testObj = new Uint8Array(1);
// Some versions of Opera throw a DATA_CLONE_ERR on
// serializing the typed array.
messageHandler.send('test', testObj);
return;
}
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();
} catch (e) {}
}
// Either workers are disabled, not supported or have thrown an exception.
// Thus, we fallback to a faked worker.
globalScope.PDFJS.disableWorker = true;
this.setupFakeWorker();
}
PDFDoc.prototype = {

View File

@ -336,16 +336,550 @@ var PDFFunction = (function PDFFunctionClosure() {
};
},
constructPostScript: function pdfFunctionConstructPostScript() {
return [CONSTRUCT_POSTSCRIPT];
constructPostScript: function pdfFunctionConstructPostScript(fn, dict,
xref) {
var domain = dict.get('Domain');
var range = dict.get('Range');
if (!domain)
error('No domain.');
if (!range)
error('No range.');
var lexer = new PostScriptLexer(fn);
var parser = new PostScriptParser(lexer);
var code = parser.parse();
return [CONSTRUCT_POSTSCRIPT, domain, range, code];
},
constructPostScriptFromIR: function pdfFunctionConstructPostScriptFromIR() {
TODO('unhandled type of function');
return function constructPostScriptFromIRResult() {
return [255, 105, 180];
constructPostScriptFromIR:
function pdfFunctionConstructPostScriptFromIR(IR) {
var domain = IR[1];
var range = IR[2];
var code = IR[3];
var numOutputs = range.length / 2;
var evaluator = new PostScriptEvaluator(code);
// Cache the values for a big speed up, the cache size is limited though
// since the number of possible values can be huge from a PS function.
var cache = new FunctionCache();
return function constructPostScriptFromIRResult(args) {
var initialStack = [];
for (var i = 0, ii = (domain.length / 2); i < ii; ++i) {
initialStack.push(args[i]);
}
var key = initialStack.join('_');
if (cache.has(key))
return cache.get(key);
var stack = evaluator.execute(initialStack);
var transformed = new Array(numOutputs);
for (i = numOutputs - 1; i >= 0; --i) {
var out = stack.pop();
var rangeIndex = 2 * i;
if (out < range[rangeIndex])
out = range[rangeIndex];
else if (out > range[rangeIndex + 1])
out = range[rangeIndex + 1];
transformed[i] = out;
}
cache.set(key, transformed);
return transformed;
};
}
};
})();
var FunctionCache = (function FunctionCacheClosure() {
// Of 10 PDF's with type4 functions the maxium number of distinct values seen
// was 256. This still may need some tweaking in the future though.
var MAX_CACHE_SIZE = 1024;
function FunctionCache() {
this.cache = {};
this.total = 0;
}
FunctionCache.prototype = {
has: function has(key) {
return key in this.cache;
},
get: function get(key) {
return this.cache[key];
},
set: function set(key, value) {
if (this.total < MAX_CACHE_SIZE) {
this.cache[key] = value;
this.total++;
}
}
};
return FunctionCache;
})();
var PostScriptStack = (function PostScriptStackClosure() {
var MAX_STACK_SIZE = 100;
function PostScriptStack(initialStack) {
this.stack = initialStack || [];
}
PostScriptStack.prototype = {
push: function push(value) {
if (this.stack.length >= MAX_STACK_SIZE)
error('PostScript function stack overflow.');
this.stack.push(value);
},
pop: function pop() {
if (this.stack.length <= 0)
error('PostScript function stack underflow.');
return this.stack.pop();
},
copy: function copy(n) {
if (this.stack.length + n >= MAX_STACK_SIZE)
error('PostScript function stack overflow.');
var stack = this.stack;
for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++)
stack.push(stack[i]);
},
index: function index(n) {
this.push(this.stack[this.stack.length - n - 1]);
},
// rotate the last n stack elements p times
roll: function roll(n, p) {
var stack = this.stack;
var l = stack.length - n;
var r = stack.length - 1, c = l + (p - Math.floor(p / n) * n), i, j, t;
for (i = l, j = r; i < j; i++, j--) {
t = stack[i]; stack[i] = stack[j]; stack[j] = t;
}
for (i = l, j = c - 1; i < j; i++, j--) {
t = stack[i]; stack[i] = stack[j]; stack[j] = t;
}
for (i = c, j = r; i < j; i++, j--) {
t = stack[i]; stack[i] = stack[j]; stack[j] = t;
}
}
};
return PostScriptStack;
})();
var PostScriptEvaluator = (function PostScriptEvaluatorClosure() {
function PostScriptEvaluator(operators, operands) {
this.operators = operators;
this.operands = operands;
}
PostScriptEvaluator.prototype = {
execute: function execute(initialStack) {
var stack = new PostScriptStack(initialStack);
var counter = 0;
var operators = this.operators;
var length = operators.length;
var operator, a, b;
while (counter < length) {
operator = operators[counter++];
if (typeof operator == 'number') {
// Operator is really an operand and should be pushed to the stack.
stack.push(operator);
continue;
}
switch (operator) {
// non standard ps operators
case 'jz': // jump if false
b = stack.pop();
a = stack.pop();
if (!a)
counter = b;
break;
case 'j': // jump
a = stack.pop();
counter = a;
break;
// all ps operators in alphabetical order (excluding if/ifelse)
case 'abs':
a = stack.pop();
stack.push(Math.abs(a));
break;
case 'add':
b = stack.pop();
a = stack.pop();
stack.push(a + b);
break;
case 'and':
b = stack.pop();
a = stack.pop();
if (isBool(a) && isBool(b))
stack.push(a && b);
else
stack.push(a & b);
break;
case 'atan':
a = stack.pop();
stack.push(Math.atan(a));
break;
case 'bitshift':
b = stack.pop();
a = stack.pop();
if (a > 0)
stack.push(a << b);
else
stack.push(a >> b);
break;
case 'ceiling':
a = stack.pop();
stack.push(Math.ceil(a));
break;
case 'copy':
a = stack.pop();
stack.copy(a);
break;
case 'cos':
a = stack.pop();
stack.push(Math.cos(a));
break;
case 'cvi':
a = stack.pop() | 0;
stack.push(a);
break;
case 'cvr':
// noop
break;
case 'div':
b = stack.pop();
a = stack.pop();
stack.push(a / b);
break;
case 'dup':
stack.copy(1);
break;
case 'eq':
b = stack.pop();
a = stack.pop();
stack.push(a == b);
break;
case 'exch':
stack.roll(2, 1);
break;
case 'exp':
b = stack.pop();
a = stack.pop();
stack.push(Math.pow(a, b));
break;
case 'false':
stack.push(false);
break;
case 'floor':
a = stack.pop();
stack.push(Math.floor(a));
break;
case 'ge':
b = stack.pop();
a = stack.pop();
stack.push(a >= b);
break;
case 'gt':
b = stack.pop();
a = stack.pop();
stack.push(a > b);
break;
case 'idiv':
b = stack.pop();
a = stack.pop();
stack.push((a / b) | 0);
break;
case 'index':
a = stack.pop();
stack.index(a);
break;
case 'le':
b = stack.pop();
a = stack.pop();
stack.push(a <= b);
break;
case 'ln':
a = stack.pop();
stack.push(Math.log(a));
break;
case 'log':
a = stack.pop();
stack.push(Math.log(a) / Math.LN10);
break;
case 'lt':
b = stack.pop();
a = stack.pop();
stack.push(a < b);
break;
case 'mod':
b = stack.pop();
a = stack.pop();
stack.push(a % b);
break;
case 'mul':
b = stack.pop();
a = stack.pop();
stack.push(a * b);
break;
case 'ne':
b = stack.pop();
a = stack.pop();
stack.push(a != b);
break;
case 'neg':
a = stack.pop();
stack.push(-b);
break;
case 'not':
a = stack.pop();
if (isBool(a) && isBool(b))
stack.push(a && b);
else
stack.push(a & b);
break;
case 'or':
b = stack.pop();
a = stack.pop();
if (isBool(a) && isBool(b))
stack.push(a || b);
else
stack.push(a | b);
break;
case 'pop':
stack.pop();
break;
case 'roll':
b = stack.pop();
a = stack.pop();
stack.roll(a, b);
break;
case 'round':
a = stack.pop();
stack.push(Math.round(a));
break;
case 'sin':
a = stack.pop();
stack.push(Math.sin(a));
break;
case 'sqrt':
a = stack.pop();
stack.push(Math.sqrt(a));
break;
case 'sub':
b = stack.pop();
a = stack.pop();
stack.push(a - b);
break;
case 'true':
stack.push(true);
break;
case 'truncate':
a = stack.pop();
a = a < 0 ? Math.ceil(a) : Math.floor(a);
stack.push(a);
break;
case 'xor':
b = stack.pop();
a = stack.pop();
if (isBool(a) && isBool(b))
stack.push(a != b);
else
stack.push(a ^ b);
break;
default:
error('Unknown operator ' + operator);
break;
}
}
return stack.stack;
}
};
return PostScriptEvaluator;
})();
var PostScriptParser = (function PostScriptParserClosure() {
function PostScriptParser(lexer) {
this.lexer = lexer;
this.operators = [];
this.token;
this.prev;
}
PostScriptParser.prototype = {
nextToken: function nextToken() {
this.prev = this.token;
this.token = this.lexer.getToken();
},
accept: function accept(type) {
if (this.token.type == type) {
this.nextToken();
return true;
}
return false;
},
expect: function expect(type) {
if (this.accept(type))
return true;
error('Unexpected symbol: found ' + this.token.type + ' expected ' +
type + '.');
},
parse: function parse() {
this.nextToken();
this.expect(PostScriptTokenTypes.LBRACE);
this.parseBlock();
this.expect(PostScriptTokenTypes.RBRACE);
return this.operators;
},
parseBlock: function parseBlock() {
while (true) {
if (this.accept(PostScriptTokenTypes.NUMBER)) {
this.operators.push(this.prev.value);
} else if (this.accept(PostScriptTokenTypes.OPERATOR)) {
this.operators.push(this.prev.value);
} else if (this.accept(PostScriptTokenTypes.LBRACE)) {
this.parseCondition();
} else {
return;
}
}
},
parseCondition: function parseCondition() {
// Add two place holders that will be updated later
var conditionLocation = this.operators.length;
this.operators.push(null, null);
this.parseBlock();
this.expect(PostScriptTokenTypes.RBRACE);
if (this.accept(PostScriptTokenTypes.IF)) {
// The true block is right after the 'if' so it just falls through on
// true else it jumps and skips the true block.
this.operators[conditionLocation] = this.operators.length;
this.operators[conditionLocation + 1] = 'jz';
} else if (this.accept(PostScriptTokenTypes.LBRACE)) {
var jumpLocation = this.operators.length;
this.operators.push(null, null);
var endOfTrue = this.operators.length;
this.parseBlock();
this.expect(PostScriptTokenTypes.RBRACE);
this.expect(PostScriptTokenTypes.IFELSE);
// The jump is added at the end of the true block to skip the false
// block.
this.operators[jumpLocation] = this.operators.length;
this.operators[jumpLocation + 1] = 'j';
this.operators[conditionLocation] = endOfTrue;
this.operators[conditionLocation + 1] = 'jz';
} else {
error('PS Function: error parsing conditional.');
}
}
};
return PostScriptParser;
})();
var PostScriptTokenTypes = {
LBRACE: 0,
RBRACE: 1,
NUMBER: 2,
OPERATOR: 3,
IF: 4,
IFELSE: 5
};
var PostScriptToken = (function PostScriptTokenClosure() {
function PostScriptToken(type, value) {
this.type = type;
this.value = value;
}
var opCache = {};
PostScriptToken.getOperator = function getOperator(op) {
var opValue = opCache[op];
if (opValue)
return opValue;
return opCache[op] = new PostScriptToken(PostScriptTokenTypes.OPERATOR, op);
};
PostScriptToken.LBRACE = new PostScriptToken(PostScriptTokenTypes.LBRACE,
'{');
PostScriptToken.RBRACE = new PostScriptToken(PostScriptTokenTypes.RBRACE,
'}');
PostScriptToken.IF = new PostScriptToken(PostScriptTokenTypes.IF, 'IF');
PostScriptToken.IFELSE = new PostScriptToken(PostScriptTokenTypes.IFELSE,
'IFELSE');
return PostScriptToken;
})();
var PostScriptLexer = (function PostScriptLexerClosure() {
function PostScriptLexer(stream) {
this.stream = stream;
}
PostScriptLexer.prototype = {
getToken: function getToken() {
var s = '';
var ch;
var comment = false;
var stream = this.stream;
// skip comments
while (true) {
if (!(ch = stream.getChar()))
return EOF;
if (comment) {
if (ch == '\x0a' || ch == '\x0d')
comment = false;
} else if (ch == '%') {
comment = true;
} else if (!Lexer.isSpace(ch)) {
break;
}
}
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 new PostScriptToken(PostScriptTokenTypes.NUMBER,
this.getNumber(ch));
case '{':
return PostScriptToken.LBRACE;
case '}':
return PostScriptToken.RBRACE;
}
// operator
var str = ch.toLowerCase();
while (true) {
ch = stream.lookChar().toLowerCase();
if (ch >= 'a' && ch <= 'z')
str += ch;
else
break;
stream.skip();
}
switch (str) {
case 'if':
return PostScriptToken.IF;
case 'ifelse':
return PostScriptToken.IFELSE;
default:
return PostScriptToken.getOperator(str);
}
},
getNumber: function getNumber(ch) {
var str = ch;
var stream = this.stream;
while (true) {
ch = stream.lookChar();
if ((ch >= '0' && ch <= '9') || ch == '-' || ch == '.')
str += ch;
else
break;
stream.skip();
}
var value = parseFloat(str);
if (isNaN(value))
error('Invalid floating point number: ' + value);
return value;
}
};
return PostScriptLexer;
})();

View File

@ -8,8 +8,7 @@ var Name = (function NameClosure() {
this.name = name;
}
Name.prototype = {
};
Name.prototype = {};
return Name;
})();
@ -19,9 +18,7 @@ var Cmd = (function CmdClosure() {
this.cmd = cmd;
}
Cmd.prototype = {
};
Cmd.prototype = {};
var cmdCache = {};
@ -80,8 +77,7 @@ var Ref = (function RefClosure() {
this.gen = gen;
}
Ref.prototype = {
};
Ref.prototype = {};
return Ref;
})();
@ -273,7 +269,7 @@ var XRef = (function XRefClosure() {
this.entries = [];
this.xrefstms = {};
var trailerDict = this.readXRef(startXRef);
this.trailer = trailerDict;
// prepare the XRef cache
this.cache = [];

View File

@ -21,3 +21,4 @@
!freeculture.pdf
!issue918.pdf
!smaskdim.pdf
!type4psfunc.pdf

BIN
test/pdfs/type4psfunc.pdf Executable file

Binary file not shown.

View File

@ -17,12 +17,13 @@
"rounds": 1,
"type": "load"
},
{ "id": "intelisa-load",
{ "id": "intelisa-eq",
"file": "pdfs/intelisa.pdf",
"md5": "f5712097d29287a97f1278839814f682",
"link": true,
"pageLimit": 100,
"rounds": 1,
"type": "load"
"type": "eq"
},
{ "id": "pdfspec-load",
"file": "pdfs/pdf.pdf",
@ -367,5 +368,11 @@
"md5": "de80aeca7cbf79940189fd34d59671ee",
"rounds": 1,
"type": "eq"
},
{ "id": "type4psfunc",
"file": "pdfs/type4psfunc.pdf",
"md5": "7e6027a02ff78577f74dccdf84e37189",
"rounds": 1,
"type": "eq"
}
]

225
test/unit/function_spec.js Normal file
View File

@ -0,0 +1,225 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
'use strict';
describe('function', function() {
beforeEach(function() {
this.addMatchers({
toMatchArray: function(expected) {
var actual = this.actual;
if (actual.length != expected.length)
return false;
for (var i = 0; i < expected.length; i++) {
var a = actual[i], b = expected[i];
if (isArray(b)) {
if (a.length != b.length)
return false;
for (var j = 0; j < a.length; j++) {
var suba = a[j], subb = b[j];
if (suba !== subb)
return false;
}
} else {
if (a !== b)
return false;
}
}
return true;
}
});
});
describe('PostScriptParser', function() {
function parse(program) {
var stream = new StringStream(program);
var parser = new PostScriptParser(new PostScriptLexer(stream));
return parser.parse();
}
it('parses empty programs', function() {
var output = parse('{}');
expect(output.length).toEqual(0);
});
it('parses positive numbers', function() {
var number = 999;
var program = parse('{ ' + number + ' }');
var expectedProgram = [number];
expect(program).toMatchArray(expectedProgram);
});
it('parses negative numbers', function() {
var number = -999;
var program = parse('{ ' + number + ' }');
var expectedProgram = [number];
expect(program).toMatchArray(expectedProgram);
});
it('parses negative floats', function() {
var number = 3.3;
var program = parse('{ ' + number + ' }');
var expectedProgram = [number];
expect(program).toMatchArray(expectedProgram);
});
it('parses operators', function() {
var program = parse('{ sub }');
var expectedProgram = ['sub'];
expect(program).toMatchArray(expectedProgram);
});
it('parses if statements', function() {
var program = parse('{ { 99 } if }');
var expectedProgram = [3, 'jz', 99];
expect(program).toMatchArray(expectedProgram);
});
it('parses ifelse statements', function() {
var program = parse('{ { 99 } { 44 } ifelse }');
var expectedProgram = [5, 'jz', 99, 6, 'j', 44];
expect(program).toMatchArray(expectedProgram);
});
it('handles missing brackets', function() {
expect(function() { parse('{'); }).toThrow(
new Error('Unexpected symbol: found undefined expected 1.'));
});
});
describe('PostScriptEvaluator', function() {
function evaluate(program) {
var stream = new StringStream(program);
var parser = new PostScriptParser(new PostScriptLexer(stream));
var code = parser.parse();
var evaluator = new PostScriptEvaluator(code);
var output = evaluator.execute();
return output;
}
it('pushes stack', function() {
var stack = evaluate('{ 99 }');
var expectedStack = [99];
expect(stack).toMatchArray(expectedStack);
});
it('handles if with true', function() {
var stack = evaluate('{ 1 {99} if }');
var expectedStack = [99];
expect(stack).toMatchArray(expectedStack);
});
it('handles if with false', function() {
var stack = evaluate('{ 0 {99} if }');
var expectedStack = [];
expect(stack).toMatchArray(expectedStack);
});
it('handles ifelse with true', function() {
var stack = evaluate('{ 1 {99} {77} ifelse }');
var expectedStack = [99];
expect(stack).toMatchArray(expectedStack);
});
it('handles ifelse with false', function() {
var stack = evaluate('{ 0 {99} {77} ifelse }');
var expectedStack = [77];
expect(stack).toMatchArray(expectedStack);
});
it('handles nested if', function() {
var stack = evaluate('{ 1 {1 {77} if} if }');
var expectedStack = [77];
expect(stack).toMatchArray(expectedStack);
});
it('abs', function() {
var stack = evaluate('{ -2 abs }');
var expectedStack = [2];
expect(stack).toMatchArray(expectedStack);
});
it('adds', function() {
var stack = evaluate('{ 1 2 add }');
var expectedStack = [3];
expect(stack).toMatchArray(expectedStack);
});
it('boolean ands', function() {
var stack = evaluate('{ true false and }');
var expectedStack = [false];
expect(stack).toMatchArray(expectedStack);
});
it('bitwise ands', function() {
var stack = evaluate('{ 254 1 and }');
var expectedStack = [254 & 1];
expect(stack).toMatchArray(expectedStack);
});
// TODO atan
// TODO bitshift
// TODO ceiling
// TODO copy
// TODO cos
it('converts to int', function() {
var stack = evaluate('{ 9.9 cvi }');
var expectedStack = [9];
expect(stack).toMatchArray(expectedStack);
});
it('converts negatives to int', function() {
var stack = evaluate('{ -9.9 cvi }');
var expectedStack = [-9];
expect(stack).toMatchArray(expectedStack);
});
// TODO cvr
// TODO div
it('duplicates', function() {
var stack = evaluate('{ 99 dup }');
var expectedStack = [99, 99];
expect(stack).toMatchArray(expectedStack);
});
// TODO eq
it('exchanges', function() {
var stack = evaluate('{ 44 99 exch }');
var expectedStack = [99, 44];
expect(stack).toMatchArray(expectedStack);
});
// TODO exp
// TODO false
// TODO floor
// TODO ge
// TODO gt
it('divides to integer', function() {
var stack = evaluate('{ 2 3 idiv }');
var expectedStack = [0];
expect(stack).toMatchArray(expectedStack);
});
it('divides to negative integer', function() {
var stack = evaluate('{ -2 3 idiv }');
var expectedStack = [0];
expect(stack).toMatchArray(expectedStack);
});
it('duplicates index', function() {
var stack = evaluate('{ 4 3 2 1 2 index }');
var expectedStack = [4, 3, 2, 1, 3];
expect(stack).toMatchArray(expectedStack);
});
// TODO le
// TODO ln
// TODO log
// TODO lt
// TODO mod
// TODO mul
// TODO ne
// TODO neg
// TODO not
// TODO or
it('pops stack', function() {
var stack = evaluate('{ 1 2 pop }');
var expectedStack = [1];
expect(stack).toMatchArray(expectedStack);
});
it('rolls stack right', function() {
var stack = evaluate('{ 1 3 2 2 4 1 roll }');
var expectedStack = [2, 1, 3, 2];
expect(stack).toMatchArray(expectedStack);
});
it('rolls stack left', function() {
var stack = evaluate('{ 1 3 2 2 4 -1 roll }');
var expectedStack = [3, 2, 2, 1];
expect(stack).toMatchArray(expectedStack);
});
// TODO round
// TODO sin
// TODO sqrt
// TODO sub
// TODO true
// TODO truncate
// TODO xor
});
});

View File

@ -3,14 +3,129 @@
'use strict';
describe("obj", function() {
describe('obj', function() {
describe("Name", function() {
it("should retain the given name", function() {
var givenName = "Font";
describe('Name', function() {
it('should retain the given name', function() {
var givenName = 'Font';
var name = new Name(givenName);
expect(name.name).toEqual(givenName);
});
});
describe('Cmd', function() {
it('should retain the given cmd name', function() {
var givenCmd = 'BT';
var cmd = new Cmd(givenCmd);
expect(cmd.cmd).toEqual(givenCmd);
});
it('should create only one object for a command and cache it', function() {
var firstBT = Cmd.get('BT');
var secondBT = Cmd.get('BT');
var firstET = Cmd.get('ET');
var secondET = Cmd.get('ET');
expect(firstBT).toBe(secondBT);
expect(firstET).toBe(secondET);
expect(firstBT).not.toBe(firstET);
});
});
describe('Dict', function() {
var checkInvalidHasValues = function(dict) {
expect(dict.has()).toBeFalsy();
expect(dict.has('Prev')).toBeFalsy();
};
var checkInvalidKeyValues = function(dict) {
expect(dict.get()).toBeUndefined();
expect(dict.get('Prev')).toBeUndefined();
expect(dict.get('Decode', 'D')).toBeUndefined();
// Note that the getter with three arguments breaks the pattern here.
expect(dict.get('FontFile', 'FontFile2', 'FontFile3')).toBeNull();
};
var emptyDict, dictWithSizeKey, dictWithManyKeys;
var storedSize = 42;
var testFontFile = 'file1';
var testFontFile2 = 'file2';
var testFontFile3 = 'file3';
beforeEach(function() {
emptyDict = new Dict();
dictWithSizeKey = new Dict();
dictWithSizeKey.set('Size', storedSize);
dictWithManyKeys = new Dict();
dictWithManyKeys.set('FontFile', testFontFile);
dictWithManyKeys.set('FontFile2', testFontFile2);
dictWithManyKeys.set('FontFile3', testFontFile3);
});
it('should return invalid values for unknown keys', function() {
checkInvalidHasValues(emptyDict);
checkInvalidKeyValues(emptyDict);
});
it('should return correct value for stored Size key', function() {
expect(dictWithSizeKey.has('Size')).toBeTruthy();
expect(dictWithSizeKey.get('Size')).toEqual(storedSize);
expect(dictWithSizeKey.get('Prev', 'Size')).toEqual(storedSize);
expect(dictWithSizeKey.get('Prev', 'Root', 'Size')).toEqual(storedSize);
});
it('should return invalid values for unknown keys when Size key is stored',
function() {
checkInvalidHasValues(dictWithSizeKey);
checkInvalidKeyValues(dictWithSizeKey);
});
it('should return correct value for stored Size key with undefined value',
function() {
var dict = new Dict();
dict.set('Size');
expect(dict.has('Size')).toBeTruthy();
checkInvalidKeyValues(dict);
});
it('should return correct values for multiple stored keys', function() {
expect(dictWithManyKeys.has('FontFile')).toBeTruthy();
expect(dictWithManyKeys.has('FontFile2')).toBeTruthy();
expect(dictWithManyKeys.has('FontFile3')).toBeTruthy();
expect(dictWithManyKeys.get('FontFile3')).toEqual(testFontFile3);
expect(dictWithManyKeys.get('FontFile2', 'FontFile3'))
.toEqual(testFontFile2);
expect(dictWithManyKeys.get('FontFile', 'FontFile2', 'FontFile3'))
.toEqual(testFontFile);
});
it('should callback for each stored key', function() {
var callbackSpy = jasmine.createSpy('spy on callback in dictionary');
dictWithManyKeys.forEach(callbackSpy);
expect(callbackSpy).wasCalled();
expect(callbackSpy.argsForCall[0]).toEqual(['FontFile', testFontFile]);
expect(callbackSpy.argsForCall[1]).toEqual(['FontFile2', testFontFile2]);
expect(callbackSpy.argsForCall[2]).toEqual(['FontFile3', testFontFile3]);
expect(callbackSpy.callCount).toEqual(3);
});
});
describe('Ref', function() {
it('should retain the stored values', function() {
var storedNum = 4;
var storedGen = 2;
var ref = new Ref(storedNum, storedGen);
expect(ref.num).toEqual(storedNum);
expect(ref.gen).toEqual(storedGen);
});
});
});

View File

@ -11,9 +11,28 @@
<!-- include spec files here... -->
<script type="text/javascript" src="obj_spec.js"></script>
<script type="text/javascript" src="function_spec.js"></script>
<!-- include source files here... -->
<script type="text/javascript" src="../../src/core.js"></script>
<script type="text/javascript" src="../../src/util.js"></script>
<script type="text/javascript" src="../../src/canvas.js"></script>
<script type="text/javascript" src="../../src/obj.js"></script>
<script type="text/javascript" src="../../src/function.js"></script>
<script type="text/javascript" src="../../src/charsets.js"></script>
<script type="text/javascript" src="../../src/cidmaps.js"></script>
<script type="text/javascript" src="../../src/colorspace.js"></script>
<script type="text/javascript" src="../../src/crypto.js"></script>
<script type="text/javascript" src="../../src/evaluator.js"></script>
<script type="text/javascript" src="../../src/fonts.js"></script>
<script type="text/javascript" src="../../src/glyphlist.js"></script>
<script type="text/javascript" src="../../src/image.js"></script>
<script type="text/javascript" src="../../src/metrics.js"></script>
<script type="text/javascript" src="../../src/parser.js"></script>
<script type="text/javascript" src="../../src/pattern.js"></script>
<script type="text/javascript" src="../../src/stream.js"></script>
<script type="text/javascript" src="../../src/worker.js"></script>
<script type="text/javascript" src="../../external/jpgjs/jpg.js"></script>
<script type="text/javascript">
'use strict';

3
web/images/check.svg Normal file
View File

@ -0,0 +1,3 @@
<svg height="40" width="40" xmlns="http://www.w3.org/2000/svg">
<path d="M2.379,14.729 5.208,11.899 12.958,19.648 25.877,6.733 28.707,9.561 12.958,25.308z" fill="#333333"></path>
</svg>

After

Width:  |  Height:  |  Size: 188 B

3
web/images/comment.svg Normal file
View File

@ -0,0 +1,3 @@
<svg height="40" width="40" xmlns="http://www.w3.org/2000/svg">
<path d="M16,5.333c-7.732,0-14,4.701-14,10.5c0,1.982,0.741,3.833,2.016,5.414L2,25.667l5.613-1.441c2.339,1.317,5.237,2.107,8.387,2.107c7.732,0,14-4.701,14-10.5C30,10.034,23.732,5.333,16,5.333z" fill="#333333"></path>
</svg>

After

Width:  |  Height:  |  Size: 289 B

View File

@ -285,6 +285,35 @@ canvas {
line-height:1.3;
}
.annotComment > div {
position: absolute;
}
.annotComment > img {
position: absolute;
}
.annotComment > img:hover {
cursor: pointer;
opacity: 0.7;
}
.annotComment > div {
padding: 0.2em;
max-width: 20em;
background-color: #F1E47B;
box-shadow: 0px 2px 10px #333;
-moz-box-shadow: 0px 2px 10px #333;
-webkit-box-shadow: 0px 2px 10px #333;
}
.annotComment > div > h1 {
font-weight: normal;
font-size: 1.2em;
border-bottom: 1px solid #000000;
margin: 0px;
}
/* TODO: file FF bug to support ::-moz-selection:window-inactive
so we can override the opaque grey background when the window is inactive;
see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */

View File

@ -11,7 +11,8 @@ var kCssUnits = 96.0 / 72.0;
var kScrollbarPadding = 40;
var kMinScale = 0.25;
var kMaxScale = 4.0;
var kImageDirectory = './images/';
var kSettingsMemory = 20;
var Cache = function cacheCache(size) {
var data = [];
@ -25,13 +26,126 @@ var Cache = function cacheCache(size) {
};
};
var RenderingQueue = (function RenderingQueueClosure() {
function RenderingQueue() {
this.items = [];
}
RenderingQueue.prototype = {
enqueueDraw: function RenderingQueueEnqueueDraw(item) {
if ('rendering' in item)
return; // is already in the queue
item.rendering = true;
this.items.push(item);
if (this.items.length > 1)
return; // not first item
item.draw(this.continueExecution.bind(this));
},
continueExecution: function RenderingQueueContinueExecution() {
var item = this.items.shift();
delete item.rendering;
if (this.items.length == 0)
return; // queue is empty
item = this.items[0];
item.draw(this.continueExecution.bind(this));
}
};
return RenderingQueue;
})();
// Settings Manager - This is a utility for saving settings
// First we see if localStorage is available, FF bug #495747
// If not, we use FUEL in FF
var Settings = (function SettingsClosure() {
var isLocalStorageEnabled = (function localStorageEnabledTest() {
try {
localStorage;
} catch (e) {
return false;
}
return true;
})();
var extPrefix = 'extensions.uriloader@pdf.js';
var isExtension = location.protocol == 'chrome:' && !isLocalStorageEnabled;
var inPrivateBrowsing = false;
if (isExtension) {
var pbs = Components.classes['@mozilla.org/privatebrowsing;1']
.getService(Components.interfaces.nsIPrivateBrowsingService);
inPrivateBrowsing = pbs.privateBrowsingEnabled;
}
function Settings(fingerprint) {
var database = null;
var index;
if (inPrivateBrowsing)
return false;
else if (isExtension)
database = Application.prefs.getValue(extPrefix + '.database', '{}');
else if (isLocalStorageEnabled)
database = localStorage.getItem('database') || '{}';
else
return false;
database = JSON.parse(database);
if (!('files' in database))
database.files = [];
if (database.files.length >= kSettingsMemory)
database.files.shift();
for (var i = 0, length = database.files.length; i < length; i++) {
var branch = database.files[i];
if (branch.fingerprint == fingerprint) {
index = i;
break;
}
}
if (typeof index != 'number')
index = database.files.push({fingerprint: fingerprint}) - 1;
this.file = database.files[index];
this.database = database;
if (isExtension)
Application.prefs.setValue(extPrefix + '.database',
JSON.stringify(database));
else if (isLocalStorageEnabled)
localStorage.setItem('database', JSON.stringify(database));
}
Settings.prototype = {
set: function settingsSet(name, val) {
if (inPrivateBrowsing)
return false;
var file = this.file;
file[name] = val;
if (isExtension)
Application.prefs.setValue(extPrefix + '.database',
JSON.stringify(this.database));
else if (isLocalStorageEnabled)
localStorage.setItem('database', JSON.stringify(this.database));
},
get: function settingsGet(name, defaultValue) {
if (inPrivateBrowsing)
return defaultValue;
else
return this.file[name] || defaultValue;
}
};
return Settings;
})();
var cache = new Cache(kCacheSize);
var renderingQueue = new RenderingQueue();
var currentPageNumber = 1;
var PDFView = {
pages: [],
thumbnails: [],
currentScale: kDefaultScale,
currentScale: 0,
initialBookmark: document.location.hash.substring(1),
setScale: function pdfViewSetScale(val, resetAutoSettings) {
@ -272,8 +386,20 @@ var PDFView = {
this.error('An error occurred while reading the PDF.', e);
}
var pagesCount = pdf.numPages;
var id = pdf.fingerprint;
var storedHash = null;
document.getElementById('numPages').innerHTML = pagesCount;
document.getElementById('pageNumber').max = pagesCount;
PDFView.documentFingerprint = id;
var store = PDFView.store = new Settings(id);
if (store.get('exists', false)) {
var page = store.get('page', '1');
var zoom = store.get('zoom', PDFView.currentScale);
var left = store.get('scrollLeft', '0');
var top = store.get('scrollTop', '0');
storedHash = 'page=' + page + '&zoom=' + zoom + ',' + left + ',' + top;
}
var pages = this.pages = [];
var pagesRefMap = {};
@ -294,7 +420,6 @@ var PDFView = {
this.pagesRefMap = pagesRefMap;
this.destinations = pdf.catalog.destinations;
this.setScale(scale || kDefaultScale, true);
if (pdf.catalog.documentOutline) {
this.outline = new DocumentOutlineView(pdf.catalog.documentOutline);
@ -307,8 +432,12 @@ var PDFView = {
this.setHash(this.initialBookmark);
this.initialBookmark = null;
}
else
else if (storedHash)
this.setHash(storedHash);
else {
this.setScale(scale || kDefaultScale, true);
this.page = 1;
}
// loosing pdf reference here, starting text indexing in 500ms
setTimeout((function loadStartTextExtraction() {
@ -396,7 +525,7 @@ var PDFView = {
if ('zoom' in params) {
var zoomArgs = params.zoom.split(','); // scale,left,top
// building destination array
var dest = [null, new Name('XYZ'), (zoomArgs[1] | 0),
var dest = [null, {name: 'XYZ'}, (zoomArgs[1] | 0),
(zoomArgs[2] | 0), (zoomArgs[0] | 0) / 100];
var currentPage = this.pages[pageNumber - 1];
currentPage.scrollIntoView(dest);
@ -533,7 +662,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
delete this.canvas;
};
function setupLinks(content, scale) {
function setupAnnotations(content, scale) {
function bindLink(link, dest) {
link.href = PDFView.getDestinationHash(dest);
link.onclick = function pageViewSetupLinksOnclick() {
@ -542,18 +671,67 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
return false;
};
}
function createElementWithStyle(tagName, item) {
var element = document.createElement(tagName);
element.style.left = (Math.floor(item.x - view.x) * scale) + 'px';
element.style.top = (Math.floor(item.y - view.y) * scale) + 'px';
element.style.width = Math.ceil(item.width * scale) + 'px';
element.style.height = Math.ceil(item.height * scale) + 'px';
return element;
}
function createCommentAnnotation(type, item) {
var container = document.createElement('section');
container.className = 'annotComment';
var links = content.getLinks();
for (var i = 0; i < links.length; i++) {
var link = document.createElement('a');
link.style.left = (Math.floor(links[i].x - view.x) * scale) + 'px';
link.style.top = (Math.floor(links[i].y - view.y) * scale) + 'px';
link.style.width = Math.ceil(links[i].width * scale) + 'px';
link.style.height = Math.ceil(links[i].height * scale) + 'px';
link.href = links[i].url || '';
if (!links[i].url)
bindLink(link, ('dest' in links[i]) ? links[i].dest : null);
div.appendChild(link);
var image = createElementWithStyle('img', item);
image.src = kImageDirectory + type.toLowerCase() + '.svg';
var content = document.createElement('div');
content.setAttribute('hidden', true);
var title = document.createElement('h1');
var text = document.createElement('p');
var offsetPos = Math.floor(item.x - view.x + item.width);
content.style.left = (offsetPos * scale) + 'px';
content.style.top = (Math.floor(item.y - view.y) * scale) + 'px';
title.textContent = item.title;
if (!item.content) {
content.setAttribute('hidden', true);
} else {
text.innerHTML = item.content.replace('\n', '<br />');
image.addEventListener('mouseover', function annotationImageOver() {
this.nextSibling.removeAttribute('hidden');
}, false);
image.addEventListener('mouseout', function annotationImageOut() {
this.nextSibling.setAttribute('hidden', true);
}, false);
}
content.appendChild(title);
content.appendChild(text);
container.appendChild(image);
container.appendChild(content);
return container;
}
var items = content.getAnnotations();
for (var i = 0; i < items.length; i++) {
var item = items[i];
switch (item.type) {
case 'Link':
var link = createElementWithStyle('a', item);
link.href = item.url || '';
if (!item.url)
bindLink(link, ('dest' in item) ? item.dest : null);
div.appendChild(link);
break;
case 'Text':
var comment = createCommentAnnotation(item.name, item);
if (comment)
div.appendChild(comment);
break;
}
}
}
@ -635,10 +813,11 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
}, 0);
};
this.draw = function pageviewDraw() {
this.draw = function pageviewDraw(callback) {
if (div.hasChildNodes()) {
this.updateStats();
return false;
callback();
return;
}
var canvas = document.createElement('canvas');
@ -670,13 +849,14 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
this.updateStats();
if (this.onAfterDraw)
this.onAfterDraw();
cache.push(this);
callback();
}).bind(this), textLayer
);
setupLinks(this.content, this.scale);
setupAnnotations(this.content, this.scale);
div.setAttribute('data-loaded', true);
return true;
};
this.updateStats = function pageViewUpdateStats() {
@ -743,12 +923,16 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) {
return ctx;
}
this.draw = function thumbnailViewDraw() {
if (this.hasImage)
this.draw = function thumbnailViewDraw(callback) {
if (this.hasImage) {
callback();
return;
}
var ctx = getPageDrawContext();
page.startRendering(ctx, function thumbnailViewDrawStartRendering() {});
page.startRendering(ctx, function thumbnailViewDrawStartRendering() {
callback();
});
this.hasImage = true;
};
@ -831,8 +1015,7 @@ function updateViewarea() {
var visiblePages = PDFView.getVisiblePages();
for (var i = 0; i < visiblePages.length; i++) {
var page = visiblePages[i];
if (PDFView.pages[page.id - 1].draw())
cache.push(page.view);
renderingQueue.enqueueDraw(PDFView.pages[page.id - 1]);
}
if (!visiblePages.length)
@ -852,6 +1035,14 @@ function updateViewarea() {
var topLeft = currentPage.getPagePoint(window.pageXOffset,
window.pageYOffset - firstPage.y - kViewerTopMargin);
pdfOpenParams += ',' + Math.round(topLeft.x) + ',' + Math.round(topLeft.y);
var store = PDFView.store;
store.set('exists', true);
store.set('page', pageNumber);
store.set('zoom', Math.round(PDFView.currentScale * 100));
store.set('scrollLeft', Math.round(topLeft.x));
store.set('scrollTop', Math.round(topLeft.y));
document.getElementById('viewBookmark').href = pdfOpenParams;
}
@ -874,7 +1065,7 @@ function updateThumbViewArea() {
var visibleThumbs = PDFView.getVisibleThumbs();
for (var i = 0; i < visibleThumbs.length; i++) {
var thumb = visibleThumbs[i];
PDFView.thumbnails[thumb.id - 1].draw();
renderingQueue.enqueueDraw(PDFView.thumbnails[thumb.id - 1]);
}
}, delay);
}
@ -914,7 +1105,6 @@ window.addEventListener('change', function webViewerChange(evt) {
// implemented in Firefox.
var file = files[0];
fileReader.readAsBinaryString(file);
document.title = file.name;
// URL does not reflect proper document location - hiding some icons.
@ -963,6 +1153,8 @@ window.addEventListener('pagechange', function pagechange(evt) {
}, true);
window.addEventListener('keydown', function keydown(evt) {
if (evt.ctrlKey || evt.altKey || evt.shiftKey || evt.metaKey)
return;
var curElement = document.activeElement;
if (curElement && curElement.tagName == 'INPUT')
return;