Merge remote-tracking branch 'upstream/master' into dev

Conflicts:
	Makefile
	test/unit/unit_test.html
This commit is contained in:
Kalervo Kujala 2012-01-05 21:16:15 +02:00
commit 09eed8d971
51 changed files with 3644 additions and 828 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

@ -68,7 +68,8 @@ bundle: | $(BUILD_DIR)
@cd src; \
cat $(PDF_JS_FILES) > all_files.tmp; \
sed '/PDFJSSCRIPT_INCLUDE_ALL/ r all_files.tmp' pdf.js > ../$(BUILD_TARGET); \
sed -i '' "s/PDFJSSCRIPT_BUNDLE_VER/`git log --format="%H" -n 1`/" ../$(BUILD_TARGET); \
sed -i.bak "s/PDFJSSCRIPT_BUNDLE_VER/`git log --format="%h" -n 1`/" ../$(BUILD_TARGET); \
rm -f ../$(BUILD_TARGET).bak
rm -f *.tmp; \
cd ..
@ -138,8 +139,8 @@ browser-test:
# To install gjslint, see:
#
# <http://code.google.com/closure/utilities/docs/linter_howto.html>
SRC_DIRS := . src utils web test test/unit examples/helloworld \
extensions/firefox extensions/firefox/components extensions/chrome
SRC_DIRS := . src utils web test examples/helloworld extensions/firefox \
extensions/firefox/components extensions/chrome test/unit
GJSLINT_FILES = $(foreach DIR,$(SRC_DIRS),$(wildcard $(DIR)/*.js))
lint:
gjslint --nojsdoc $(GJSLINT_FILES)

View File

@ -205,3 +205,4 @@ a "PDF Reference" from Adobe:
Recommended chapters to read: "2. Overview", "3.4 File Structure",
"4.1 Graphics Objects" that lists the PDF commands.

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

@ -6,13 +6,13 @@
<Description about="urn:mozilla:install-manifest">
<em:id>uriloader@pdf.js</em:id>
<em:name>pdf.js</em:name>
<em:version>0.1</em:version>
<em:version>0.1.0</em:version>
<em:iconURL>chrome://pdf.js/skin/logo.png</em:iconURL>
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>6.0</em:minVersion>
<em:maxVersion>11.0.*</em:maxVersion>
<em:maxVersion>11.0a1</em:maxVersion>
</Description>
</em:targetApplication>
<em:bootstrap>true</em:bootstrap>
@ -20,5 +20,6 @@
<em:creator>Vivien Nicolas</em:creator>
<em:description>pdf.js uri loader</em:description>
<em:homepageURL>https://github.com/mozilla/pdf.js/</em:homepageURL>
<em:type>2</em:type>
</Description>
</RDF>

View File

@ -6,8 +6,19 @@
// <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) {
var TextRenderingMode = {
FILL: 0,
STROKE: 1,
FILL_STROKE: 2,
INVISIBLE: 3,
FILL_ADD_TO_PATH: 4,
STROKE_ADD_TO_PATH: 5,
FILL_STROKE_ADD_TO_PATH: 6,
ADD_TO_PATH: 7
};
var CanvasExtraState = (function CanvasExtraStateClosure() {
function CanvasExtraState(old) {
// Are soft masks and alpha values shapes or opacities?
this.alphaIsShape = false;
this.fontSize = 0;
@ -23,6 +34,7 @@ var CanvasExtraState = (function canvasExtraState() {
this.charSpacing = 0;
this.wordSpacing = 0;
this.textHScale = 1;
this.textRenderingMode = TextRenderingMode.FILL;
// Color spaces
this.fillColorSpace = new DeviceGrayCS();
this.fillColorSpaceObj = null;
@ -40,7 +52,7 @@ var CanvasExtraState = (function canvasExtraState() {
this.old = old;
}
constructor.prototype = {
CanvasExtraState.prototype = {
clone: function canvasextra_clone() {
return Object.create(this);
},
@ -49,7 +61,7 @@ var CanvasExtraState = (function canvasExtraState() {
this.y = y;
}
};
return constructor;
return CanvasExtraState;
})();
function ScratchCanvas(width, height) {
@ -59,16 +71,122 @@ function ScratchCanvas(width, height) {
return canvas;
}
var CanvasGraphics = (function canvasGraphics() {
function addContextCurrentTransform(ctx) {
// If the context doesn't expose a `mozCurrentTransform`, add a JS based on.
if (!ctx.mozCurrentTransform) {
// Store the original context
ctx._originalSave = ctx.save;
ctx._originalRestore = ctx.restore;
ctx._originalRotate = ctx.rotate;
ctx._originalScale = ctx.scale;
ctx._originalTranslate = ctx.translate;
ctx._originalTransform = ctx.transform;
ctx._transformMatrix = [1, 0, 0, 1, 0, 0];
ctx._transformStack = [];
Object.defineProperty(ctx, 'mozCurrentTransform', {
get: function getCurrentTransform() {
return this._transformMatrix;
}
});
Object.defineProperty(ctx, 'mozCurrentTransformInverse', {
get: function getCurrentTransformInverse() {
// Calculation done using WolframAlpha:
// http://www.wolframalpha.com/input/?
// i=Inverse+{{a%2C+c%2C+e}%2C+{b%2C+d%2C+f}%2C+{0%2C+0%2C+1}}
var m = this._transformMatrix;
var a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5];
var ad_bc = a * d - b * c;
var bc_ad = b * c - a * d;
return [
d / ad_bc,
b / bc_ad,
c / bc_ad,
a / ad_bc,
(d * e - c * f) / bc_ad,
(b * e - a * f) / ad_bc
];
}
});
ctx.save = function ctxSave() {
var old = this._transformMatrix;
this._transformStack.push(old);
this._transformMatrix = old.slice(0, 6);
this._originalSave();
};
ctx.restore = function ctxRestore() {
var prev = this._transformStack.pop();
if (prev) {
this._transformMatrix = prev;
this._originalRestore();
}
};
ctx.translate = function ctxTranslate(x, y) {
var m = this._transformMatrix;
m[4] = m[0] * x + m[2] * y + m[4];
m[5] = m[1] * x + m[3] * y + m[5];
this._originalTranslate(x, y);
};
ctx.scale = function ctxScale(x, y) {
var m = this._transformMatrix;
m[0] = m[0] * x;
m[1] = m[1] * x;
m[2] = m[2] * y;
m[3] = m[3] * y;
this._originalScale(x, y);
};
ctx.transform = function ctxTransform(a, b, c, d, e, f) {
var m = this._transformMatrix;
this._transformMatrix = [
m[0] * a + m[2] * b,
m[1] * a + m[3] * b,
m[0] * c + m[2] * d,
m[1] * c + m[3] * d,
m[0] * e + m[2] * f + m[4],
m[1] * e + m[3] * f + m[5]
];
ctx._originalTransform(a, b, c, d, e, f);
};
ctx.rotate = function ctxRotate(angle) {
var cosValue = Math.cos(angle);
var sinValue = Math.sin(angle);
var m = this._transformMatrix;
this._transformMatrix = [
m[0] * cosValue + m[2] * sinValue,
m[1] * cosValue + m[3] * sinValue,
m[0] * (-sinValue) + m[2] * cosValue,
m[1] * (-sinValue) + m[3] * cosValue,
m[4],
m[5]
];
this._originalRotate(angle);
};
}
}
var CanvasGraphics = (function CanvasGraphicsClosure() {
// 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) {
function CanvasGraphics(canvasCtx, objs, textLayer) {
this.ctx = canvasCtx;
this.current = new CanvasExtraState();
this.stateStack = [];
@ -77,6 +195,10 @@ var CanvasGraphics = (function canvasGraphics() {
this.xobjs = null;
this.ScratchCanvas = ScratchCanvas;
this.objs = objs;
this.textLayer = textLayer;
if (canvasCtx) {
addContextCurrentTransform(canvasCtx);
}
}
var LINE_CAP_STYLES = ['butt', 'round', 'square'];
@ -84,7 +206,36 @@ var CanvasGraphics = (function canvasGraphics() {
var NORMAL_CLIP = {};
var EO_CLIP = {};
constructor.prototype = {
CanvasGraphics.prototype = {
slowCommands: {
'stroke': true,
'closeStroke': true,
'fill': true,
'eoFill': true,
'fillStroke': true,
'eoFillStroke': true,
'closeFillStroke': true,
'closeEOFillStroke': true,
'showText': true,
'showSpacedText': true,
'setStrokeColorSpace': true,
'setFillColorSpace': true,
'setStrokeColor': true,
'setStrokeColorN': true,
'setFillColor': true,
'setFillColorN_IR': true,
'setStrokeGray': true,
'setFillGray': true,
'setStrokeRGBColor': true,
'setFillRGBColor': true,
'setStrokeCMYKColor': true,
'setFillCMYKColor': true,
'paintJpegXObject': true,
'paintImageXObject': true,
'paintImageMaskXObject': true,
'shadingFill': true
},
beginDrawing: function canvasGraphicsBeginDrawing(mediaBox) {
var cw = this.ctx.canvas.width, ch = this.ctx.canvas.height;
this.ctx.save();
@ -102,7 +253,13 @@ var CanvasGraphics = (function canvasGraphics() {
this.ctx.transform(0, -1, -1, 0, cw, ch);
break;
}
// Scale so that canvas units are the same as PDF user space units
this.ctx.scale(cw / mediaBox.width, ch / mediaBox.height);
// Move the media left-top corner to the (0,0) canvas position
this.ctx.translate(-mediaBox.x, -mediaBox.y);
if (this.textLayer)
this.textLayer.beginLayout();
},
executeIRQueue: function canvasGraphicsExecuteIRQueue(codeIR,
@ -112,32 +269,39 @@ var CanvasGraphics = (function canvasGraphics() {
var i = executionStartIdx || 0;
var argsArrayLen = argsArray.length;
// Sometimes the IRQueue to execute is empty.
if (argsArrayLen == i) {
return i;
}
var executionEndIdx;
var startTime = Date.now();
var endTime = Date.now() + kExecutionTime;
var objs = this.objs;
var fnName;
var slowCommands = this.slowCommands;
do {
executionEndIdx = Math.min(argsArrayLen, i + kExecutionTimeCheck);
while (true) {
fnName = fnArray[i];
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 (fnName !== 'dependency') {
this[fnName].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 promise isn't resolved yet, add the continueCallback
// to the promise and bail out.
if (!objs.isResolved(depObjId)) {
objs.get(depObjId, continueCallback);
return i;
}
}
}
i++;
// If the entire IRQueue was executed, stop as were done.
if (i == argsArrayLen) {
return i;
@ -146,18 +310,21 @@ var CanvasGraphics = (function canvasGraphics() {
// 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) {
if (continueCallback && slowCommands[fnName] && Date.now() > endTime) {
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();
if (this.textLayer)
this.textLayer.endLayout();
},
// Graphics state
@ -176,6 +343,8 @@ var CanvasGraphics = (function canvasGraphics() {
setDash: function canvasGraphicsSetDash(dashArray, dashPhase) {
this.ctx.mozDash = dashArray;
this.ctx.mozDashOffset = dashPhase;
this.ctx.webkitLineDash = dashArray;
this.ctx.webkitLineDashOffset = dashPhase;
},
setRenderingIntent: function canvasGraphicsSetRenderingIntent(intent) {
TODO('set rendering intent: ' + intent);
@ -394,7 +563,9 @@ var CanvasGraphics = (function canvasGraphics() {
this.ctx.font = rule;
},
setTextRenderingMode: function canvasGraphicsSetTextRenderingMode(mode) {
TODO('text rendering mode: ' + mode);
if (mode >= TextRenderingMode.FILL_ADD_TO_PATH)
TODO('unsupported text rendering mode: ' + mode);
this.current.textRenderingMode = mode;
},
setTextRise: function canvasGraphicsSetTextRise(rise) {
TODO('text rise: ' + rise);
@ -416,23 +587,67 @@ var CanvasGraphics = (function canvasGraphics() {
nextLine: function canvasGraphicsNextLine() {
this.moveText(0, this.current.leading);
},
showText: function canvasGraphicsShowText(text) {
applyTextTransforms: function canvasApplyTransforms() {
var ctx = this.ctx;
var current = this.current;
var textHScale = current.textHScale;
var fontMatrix = current.font.fontMatrix || IDENTITY_MATRIX;
ctx.transform.apply(ctx, current.textMatrix);
ctx.scale(1, -1);
ctx.translate(current.x, -1 * current.y);
ctx.transform.apply(ctx, fontMatrix);
ctx.scale(textHScale, 1);
},
getTextGeometry: function canvasGetTextGeometry() {
var geometry = {};
var ctx = this.ctx;
var font = this.current.font;
var ctxMatrix = ctx.mozCurrentTransform;
if (ctxMatrix) {
var bl = Util.applyTransform([0, 0], ctxMatrix);
var tr = Util.applyTransform([1, 1], ctxMatrix);
geometry.x = bl[0];
geometry.y = bl[1];
geometry.hScale = tr[0] - bl[0];
geometry.vScale = tr[1] - bl[1];
}
geometry.spaceWidth = font.spaceWidth;
return geometry;
},
showText: function canvasGraphicsShowText(str, skipTextSelection) {
var ctx = this.ctx;
var current = this.current;
var font = current.font;
var glyphs = font.charsToGlyphs(text);
var glyphs = font.charsToGlyphs(str);
var fontSize = current.fontSize;
var charSpacing = current.charSpacing;
var wordSpacing = current.wordSpacing;
var textHScale = current.textHScale;
var fontMatrix = font.fontMatrix || IDENTITY_MATRIX;
var textHScale2 = textHScale * fontMatrix[0];
var glyphsLength = glyphs.length;
var textLayer = this.textLayer;
var text = {str: '', length: 0, canvasWidth: 0, geom: {}};
var textSelection = textLayer && !skipTextSelection ? true : false;
var textRenderingMode = current.textRenderingMode;
// Type3 fonts - each glyph is a "mini-PDF"
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);
ctx.scale(textHScale, 1);
ctx.lineWidth /= current.textMatrix[0];
if (textSelection) {
this.save();
ctx.scale(1, -1);
text.geom = this.getTextGeometry();
this.restore();
}
for (var i = 0; i < glyphsLength; ++i) {
var glyph = glyphs[i];
@ -452,18 +667,20 @@ var CanvasGraphics = (function canvasGraphics() {
var width = transformed[0] * fontSize + charSpacing;
ctx.translate(width, 0);
current.x += width;
current.x += width * textHScale;
text.str += glyph.unicode;
text.length++;
text.canvasWidth += 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);
this.applyTextTransforms();
ctx.lineWidth /= current.textMatrix[0] * fontMatrix[0];
ctx.scale(1 / textHScale, 1);
if (textSelection)
text.geom = this.getTextGeometry();
var width = 0;
for (var i = 0; i < glyphsLength; ++i) {
@ -474,36 +691,106 @@ var CanvasGraphics = (function canvasGraphics() {
continue;
}
var unicode = glyph.unicode;
var char = (unicode >= 0x10000) ?
String.fromCharCode(0xD800 | ((unicode - 0x10000) >> 10),
0xDC00 | (unicode & 0x3FF)) : String.fromCharCode(unicode);
var char = glyph.fontChar;
var charWidth = glyph.width * fontSize * 0.001 + charSpacing;
ctx.fillText(char, width, 0);
width += glyph.width * fontSize * 0.001 + charSpacing;
switch (textRenderingMode) {
default: // other unsupported rendering modes
case TextRenderingMode.FILL:
case TextRenderingMode.FILL_ADD_TO_PATH:
ctx.fillText(char, width, 0);
break;
case TextRenderingMode.STROKE:
case TextRenderingMode.STROKE_ADD_TO_PATH:
ctx.strokeText(char, width, 0);
break;
case TextRenderingMode.FILL_STROKE:
case TextRenderingMode.FILL_STROKE_ADD_TO_PATH:
ctx.fillText(char, width, 0);
ctx.strokeText(char, width, 0);
break;
case TextRenderingMode.INVISIBLE:
break;
}
width += charWidth;
text.str += glyph.unicode === ' ' ? '\u00A0' : glyph.unicode;
text.length++;
text.canvasWidth += charWidth;
}
current.x += width;
current.x += width * textHScale2;
ctx.restore();
}
},
if (textSelection)
this.textLayer.appendText(text, font.loadedName, fontSize);
return text;
},
showSpacedText: function canvasGraphicsShowSpacedText(arr) {
var ctx = this.ctx;
var current = this.current;
var font = current.font;
var fontSize = current.fontSize;
var textHScale = current.textHScale;
if (!font.coded)
textHScale *= (font.fontMatrix || IDENTITY_MATRIX)[0];
var arrLength = arr.length;
var textLayer = this.textLayer;
var text = {str: '', length: 0, canvasWidth: 0, geom: {}};
var textSelection = textLayer ? true : false;
if (textSelection) {
ctx.save();
// Type3 fonts - each glyph is a "mini-PDF" (see also showText)
if (font.coded) {
ctx.transform.apply(ctx, current.textMatrix);
ctx.scale(1, -1);
ctx.translate(current.x, -1 * current.y);
ctx.scale(textHScale, 1);
} else
this.applyTextTransforms();
text.geom = this.getTextGeometry();
ctx.restore();
}
for (var i = 0; i < arrLength; ++i) {
var e = arr[i];
if (isNum(e)) {
current.x -= e * 0.001 * fontSize * textHScale;
var spacingLength = -e * 0.001 * fontSize * textHScale;
current.x += spacingLength;
if (textSelection) {
// Emulate precise spacing via HTML spaces
text.canvasWidth += spacingLength;
if (e < 0 && text.geom.spaceWidth > 0) { // avoid div by zero
var numFakeSpaces = Math.round(-e / text.geom.spaceWidth);
if (numFakeSpaces > 0) {
text.str += '\u00A0';
text.length++;
}
}
}
} else if (isString(e)) {
this.showText(e);
var shownText = this.showText(e, true);
if (textSelection) {
if (shownText.str === ' ') {
text.str += '\u00A0';
} else {
text.str += shownText.str;
}
text.canvasWidth += shownText.canvasWidth;
text.length += e.length;
}
} else {
malformed('TJ array element ' + e + ' is not string or num');
}
}
if (textSelection)
this.textLayer.appendText(text, font.loadedName, fontSize);
},
nextLineShowText: function canvasGraphicsNextLineShowText(text) {
this.nextLine();
@ -658,9 +945,9 @@ var CanvasGraphics = (function canvasGraphics() {
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 br = Util.applyTransform([0, height], inv);
var ul = Util.applyTransform([width, 0], inv);
var ur = Util.applyTransform([width, height], 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]);
@ -710,8 +997,8 @@ var CanvasGraphics = (function canvasGraphics() {
},
paintJpegXObject: function canvasGraphicsPaintJpegXObject(objId, w, h) {
var image = this.objs.get(objId);
if (!image) {
var domImage = this.objs.get(objId);
if (!domImage) {
error('Dependent image isn\'t ready yet');
}
@ -721,7 +1008,6 @@ var CanvasGraphics = (function canvasGraphics() {
// 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);
@ -777,7 +1063,11 @@ var CanvasGraphics = (function canvasGraphics() {
this.restore();
},
paintImageXObject: function canvasGraphicsPaintImageXObject(imgData) {
paintImageXObject: function canvasGraphicsPaintImageXObject(objId) {
var imgData = this.objs.get(objId);
if (!imgData)
error('Dependent image isn\'t ready yet');
this.save();
var ctx = this.ctx;
var w = imgData.width;
@ -787,26 +1077,16 @@ var CanvasGraphics = (function canvasGraphics() {
var tmpCanvas = new this.ScratchCanvas(w, h);
var tmpCtx = tmpCanvas.getContext('2d');
var tmpImgData;
this.putBinaryImageData(tmpCtx, imgData, w, h);
// 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();
},
putBinaryImageData: function canvasPutBinaryImageData() {
//
},
// Marked content
markPoint: function canvasGraphicsMarkPoint(tag) {
@ -864,6 +1144,41 @@ var CanvasGraphics = (function canvasGraphics() {
}
};
return constructor;
return CanvasGraphics;
})();
if (!isWorker) {
// Feature detection if the browser can use an Uint8Array directly as imgData.
var canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
var ctx = canvas.getContext('2d');
try {
ctx.putImageData({
width: 1,
height: 1,
data: new Uint8Array(4)
}, 0, 0);
CanvasGraphics.prototype.putBinaryImageData =
function CanvasGraphicsPutBinaryImageDataNative(ctx, imgData) {
ctx.putImageData(imgData, 0, 0);
};
} catch (e) {
CanvasGraphics.prototype.putBinaryImageData =
function CanvasGraphicsPutBinaryImageDataShim(ctx, imgData, w, h) {
var tmpImgData = ctx.getImageData(0, 0, w, h);
// Copy over the imageData pixel by pixel.
var tmpImgDataPixels = tmpImgData.data;
var len = tmpImgDataPixels.length;
while (len--) {
tmpImgDataPixels[len] = imgData.data[len];
}
ctx.putImageData(tmpImgData, 0, 0);
};
}
}

View File

@ -3,13 +3,13 @@
'use strict';
var ColorSpace = (function colorSpaceColorSpace() {
var ColorSpace = (function ColorSpaceClosure() {
// Constructor should define this.numComps, this.defaultColor, this.name
function constructor() {
function ColorSpace() {
error('should not call ColorSpace constructor');
}
constructor.prototype = {
ColorSpace.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) {
@ -22,15 +22,15 @@ var ColorSpace = (function colorSpaceColorSpace() {
}
};
constructor.parse = function colorSpaceParse(cs, xref, res) {
var IR = constructor.parseToIR(cs, xref, res);
ColorSpace.parse = function colorSpaceParse(cs, xref, res) {
var IR = ColorSpace.parseToIR(cs, xref, res);
if (IR instanceof AlternateCS)
return IR;
return constructor.fromIR(IR);
return ColorSpace.fromIR(IR);
};
constructor.fromIR = function colorSpaceFromIR(IR) {
ColorSpace.fromIR = function colorSpaceFromIR(IR) {
var name = isArray(IR) ? IR[0] : IR;
switch (name) {
@ -63,7 +63,7 @@ var ColorSpace = (function colorSpaceColorSpace() {
return null;
};
constructor.parseToIR = function colorSpaceParseToIR(cs, xref, res) {
ColorSpace.parseToIR = function colorSpaceParseToIR(cs, xref, res) {
if (isName(cs)) {
var colorSpaces = xref.fetchIfRef(res.get('ColorSpace'));
if (isDict(colorSpaces)) {
@ -154,8 +154,31 @@ var ColorSpace = (function colorSpaceColorSpace() {
}
return null;
};
/**
* Checks if a decode map matches the default decode map for a color space.
* This handles the general decode maps where there are two values per
* component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color.
* This does not handle Lab, Indexed, or Pattern decode maps since they are
* slightly different.
* @param {Array} decode Decode map (usually from an image).
* @param {Number} n Number of components the color space has.
*/
ColorSpace.isDefaultDecode = function colorSpaceIsDefaultDecode(decode, n) {
if (!decode)
return true;
return constructor;
if (n * 2 !== decode.length) {
warning('The decode map is not the correct length');
return true;
}
for (var i = 0, ii = decode.length; i < ii; i += 2) {
if (decode[i] != 0 || decode[i + 1] != 1)
return false;
}
return true;
};
return ColorSpace;
})();
/**
@ -164,8 +187,8 @@ var ColorSpace = (function colorSpaceColorSpace() {
* Both color spaces use a tinting function to convert colors to a base color
* space.
*/
var AlternateCS = (function alternateCS() {
function constructor(numComps, base, tintFn) {
var AlternateCS = (function AlternateCSClosure() {
function AlternateCS(numComps, base, tintFn) {
this.name = 'Alternate';
this.numComps = numComps;
this.defaultColor = [];
@ -175,7 +198,7 @@ var AlternateCS = (function alternateCS() {
this.tintFn = tintFn;
}
constructor.prototype = {
AlternateCS.prototype = {
getRgb: function altcs_getRgb(color) {
var tinted = this.tintFn(color);
return this.base.getRgb(tinted);
@ -200,24 +223,27 @@ var AlternateCS = (function alternateCS() {
baseBuf[pos++] = 255 * tinted[j];
}
return base.getRgbBuffer(baseBuf, 8);
},
isDefaultDecode: function altcs_isDefaultDecode(decodeMap) {
return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
}
};
return constructor;
return AlternateCS;
})();
var PatternCS = (function patternCS() {
function constructor(baseCS) {
var PatternCS = (function PatternCSClosure() {
function PatternCS(baseCS) {
this.name = 'Pattern';
this.base = baseCS;
}
constructor.prototype = {};
PatternCS.prototype = {};
return constructor;
return PatternCS;
})();
var IndexedCS = (function indexedCS() {
function constructor(base, highVal, lookup) {
var IndexedCS = (function IndexedCSClosure() {
function IndexedCS(base, highVal, lookup) {
this.name = 'Indexed';
this.numComps = 1;
this.defaultColor = [0];
@ -240,7 +266,7 @@ var IndexedCS = (function indexedCS() {
this.lookup = lookupArray;
}
constructor.prototype = {
IndexedCS.prototype = {
getRgb: function indexcs_getRgb(color) {
var numComps = this.base.numComps;
var start = color[0] * numComps;
@ -267,19 +293,23 @@ var IndexedCS = (function indexedCS() {
}
return base.getRgbBuffer(baseBuf, 8);
},
isDefaultDecode: function indexcs_isDefaultDecode(decodeMap) {
// indexed color maps shouldn't be changed
return true;
}
};
return constructor;
return IndexedCS;
})();
var DeviceGrayCS = (function deviceGrayCS() {
function constructor() {
var DeviceGrayCS = (function DeviceGrayCSClosure() {
function DeviceGrayCS() {
this.name = 'DeviceGray';
this.numComps = 1;
this.defaultColor = [0];
}
constructor.prototype = {
DeviceGrayCS.prototype = {
getRgb: function graycs_getRgb(color) {
var c = color[0];
return [c, c, c];
@ -295,18 +325,21 @@ var DeviceGrayCS = (function deviceGrayCS() {
rgbBuf[j++] = c;
}
return rgbBuf;
},
isDefaultDecode: function graycs_isDefaultDecode(decodeMap) {
return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
}
};
return constructor;
return DeviceGrayCS;
})();
var DeviceRgbCS = (function deviceRgbCS() {
function constructor() {
var DeviceRgbCS = (function DeviceRgbCSClosure() {
function DeviceRgbCS() {
this.name = 'DeviceRGB';
this.numComps = 3;
this.defaultColor = [0, 0, 0];
}
constructor.prototype = {
DeviceRgbCS.prototype = {
getRgb: function rgbcs_getRgb(color) {
return color;
},
@ -319,18 +352,21 @@ var DeviceRgbCS = (function deviceRgbCS() {
for (i = 0; i < length; ++i)
rgbBuf[i] = (scale * input[i]) | 0;
return rgbBuf;
},
isDefaultDecode: function rgbcs_isDefaultDecode(decodeMap) {
return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
}
};
return constructor;
return DeviceRgbCS;
})();
var DeviceCmykCS = (function deviceCmykCS() {
function constructor() {
var DeviceCmykCS = (function DeviceCmykCSClosure() {
function DeviceCmykCS() {
this.name = 'DeviceCMYK';
this.numComps = 4;
this.defaultColor = [0, 0, 0, 1];
}
constructor.prototype = {
DeviceCmykCS.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;
@ -403,9 +439,12 @@ var DeviceCmykCS = (function deviceCmykCS() {
}
return rgbBuf;
},
isDefaultDecode: function cmykcs_isDefaultDecode(decodeMap) {
return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
}
};
return constructor;
return DeviceCmykCS;
})();

View File

@ -5,6 +5,8 @@
var globalScope = (typeof window === 'undefined') ? this : window;
var isWorker = (typeof window == 'undefined');
var ERRORS = 0, WARNINGS = 1, TODOS = 5;
var verbosity = WARNINGS;
@ -31,7 +33,7 @@ function getPdf(arg, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', params.url);
xhr.mozResponseType = xhr.responseType = 'arraybuffer';
xhr.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
xhr.expected = (params.url.indexOf('file:') === 0) ? 0 : 200;
if ('progress' in params)
xhr.onprogress = params.progress || undefined;
@ -39,19 +41,23 @@ function getPdf(arg, callback) {
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.onreadystatechange = function getPdfOnreadystatechange(e) {
if (xhr.readyState === 4) {
if (xhr.status === xhr.expected) {
var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse ||
xhr.responseArrayBuffer || xhr.response);
callback(data);
} else if (params.error) {
params.error(e);
}
}
};
xhr.send(null);
}
globalScope.PDFJS.getPdf = getPdf;
var Page = (function pagePage() {
function constructor(xref, pageNumber, pageDict, ref) {
var Page = (function PageClosure() {
function Page(xref, pageNumber, pageDict, ref) {
this.pageNumber = pageNumber;
this.pageDict = pageDict;
this.stats = {
@ -63,9 +69,11 @@ var Page = (function pagePage() {
};
this.xref = xref;
this.ref = ref;
this.displayReadyPromise = null;
}
constructor.prototype = {
Page.prototype = {
getPageProp: function pageGetPageProp(key) {
return this.xref.fetchIfRef(this.pageDict.get(key));
},
@ -101,9 +109,11 @@ var Page = (function pagePage() {
width: this.width,
height: this.height
};
var mediaBox = this.mediaBox;
var offsetX = mediaBox[0], offsetY = mediaBox[1];
if (isArray(obj) && obj.length == 4) {
var tl = this.rotatePoint(obj[0], obj[1]);
var br = this.rotatePoint(obj[2], obj[3]);
var tl = this.rotatePoint(obj[0] - offsetX, obj[1] - offsetY);
var br = this.rotatePoint(obj[2] - offsetX, obj[3] - offsetY);
view.x = Math.min(tl.x, br.x);
view.y = Math.min(tl.y, br.y);
view.width = Math.abs(tl.x - br.x);
@ -156,18 +166,12 @@ var Page = (function pagePage() {
IRQueue, fonts) {
var self = this;
this.IRQueue = IRQueue;
var gfx = new CanvasGraphics(this.ctx, this.objs);
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;
}
self.displayReadyPromise.resolve();
});
};
@ -241,6 +245,7 @@ var Page = (function pagePage() {
startIdx = gfx.executeIRQueue(IRQueue, startIdx, next);
if (startIdx == length) {
self.stats.render = Date.now();
gfx.endDrawing();
if (callback) callback();
}
}
@ -262,57 +267,160 @@ var Page = (function pagePage() {
}
},
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) {
this.ctx = ctx;
this.callback = callback;
startRendering: function pageStartRendering(ctx, callback, textLayer) {
this.startRenderingTime = Date.now();
this.pdf.startRendering(this);
// If there is no displayReadyPromise yet, then the IRQueue was never
// requested before. Make the request and create the promise.
if (!this.displayReadyPromise) {
this.pdf.startRendering(this);
this.displayReadyPromise = new Promise();
}
// Once the IRQueue and fonts are loaded, perform the actual rendering.
this.displayReadyPromise.then(
function pageDisplayReadyPromise() {
var gfx = new CanvasGraphics(ctx, this.objs, textLayer);
try {
this.display(gfx, callback);
} catch (e) {
if (callback)
callback(e);
else
throw e;
}
}.bind(this),
function pageDisplayReadPromiseError(reason) {
if (callback)
callback(reason);
else
throw reason;
}
);
}
};
return constructor;
return Page;
})();
/**
@ -325,8 +433,8 @@ var Page = (function pagePage() {
* 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) {
var PDFDocModel = (function PDFDocModelClosure() {
function PDFDocModel(arg, callback) {
if (isStream(arg))
init.call(this, arg);
else if (isArrayBuffer(arg))
@ -339,6 +447,7 @@ var PDFDocModel = (function pdfDoc() {
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) {
@ -357,7 +466,7 @@ var PDFDocModel = (function pdfDoc() {
return true; /* found */
}
constructor.prototype = {
PDFDocModel.prototype = {
get linearization() {
var length = this.stream.length;
var linearization = false;
@ -379,12 +488,17 @@ var PDFDocModel = (function pdfDoc() {
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)) {
// Find startxref by jumping backward from the end of the file.
var step = 1024;
var found = false, pos = stream.end;
while (!found && pos > 0) {
pos -= step - 'startxref'.length;
if (pos < 0)
pos = 0;
stream.pos = pos;
found = find(stream, 'startxref', step, true);
}
if (found) {
stream.skip(9);
var ch;
do {
@ -425,10 +539,19 @@ var PDFDocModel = (function pdfDoc() {
},
setup: function pdfDocSetup(ownerPassword, userPassword) {
this.checkHeader();
this.xref = new XRef(this.stream,
this.startXRef,
this.mainXRefEntriesOffset);
this.catalog = new Catalog(this.xref);
var xref = new XRef(this.stream,
this.startXRef,
this.mainXRefEntriesOffset);
this.xref = xref;
this.catalog = new Catalog(xref);
if (xref.trailer && xref.trailer.has('ID')) {
var fileID = '';
var id = xref.fetchIfRef(xref.trailer.get('ID'))[0];
id.split('').forEach(function(el) {
fileID += Number(el.charCodeAt(0)).toString(16);
});
this.fileID = fileID;
}
},
get numPages() {
var linearization = this.linearization;
@ -436,16 +559,32 @@ var PDFDocModel = (function pdfDoc() {
// 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);
}
};
return constructor;
return PDFDocModel;
})();
var PDFDoc = (function pdfDoc() {
function constructor(arg, callback) {
var PDFDoc = (function PDFDocClosure() {
function PDFDoc(arg, callback) {
var stream = null;
var data = null;
@ -462,7 +601,7 @@ var PDFDoc = (function pdfDoc() {
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();
@ -481,39 +620,38 @@ var PDFDoc = (function pdfDoc() {
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();
}
constructor.prototype = {
PDFDoc.prototype = {
setupFakeWorker: function() {
// If we don't use a worker, just post/sendMessage to the main thread.
var fakeWorker = {
@ -549,8 +687,12 @@ var PDFDoc = (function pdfDoc() {
switch (type) {
case 'JpegStream':
var IR = data[2];
new JpegImageLoader(id, IR, this.objs);
var imageData = data[2];
loadJpegStream(id, imageData, this.objs);
break;
case 'Image':
var imageData = data[2];
this.objs.resolve(id, imageData);
break;
case 'Font':
var name = data[2];
@ -558,20 +700,9 @@ var PDFDoc = (function pdfDoc() {
var properties = data[4];
if (file) {
// Rewrap the ArrayBuffer in a stream.
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;
}
file = new Stream(file, 0, file.length, fontFileDict);
}
// For now, resolve the font object here direclty. The real font
@ -599,6 +730,49 @@ var PDFDoc = (function pdfDoc() {
}
}.bind(this));
messageHandler.on('page_error', function pdfDocError(data) {
var page = this.pageCache[data.pageNum];
if (page.displayReadyPromise)
page.displayReadyPromise.reject(data.error);
else
throw data.error;
}, this);
messageHandler.on('jpeg_decode', function(data, promise) {
var imageData = data[0];
var components = data[1];
if (components != 3 && components != 1)
error('Only 3 component or 1 component can be returned');
var img = new Image();
img.onload = (function jpegImageLoaderOnload() {
var width = img.width;
var height = img.height;
var size = width * height;
var rgbaLength = size * 4;
var buf = new Uint8Array(size * components);
var tmpCanvas = new ScratchCanvas(width, height);
var tmpCtx = tmpCanvas.getContext('2d');
tmpCtx.drawImage(img, 0, 0);
var data = tmpCtx.getImageData(0, 0, width, height).data;
if (components == 3) {
for (var i = 0, j = 0; i < rgbaLength; i += 4, j += 3) {
buf[j] = data[i];
buf[j + 1] = data[i + 1];
buf[j + 2] = data[i + 2];
}
} else if (components == 1) {
for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) {
buf[j] = data[i];
}
}
promise.resolve({ data: buf, width: width, height: height});
}).bind(this);
var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
img.src = src;
});
setTimeout(function pdfDocFontReadySetTimeout() {
messageHandler.send('doc', this.data);
this.workerReadyPromise.resolve(true);
@ -645,7 +819,7 @@ var PDFDoc = (function pdfDoc() {
}
};
return constructor;
return PDFDoc;
})();
globalScope.PDFJS.PDFDoc = PDFDoc;

View File

@ -3,8 +3,8 @@
'use strict';
var ARCFourCipher = (function arcFourCipher() {
function constructor(key) {
var ARCFourCipher = (function ARCFourCipherClosure() {
function ARCFourCipher(key) {
this.a = 0;
this.b = 0;
var s = new Uint8Array(256);
@ -20,7 +20,7 @@ var ARCFourCipher = (function arcFourCipher() {
this.s = s;
}
constructor.prototype = {
ARCFourCipher.prototype = {
encryptBlock: function arcFourCipherEncryptBlock(data) {
var i, n = data.length, tmp, tmp2;
var a = this.a, b = this.b, s = this.s;
@ -39,12 +39,12 @@ var ARCFourCipher = (function arcFourCipher() {
return output;
}
};
constructor.prototype.decryptBlock = constructor.prototype.encryptBlock;
ARCFourCipher.prototype.decryptBlock = ARCFourCipher.prototype.encryptBlock;
return constructor;
return ARCFourCipher;
})();
var calculateMD5 = (function calculateMD5() {
var calculateMD5 = (function calculateMD5Closure() {
var r = new Uint8Array([
7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
@ -128,20 +128,20 @@ var calculateMD5 = (function calculateMD5() {
return hash;
})();
var NullCipher = (function nullCipher() {
function constructor() {
var NullCipher = (function NullCipherClosure() {
function NullCipher() {
}
constructor.prototype = {
NullCipher.prototype = {
decryptBlock: function nullCipherDecryptBlock(data) {
return data;
}
};
return constructor;
return NullCipher;
})();
var AES128Cipher = (function aes128Cipher() {
var AES128Cipher = (function AES128CipherClosure() {
var rcon = new Uint8Array([
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c,
0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a,
@ -330,7 +330,7 @@ var AES128Cipher = (function aes128Cipher() {
return state;
}
function constructor(key) {
function AES128Cipher(key) {
this.key = expandKey128(key);
this.buffer = new Uint8Array(16);
this.bufferPosition = 0;
@ -370,7 +370,7 @@ var AES128Cipher = (function aes128Cipher() {
return output;
}
constructor.prototype = {
AES128Cipher.prototype = {
decryptBlock: function aes128CipherDecryptBlock(data) {
var i, sourceLength = data.length;
var buffer = this.buffer, bufferLength = this.bufferPosition;
@ -391,15 +391,15 @@ var AES128Cipher = (function aes128Cipher() {
}
};
return constructor;
return AES128Cipher;
})();
var CipherTransform = (function cipherTransform() {
function constructor(stringCipherConstructor, streamCipherConstructor) {
var CipherTransform = (function CipherTransformClosure() {
function CipherTransform(stringCipherConstructor, streamCipherConstructor) {
this.stringCipherConstructor = stringCipherConstructor;
this.streamCipherConstructor = streamCipherConstructor;
}
constructor.prototype = {
CipherTransform.prototype = {
createStream: function cipherTransformCreateStream(stream) {
var cipher = new this.streamCipherConstructor();
return new DecryptStream(stream,
@ -415,10 +415,10 @@ var CipherTransform = (function cipherTransform() {
return bytesToString(data);
}
};
return constructor;
return CipherTransform;
})();
var CipherTransformFactory = (function cipherTransformFactory() {
var CipherTransformFactory = (function CipherTransformFactoryClosure() {
function prepareKeyData(fileId, password, ownerPassword, userPassword,
flags, revision, keyLength, encryptMetadata) {
var defaultPasswordBytes = new Uint8Array([
@ -490,7 +490,7 @@ var CipherTransformFactory = (function cipherTransformFactory() {
var identityName = new Name('Identity');
function constructor(dict, fileId, password) {
function CipherTransformFactory(dict, fileId, password) {
var filter = dict.get('Filter');
if (!isName(filter) || filter.name != 'Standard')
error('unknown encryption method');
@ -573,7 +573,7 @@ var CipherTransformFactory = (function cipherTransformFactory() {
return null;
}
constructor.prototype = {
CipherTransformFactory.prototype = {
createCipherTransform: function buildCipherCreateCipherTransform(num,
gen) {
if (this.algorithm == 4) {
@ -592,6 +592,6 @@ var CipherTransformFactory = (function cipherTransformFactory() {
}
};
return constructor;
return CipherTransformFactory;
})();

View File

@ -3,8 +3,8 @@
'use strict';
var PartialEvaluator = (function partialEvaluator() {
function constructor(xref, handler, uniquePrefix) {
var PartialEvaluator = (function PartialEvaluatorClosure() {
function PartialEvaluator(xref, handler, uniquePrefix) {
this.state = new EvalState();
this.stateStack = [];
@ -111,7 +111,7 @@ var PartialEvaluator = (function partialEvaluator() {
EX: 'endCompat'
};
constructor.prototype = {
PartialEvaluator.prototype = {
getIRQueue: function partialEvaluatorGetIRQueue(stream, resources,
queue, dependency) {
@ -155,6 +155,11 @@ var PartialEvaluator = (function partialEvaluator() {
font.loadedName = loadedName;
var translated = font.translated;
// Convert the file to an ArrayBuffer which will be turned back into
// a Stream in the main thread.
if (translated.file)
translated.file = translated.file.getBytes();
handler.send('obj', [
loadedName,
'Font',
@ -179,62 +184,54 @@ var PartialEvaluator = (function partialEvaluator() {
var w = dict.get('Width', 'W');
var h = dict.get('Height', 'H');
if (image instanceof JpegStream && image.isNative) {
var objId = 'img_' + uniquePrefix + (++self.objIdCounter);
handler.send('obj', [objId, 'JpegStream', image.getIR()]);
var imageMask = dict.get('ImageMask', 'IM') || false;
if (imageMask) {
// 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.
// Add the dependency on the image object.
insertDependency([objId]);
// The normal fn.
fn = 'paintJpegXObject';
args = [objId, w, h];
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;
fn = 'paintImageMaskXObject';
args = [imgArray, inverseDecode, width, height];
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);
var objId = 'img_' + uniquePrefix + (++self.objIdCounter);
insertDependency([objId]);
args = [objId, w, h];
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];
var softMask = dict.get('SMask', 'IM') || false;
if (!softMask && image instanceof JpegStream && image.isNative) {
// These JPEGs don't need any more processing so we can just send it.
fn = 'paintJpegXObject';
handler.send('obj', [objId, 'JpegStream', image.getIR()]);
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';
fn = 'paintImageXObject';
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];
PDFImage.buildImage(function(imageObj) {
var drawWidth = imageObj.drawWidth;
var drawHeight = imageObj.drawHeight;
var imgData = {
width: drawWidth,
height: drawHeight,
data: new Uint8Array(drawWidth * drawHeight * 4)
};
var pixels = imgData.data;
imageObj.fillRgbaBuffer(pixels, drawWidth, drawHeight);
handler.send('obj', [objId, 'Image', imgData]);
}, handler, xref, resources, image, inline);
}
uniquePrefix = uniquePrefix || '';
@ -493,6 +490,8 @@ var PartialEvaluator = (function partialEvaluator() {
var baseName = encoding.get('BaseEncoding');
if (baseName)
baseEncoding = Encodings[baseName.name];
else
hasEncoding = false; // base encoding was not provided
// Load the differences between the base and original
if (encoding.has('Differences')) {
@ -512,6 +511,7 @@ var PartialEvaluator = (function partialEvaluator() {
error('Encoding is not a Name nor a Dict');
}
}
properties.differences = differences;
properties.baseEncoding = baseEncoding;
properties.hasEncoding = hasEncoding;
@ -554,9 +554,21 @@ var PartialEvaluator = (function partialEvaluator() {
var startRange = tokens[j];
var endRange = tokens[j + 1];
var code = tokens[j + 2];
while (startRange <= endRange) {
charToUnicode[startRange] = code++;
++startRange;
if (code == 0xFFFF) {
// CMap is broken, assuming code == startRange
code = startRange;
}
if (isArray(code)) {
var codeindex = 0;
while (startRange <= endRange) {
charToUnicode[startRange] = code[codeindex++];
++startRange;
}
} else {
while (startRange <= endRange) {
charToUnicode[startRange] = code++;
++startRange;
}
}
}
break;
@ -595,9 +607,18 @@ var PartialEvaluator = (function partialEvaluator() {
}
} else if (byte == 0x3E) {
if (token.length) {
// parsing hex number
tokens.push(parseInt(token, 16));
token = '';
if (token.length <= 4) {
// parsing hex number
tokens.push(parseInt(token, 16));
token = '';
} else {
// parsing hex UTF-16BE numbers
var str = [];
for (var i = 0, ii = token.length; i < ii; i += 4)
str.push(parseInt(token.substr(i, 4), 16));
tokens.push(String.fromCharCode.apply(String, str));
token = '';
}
}
} else {
token += String.fromCharCode(byte);
@ -829,11 +850,11 @@ var PartialEvaluator = (function partialEvaluator() {
}
};
return constructor;
return PartialEvaluator;
})();
var EvalState = (function evalState() {
function constructor() {
var EvalState = (function EvalStateClosure() {
function EvalState() {
// Are soft masks and alpha values shapes or opacities?
this.alphaIsShape = false;
this.fontSize = 0;
@ -850,8 +871,8 @@ var EvalState = (function evalState() {
this.fillColorSpace = null;
this.strokeColorSpace = null;
}
constructor.prototype = {
EvalState.prototype = {
};
return constructor;
return EvalState;
})();

View File

@ -3,8 +3,6 @@
'use strict';
var isWorker = (typeof window == 'undefined');
/**
* Maximum time to wait for a font to be loaded by font-face rules.
*/
@ -719,20 +717,10 @@ function getUnicodeRangeFor(value) {
return -1;
}
function adaptUnicode(unicode) {
return (unicode <= 0x1F || (unicode >= 127 && unicode < kSizeOfGlyphArea)) ?
unicode + kCmapGlyphOffset : unicode;
}
function isAdaptedUnicode(unicode) {
return unicode >= kCmapGlyphOffset &&
unicode < kCmapGlyphOffset + kSizeOfGlyphArea;
}
function isSpecialUnicode(unicode) {
return (unicode <= 0x1F || (unicode >= 127 && unicode < kSizeOfGlyphArea)) ||
unicode >= kCmapGlyphOffset &&
unicode < kCmapGlyphOffset + kSizeOfGlyphArea;
(unicode >= kCmapGlyphOffset &&
unicode < kCmapGlyphOffset + kSizeOfGlyphArea);
}
/**
@ -743,8 +731,8 @@ function isSpecialUnicode(unicode) {
* var type1Font = new Font("MyFontName", binaryFile, propertiesObject);
* type1Font.bind();
*/
var Font = (function Font() {
var constructor = function font_constructor(name, file, properties) {
var Font = (function FontClosure() {
function Font(name, file, properties) {
this.name = name;
this.coded = properties.coded;
this.charProcIRQueues = properties.charProcIRQueues;
@ -771,16 +759,23 @@ var Font = (function Font() {
this.widths = properties.widths;
this.defaultWidth = properties.defaultWidth;
this.composite = properties.composite;
this.toUnicode = properties.toUnicode;
this.hasEncoding = properties.hasEncoding;
this.fontMatrix = properties.fontMatrix;
if (properties.type == 'Type3')
this.widthMultiplier = 1.0;
if (properties.type == 'Type3') {
this.encoding = properties.baseEncoding;
return;
}
// Trying to fix encoding using glyph CIDSystemInfo.
this.loadCidToUnicode(properties);
if (properties.toUnicode)
this.toUnicode = properties.toUnicode;
else
this.rebuildToUnicode(properties);
if (!file) {
// The file data is not specified. Trying to fix the font name
// to be used with the canvas.font.
@ -832,6 +827,8 @@ var Font = (function Font() {
this.data = data;
this.fontMatrix = properties.fontMatrix;
this.widthMultiplier = !properties.fontMatrix ? 1.0 :
1.0 / properties.fontMatrix[0];
this.encoding = properties.baseEncoding;
this.hasShortCmap = properties.hasShortCmap;
this.loadedName = getUniqueName();
@ -887,6 +884,13 @@ var Font = (function Font() {
String.fromCharCode(value & 0xff);
};
function safeString16(value) {
// clamp value to the 16-bit int range
value = value > 0x7FFF ? 0x7FFF : value < -0x8000 ? -0x8000 : value;
return String.fromCharCode((value >> 8) & 0xff) +
String.fromCharCode(value & 0xff);
};
function string32(value) {
return String.fromCharCode((value >> 24) & 0xff) +
String.fromCharCode((value >> 16) & 0xff) +
@ -961,15 +965,15 @@ var Font = (function Font() {
var ranges = [];
for (var n = 0; n < length; ) {
var start = codes[n].unicode;
var startCode = codes[n].code;
var codeIndices = [codes[n].code];
++n;
var end = start;
while (n < length && end + 1 == codes[n].unicode) {
codeIndices.push(codes[n].code);
++end;
++n;
}
var endCode = codes[n - 1].code;
ranges.push([start, end, startCode, endCode]);
ranges.push([start, end, codeIndices]);
}
return ranges;
@ -1012,17 +1016,16 @@ var Font = (function Font() {
idDeltas += string16(0);
idRangeOffsets += string16(offset);
var startCode = range[2];
var endCode = range[3];
for (var j = startCode; j <= endCode; ++j)
glyphsIds += string16(deltas[j]);
var codes = range[2];
for (var j = 0, jj = codes.length; j < jj; ++j)
glyphsIds += string16(deltas[codes[j]]);
}
} else {
for (var i = 0; i < segCount - 1; i++) {
var range = ranges[i];
var start = range[0];
var end = range[1];
var startCode = range[2];
var startCode = range[2][0];
startCount += string16(start);
endCount += string16(end);
@ -1226,7 +1229,7 @@ var Font = (function Font() {
return nameTable;
}
constructor.prototype = {
Font.prototype = {
name: null,
font: null,
mimetype: null,
@ -1299,7 +1302,7 @@ var Font = (function Font() {
properties.baseEncoding = encoding;
}
function replaceCMapTable(cmap, font, properties) {
function readCMapTable(cmap, font) {
var start = (font.start ? font.start : 0) + cmap.offset;
font.pos = start;
@ -1316,7 +1319,7 @@ var Font = (function Font() {
}
// Check that table are sorted by platformID then encodingID,
records.sort(function fontReplaceCMapTableSort(a, b) {
records.sort(function fontReadCMapTableSort(a, b) {
return ((a.platformID << 16) + a.encodingID) -
((b.platformID << 16) + b.encodingID);
});
@ -1371,16 +1374,15 @@ var Font = (function Font() {
for (var j = 0; j < 256; j++) {
var index = font.getByte();
if (index) {
var unicode = adaptUnicode(j);
glyphs.push({ unicode: unicode, code: j });
glyphs.push({ unicode: j, code: j });
ids.push(index);
}
}
properties.hasShortCmap = true;
createGlyphNameMap(glyphs, ids, properties);
return cmap.data = createCMapTable(glyphs, ids);
return {
glyphs: glyphs,
ids: ids,
hasShortCmap: true
};
} else if (format == 4) {
// re-creating the table in format 4 since the encoding
// might be changed
@ -1432,17 +1434,18 @@ var Font = (function Font() {
var glyphCode = offsetIndex < 0 ? j :
offsets[offsetIndex + j - start];
glyphCode = (glyphCode + delta) & 0xFFFF;
if (glyphCode == 0 || isAdaptedUnicode(j))
if (glyphCode == 0)
continue;
var unicode = adaptUnicode(j);
glyphs.push({ unicode: unicode, code: j });
glyphs.push({ unicode: j, code: j });
ids.push(glyphCode);
}
}
createGlyphNameMap(glyphs, ids, properties);
return cmap.data = createCMapTable(glyphs, ids);
return {
glyphs: glyphs,
ids: ids
};
} else if (format == 6) {
// Format 6 is a 2-bytes dense mapping, which means the font data
// lives glue together even if they are pretty far in the unicode
@ -1457,19 +1460,18 @@ var Font = (function Font() {
for (var j = 0; j < entryCount; j++) {
var glyphCode = int16(font.getBytes(2));
var code = firstCode + j;
if (isAdaptedUnicode(glyphCode))
continue;
var unicode = adaptUnicode(code);
glyphs.push({ unicode: unicode, code: code });
glyphs.push({ unicode: code, code: code });
ids.push(glyphCode);
}
createGlyphNameMap(glyphs, ids, properties);
return cmap.data = createCMapTable(glyphs, ids);
return {
glyphs: glyphs,
ids: ids
};
}
}
return cmap.data;
error('Unsupported cmap table format');
};
function sanitizeMetrics(font, header, metrics, numGlyphs) {
@ -1708,17 +1710,108 @@ var Font = (function Font() {
tables.push(cmap);
}
var glyphs = [];
for (i = 1; i < numGlyphs; i++) {
if (isAdaptedUnicode(i))
continue;
glyphs.push({ unicode: adaptUnicode(i) });
var cidToGidMap = properties.cidToGidMap || [];
var gidToCidMap = [0];
if (cidToGidMap.length > 0) {
for (var j = cidToGidMap.length - 1; j >= 0; j--) {
var gid = cidToGidMap[j];
if (gid)
gidToCidMap[gid] = j;
}
// filling the gaps using CID above the CIDs currently used in font
var nextCid = cidToGidMap.length;
for (var i = 1; i < numGlyphs; i++) {
if (!gidToCidMap[i])
gidToCidMap[i] = nextCid++;
}
}
cmap.data = createCMapTable(glyphs);
var glyphs = [], ids = [];
var usedUnicodes = [];
var unassignedUnicodeItems = [];
for (var i = 1; i < numGlyphs; i++) {
var cid = gidToCidMap[i] || i;
var unicode = this.toUnicode[cid];
if (!unicode || isSpecialUnicode(unicode) ||
unicode in usedUnicodes) {
unassignedUnicodeItems.push(i);
continue;
}
usedUnicodes[unicode] = true;
glyphs.push({ unicode: unicode, code: cid });
ids.push(i);
}
// trying to fit as many unassigned symbols as we can
// in the range allocated for the user defined symbols
var unusedUnicode = kCmapGlyphOffset;
for (var j = 0, jj = unassignedUnicodeItems.length; j < jj; j++) {
var i = unassignedUnicodeItems[j];
var cid = gidToCidMap[i] || i;
while (unusedUnicode in usedUnicodes)
unusedUnicode++;
if (unusedUnicode >= kCmapGlyphOffset + kSizeOfGlyphArea)
break;
var unicode = unusedUnicode++;
this.toUnicode[cid] = unicode;
usedUnicodes[unicode] = true;
glyphs.push({ unicode: unicode, code: cid });
ids.push(i);
}
cmap.data = createCMapTable(glyphs, ids);
} else {
replaceCMapTable(cmap, font, properties);
var cmapTable = readCMapTable(cmap, font);
var glyphs = cmapTable.glyphs;
var ids = cmapTable.ids;
var hasShortCmap = !!cmapTable.hasShortCmap;
var toUnicode = this.toUnicode;
if (toUnicode && toUnicode.length > 0) {
// checking if cmap is just identity map
var isIdentity = true;
for (var i = 0, ii = glyphs.length; i < ii; i++) {
if (glyphs[i].unicode != i + 1) {
isIdentity = false;
break;
}
}
// if it is, replacing with meaningful toUnicode values
if (isIdentity) {
var usedUnicodes = [], unassignedUnicodeItems = [];
for (var i = 0, ii = glyphs.length; i < ii; i++) {
var unicode = toUnicode[i + 1];
if (!unicode || unicode in usedUnicodes) {
unassignedUnicodeItems.push(i);
continue;
}
glyphs[i].unicode = unicode;
usedUnicodes[unicode] = true;
}
var unusedUnicode = kCmapGlyphOffset;
for (var j = 0, jj = unassignedUnicodeItems.length; j < jj; j++) {
var i = unassignedUnicodeItems[j];
while (unusedUnicode in usedUnicodes)
unusedUnicode++;
var cid = i + 1;
// override only if unicode mapping is not specified
if (!(cid in toUnicode))
toUnicode[cid] = unusedUnicode;
glyphs[i].unicode = unusedUnicode++;
}
this.useToUnicode = true;
}
}
properties.hasShortCmap = hasShortCmap;
// remove glyph references outside range of avaialable glyphs
for (var i = 0, ii = ids.length; i < ii; i++) {
if (ids[i] >= numGlyphs)
ids[i] = 0;
}
createGlyphNameMap(glyphs, ids, properties);
this.glyphNameMap = properties.glyphNameMap;
cmap.data = createCMapTable(glyphs, ids);
}
// Rewrite the 'post' table if needed
@ -1808,6 +1901,14 @@ var Font = (function Font() {
}
properties.baseEncoding = encoding;
}
if (properties.subtype == 'CIDFontType0C') {
var toUnicode = [];
for (var i = 0; i < charstrings.length; ++i) {
var charstring = charstrings[i];
toUnicode[charstring.code] = charstring.unicode;
}
this.toUnicode = toUnicode;
}
var fields = {
// PostScript Font Program
@ -1832,9 +1933,9 @@ var Font = (function Font() {
'\x00\x00\x00\x00\x9e\x0b\x7e\x27' + // creation date
'\x00\x00\x00\x00\x9e\x0b\x7e\x27' + // modifification date
'\x00\x00' + // xMin
string16(properties.descent) + // yMin
safeString16(properties.descent) + // yMin
'\x0F\xFF' + // xMax
string16(properties.ascent) + // yMax
safeString16(properties.ascent) + // yMax
string16(properties.italicAngle ? 2 : 0) + // macStyle
'\x00\x11' + // lowestRecPPEM
'\x00\x00' + // fontDirectionHint
@ -1846,15 +1947,15 @@ var Font = (function Font() {
'hhea': (function fontFieldsHhea() {
return stringToArray(
'\x00\x01\x00\x00' + // Version number
string16(properties.ascent) + // Typographic Ascent
string16(properties.descent) + // Typographic Descent
safeString16(properties.ascent) + // Typographic Ascent
safeString16(properties.descent) + // Typographic Descent
'\x00\x00' + // Line Gap
'\xFF\xFF' + // advanceWidthMax
'\x00\x00' + // minLeftSidebearing
'\x00\x00' + // minRightSidebearing
'\x00\x00' + // xMaxExtent
string16(properties.capHeight) + // caretSlopeRise
string16(Math.tan(properties.italicAngle) *
safeString16(properties.capHeight) + // caretSlopeRise
safeString16(Math.tan(properties.italicAngle) *
properties.xHeight) + // caretSlopeRun
'\x00\x00' + // caretOffset
'\x00\x00' + // -reserved-
@ -1868,8 +1969,11 @@ var Font = (function Font() {
// Horizontal metrics
'hmtx': (function fontFieldsHmtx() {
var hmtx = '\x00\x00\x00\x00'; // Fake .notdef
for (var i = 0, ii = charstrings.length; i < ii; i++)
hmtx += string16(charstrings[i].width) + string16(0);
for (var i = 0, ii = charstrings.length; i < ii; i++) {
var charstring = charstrings[i];
var width = 'width' in charstring ? charstring.width : 0;
hmtx += string16(width) + string16(0);
}
return stringToArray(hmtx);
})(),
@ -1898,17 +2002,35 @@ var Font = (function Font() {
return stringToArray(otf.file);
},
loadCidToUnicode: function font_loadCidToUnicode(properties) {
if (properties.cidToGidMap) {
this.cidToUnicode = properties.cidToGidMap;
return;
rebuildToUnicode: function font_rebuildToUnicode(properties) {
var firstChar = properties.firstChar, lastChar = properties.lastChar;
var map = [];
if (properties.composite) {
var isIdentityMap = this.cidToUnicode.length == 0;
for (var i = firstChar, ii = lastChar; i <= ii; i++) {
// TODO missing map the character according font's CMap
var cid = i;
map[i] = isIdentityMap ? cid : this.cidToUnicode[cid];
}
} else {
for (var i = firstChar, ii = lastChar; i <= ii; i++) {
var glyph = properties.differences[i];
if (!glyph)
glyph = properties.baseEncoding[i];
if (!!glyph && (glyph in GlyphsUnicode))
map[i] = GlyphsUnicode[glyph];
}
}
this.toUnicode = map;
},
loadCidToUnicode: function font_loadCidToUnicode(properties) {
if (!properties.cidSystemInfo)
return;
var cidToUnicodeMap = [];
var cidToUnicodeMap = [], unicodeToCIDMap = [];
this.cidToUnicode = cidToUnicodeMap;
this.unicodeToCID = unicodeToCIDMap;
var cidSystemInfo = properties.cidSystemInfo;
var cidToUnicode;
@ -1920,28 +2042,34 @@ var Font = (function Font() {
if (!cidToUnicode)
return; // identity encoding
var glyph = 1, i, j, k, ii;
var cid = 1, i, j, k, ii;
for (i = 0, ii = cidToUnicode.length; i < ii; ++i) {
var unicode = cidToUnicode[i];
if (isArray(unicode)) {
var length = unicode.length;
for (j = 0; j < length; j++)
cidToUnicodeMap[unicode[j]] = glyph;
glyph++;
for (j = 0; j < length; j++) {
cidToUnicodeMap[cid] = unicode[j];
unicodeToCIDMap[unicode[j]] = cid;
}
cid++;
} else if (typeof unicode === 'object') {
var fillLength = unicode.f;
if (fillLength) {
k = unicode.c;
for (j = 0; j < fillLength; ++j) {
cidToUnicodeMap[k] = glyph++;
cidToUnicodeMap[cid] = k;
unicodeToCIDMap[k] = cid;
cid++;
k++;
}
} else
glyph += unicode.s;
cid += unicode.s;
} else if (unicode) {
cidToUnicodeMap[unicode] = glyph++;
cidToUnicodeMap[cid] = unicode;
unicodeToCIDMap[unicode] = cid;
cid++;
} else
glyph++;
cid++;
}
},
@ -1964,7 +2092,7 @@ var Font = (function Font() {
window.btoa(data) + ');');
var rule = "@font-face { font-family:'" + fontName + "';src:" + url + '}';
document.documentElement.firstChild.appendChild(
document.documentElement.getElementsByTagName('head')[0].appendChild(
document.createElement('style'));
var styleSheet = document.styleSheets[document.styleSheets.length - 1];
@ -1973,6 +2101,37 @@ var Font = (function Font() {
return rule;
},
get spaceWidth() {
// trying to estimate space character width
var possibleSpaceReplacements = ['space', 'minus', 'one', 'i'];
var width;
for (var i = 0, ii = possibleSpaceReplacements.length; i < ii; i++) {
var glyphName = possibleSpaceReplacements[i];
// if possible, getting width by glyph name
if (glyphName in this.widths) {
width = this.widths[glyphName];
break;
}
var glyphUnicode = GlyphsUnicode[glyphName];
// finding the charcode via unicodeToCID map
var charcode = 0;
if (this.composite)
charcode = this.unicodeToCID[glyphUnicode];
// ... via toUnicode map
if (!charcode && 'toUnicode' in this)
charcode = this.toUnicode.indexOf(glyphUnicode);
// setting it to unicode if negative or undefined
if (!(charcode > 0))
charcode = glyphUnicode;
// trying to get width via charcode
width = this.widths[charcode];
if (width)
break; // the non-zero width found
}
width = (width || this.defaultWidth) * this.widthMultiplier;
return shadow(this, 'spaceWidth', width);
},
charToGlyph: function fonts_charToGlyph(charcode) {
var unicode, width, codeIRQueue;
@ -1981,30 +2140,30 @@ var Font = (function Font() {
switch (this.type) {
case 'CIDFontType0':
if (this.noUnicodeAdaptation) {
width = this.widths[this.cidToUnicode[charcode]];
width = this.widths[this.unicodeToCID[charcode] || charcode];
unicode = charcode;
break;
}
unicode = adaptUnicode(this.cidToUnicode[charcode] || charcode);
unicode = this.toUnicode[charcode] || charcode;
break;
case 'CIDFontType2':
if (this.noUnicodeAdaptation) {
width = this.widths[this.cidToUnicode[charcode]];
width = this.widths[this.unicodeToCID[charcode] || charcode];
unicode = charcode;
break;
}
unicode = adaptUnicode(this.cidToUnicode[charcode] || charcode);
unicode = this.toUnicode[charcode] || charcode;
break;
case 'Type1':
var glyphName = this.differences[charcode] || this.encoding[charcode];
if (!isNum(width))
width = this.widths[glyphName];
if (this.noUnicodeAdaptation) {
if (!isNum(width))
width = this.widths[glyphName];
unicode = GlyphsUnicode[glyphName] || charcode;
break;
}
unicode = this.glyphNameMap[glyphName] ||
adaptUnicode(GlyphsUnicode[glyphName] || charcode);
GlyphsUnicode[glyphName] || charcode;
break;
case 'Type3':
var glyphName = this.differences[charcode] || this.encoding[charcode];
@ -2012,6 +2171,10 @@ var Font = (function Font() {
unicode = charcode;
break;
case 'TrueType':
if (this.useToUnicode) {
unicode = this.toUnicode[charcode] || charcode;
break;
}
var glyphName = this.differences[charcode] || this.encoding[charcode];
if (!glyphName)
glyphName = Encodings.StandardEncoding[charcode];
@ -2022,16 +2185,16 @@ var Font = (function Font() {
break;
}
if (!this.hasEncoding) {
unicode = adaptUnicode(charcode);
unicode = this.useToUnicode ? this.toUnicode[charcode] : charcode;
break;
}
if (this.hasShortCmap) {
if (this.hasShortCmap && false) {
var j = Encodings.MacRomanEncoding.indexOf(glyphName);
unicode = j >= 0 && !isSpecialUnicode(j) ? j :
unicode = j >= 0 ? j :
this.glyphNameMap[glyphName];
} else {
unicode = glyphName in GlyphsUnicode ?
adaptUnicode(GlyphsUnicode[glyphName]) :
GlyphsUnicode[glyphName] :
this.glyphNameMap[glyphName];
}
break;
@ -2039,14 +2202,23 @@ var Font = (function Font() {
warn('Unsupported font type: ' + this.type);
break;
}
var unicodeChars = !('toUnicode' in this) ? charcode :
this.toUnicode[charcode] || charcode;
if (typeof unicodeChars === 'number')
unicodeChars = String.fromCharCode(unicodeChars);
width = (isNum(width) ? width : this.defaultWidth) * this.widthMultiplier;
return {
unicode: unicode,
width: isNum(width) ? width : this.defaultWidth,
fontChar: String.fromCharCode(unicode),
unicode: unicodeChars,
width: width,
codeIRQueue: codeIRQueue
};
},
charsToGlyphs: function fonts_chars2Glyphs(chars) {
charsToGlyphs: function fonts_charsToGlyphs(chars) {
var charsCache = this.charsCache;
var glyphs;
@ -2094,7 +2266,7 @@ var Font = (function Font() {
}
};
return constructor;
return Font;
})();
/*
@ -2753,22 +2925,13 @@ CFF.prototype = {
getOrderedCharStrings: function cff_getOrderedCharStrings(glyphs,
properties) {
var charstrings = [];
var reverseMapping = {};
var encoding = properties.baseEncoding;
var i, length, glyphName;
for (i = 0, length = encoding.length; i < length; ++i) {
glyphName = encoding[i];
if (!glyphName || isSpecialUnicode(i))
continue;
reverseMapping[glyphName] = i;
}
reverseMapping['.notdef'] = 0;
var unusedUnicode = kCmapGlyphOffset;
for (i = 0, length = glyphs.length; i < length; i++) {
var item = glyphs[i];
var glyphName = item.glyph;
var unicode = glyphName in reverseMapping ?
reverseMapping[glyphName] : unusedUnicode++;
var unicode = glyphName in GlyphsUnicode ?
GlyphsUnicode[glyphName] : unusedUnicode++;
charstrings.push({
glyph: glyphName,
unicode: unicode,
@ -3013,9 +3176,9 @@ CFF.prototype = {
}
};
var Type2CFF = (function type2CFF() {
var Type2CFF = (function Type2CFFClosure() {
// TODO: replace parsing code with the Type2Parser in font_utils.js
function constructor(file, properties) {
function Type2CFF(file, properties) {
var bytes = file.getBytes();
this.bytes = bytes;
this.properties = properties;
@ -3023,7 +3186,7 @@ var Type2CFF = (function type2CFF() {
this.data = this.parse();
}
constructor.prototype = {
Type2CFF.prototype = {
parse: function cff_parse() {
var header = this.parseHeader();
var properties = this.properties;
@ -3055,16 +3218,14 @@ var Type2CFF = (function type2CFF() {
}
var charStrings = this.parseIndex(topDict.CharStrings);
var charset = this.parseCharsets(topDict.charset,
charStrings.length, strings);
var encoding = this.parseEncoding(topDict.Encoding, properties,
strings, charset);
var charset, encoding;
var isCIDFont = properties.subtype == 'CIDFontType0C';
if (isCIDFont) {
charset = [];
charset.length = charStrings.length;
charset = ['.notdef'];
for (var i = 1, ii = charStrings.length; i < ii; ++i)
charset.push('glyph' + i);
encoding = this.parseCidMap(topDict.charset,
charStrings.length);
} else {
@ -3133,38 +3294,44 @@ var Type2CFF = (function type2CFF() {
var charstrings = [];
var unicodeUsed = [];
var unassignedUnicodeItems = [];
var inverseEncoding = [];
for (var charcode in encoding)
inverseEncoding[encoding[charcode]] = charcode | 0;
for (var i = 0, ii = charsets.length; i < ii; i++) {
var glyph = charsets[i];
var encodingFound = false;
for (var charcode in encoding) {
if (encoding[charcode] == i) {
var code = charcode | 0;
charstrings.push({
unicode: adaptUnicode(code),
code: code,
gid: i,
glyph: glyph
});
unicodeUsed[code] = true;
encodingFound = true;
break;
}
if (glyph == '.notdef') {
charstrings.push({
unicode: 0,
code: 0,
gid: i,
glyph: glyph
});
continue;
}
if (!encodingFound) {
var code = inverseEncoding[i];
if (!code || isSpecialUnicode(code)) {
unassignedUnicodeItems.push(i);
continue;
}
charstrings.push({
unicode: code,
code: code,
gid: i,
glyph: glyph
});
unicodeUsed[code] = true;
}
var nextUnusedUnicode = 0x21;
var nextUnusedUnicode = kCmapGlyphOffset;
for (var j = 0, jj = unassignedUnicodeItems.length; j < jj; ++j) {
var i = unassignedUnicodeItems[j];
// giving unicode value anyway
while (unicodeUsed[nextUnusedUnicode])
while (nextUnusedUnicode in unicodeUsed)
nextUnusedUnicode++;
var code = nextUnusedUnicode++;
var unicode = nextUnusedUnicode++;
charstrings.push({
unicode: adaptUnicode(code),
code: code,
unicode: unicode,
code: inverseEncoding[i] || 0,
gid: i,
glyph: charsets[i]
});
@ -3563,6 +3730,6 @@ var Type2CFF = (function type2CFF() {
}
};
return constructor;
return Type2CFF;
})();

View File

@ -3,7 +3,7 @@
'use strict';
var PDFFunction = (function pdfFunction() {
var PDFFunction = (function PDFFunctionClosure() {
var CONSTRUCT_SAMPLED = 0;
var CONSTRUCT_INTERPOLATED = 2;
var CONSTRUCT_STICHED = 3;
@ -270,7 +270,6 @@ var PDFFunction = (function pdfFunction() {
constructStiched: function pdfFunctionConstructStiched(fn, dict, xref) {
var domain = dict.get('Domain');
var range = dict.get('Range');
if (!domain)
error('No domain');
@ -279,13 +278,13 @@ var PDFFunction = (function pdfFunction() {
if (inputSize != 1)
error('Bad domain for stiched function');
var fnRefs = dict.get('Functions');
var fnRefs = xref.fetchIfRef(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');
var bounds = xref.fetchIfRef(dict.get('Bounds'));
var encode = xref.fetchIfRef(dict.get('Encode'));
return [CONSTRUCT_STICHED, domain, bounds, encode, fns];
},
@ -336,16 +335,550 @@ var PDFFunction = (function pdfFunction() {
};
},
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

@ -4287,6 +4287,7 @@ var GlyphsUnicode = {
zretroflexhook: 0x0290,
zstroke: 0x01B6,
zuhiragana: 0x305A,
zukatakana: 0x30BA
zukatakana: 0x30BA,
'.notdef': 0x0000
};

View File

@ -3,8 +3,37 @@
'use strict';
var PDFImage = (function pdfImage() {
function constructor(xref, res, image, inline) {
var PDFImage = (function PDFImageClosure() {
/**
* Decode the image in the main thread if it supported. Resovles the promise
* when the image data is ready.
*/
function handleImageData(handler, xref, res, image, promise) {
if (image instanceof JpegStream && image.isNative) {
// For natively supported jpegs send them to the main thread for decoding.
var dict = image.dict;
var colorSpace = dict.get('ColorSpace', 'CS');
colorSpace = ColorSpace.parse(colorSpace, xref, res);
var numComps = colorSpace.numComps;
handler.send('jpeg_decode', [image.getIR(), numComps], function(message) {
var data = message.data;
var stream = new Stream(data, 0, data.length, image.dict);
promise.resolve(stream);
});
} else {
promise.resolve(image);
}
}
/**
* Decode and clamp a value. The formula is different from the spec because we
* don't decode to float range [0,1], we decode it in the [0,max] range.
*/
function decodeAndClamp(value, addend, coefficient, max) {
value = addend + value * coefficient;
// Clamp the value to the range
return value < 0 ? 0 : value > max ? max : value;
}
function PDFImage(xref, res, image, inline, smask) {
this.image = image;
if (image.getParams) {
// JPX/JPEG2000 streams directly contain bits per component
@ -49,34 +78,142 @@ var PDFImage = (function pdfImage() {
}
this.decode = dict.get('Decode', 'D');
this.needsDecode = false;
if (this.decode && this.colorSpace &&
!this.colorSpace.isDefaultDecode(this.decode)) {
this.needsDecode = true;
// Do some preprocessing to avoid more math.
var max = (1 << bitsPerComponent) - 1;
this.decodeCoefficients = [];
this.decodeAddends = [];
for (var i = 0, j = 0; i < this.decode.length; i += 2, ++j) {
var dmin = this.decode[i];
var dmax = this.decode[i + 1];
this.decodeCoefficients[j] = dmax - dmin;
this.decodeAddends[j] = max * dmin;
}
}
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);
this.smask = new PDFImage(xref, res, smask, false);
}
}
/**
* Handles processing of image data and calls the callback with an argument
* of a PDFImage when the image is ready to be used.
*/
PDFImage.buildImage = function buildImage(callback, handler, xref, res,
image, inline) {
var imageDataPromise = new Promise();
var smaskPromise = new Promise();
// The image data and smask data may not be ready yet, wait till both are
// resolved.
Promise.all([imageDataPromise, smaskPromise]).then(function(results) {
var imageData = results[0], smaskData = results[1];
var image = new PDFImage(xref, res, imageData, inline, smaskData);
callback(image);
});
constructor.prototype = {
getComponents: function getComponents(buffer, decodeMap) {
handleImageData(handler, xref, res, image, imageDataPromise);
var smask = xref.fetchIfRef(image.dict.get('SMask'));
if (smask)
handleImageData(handler, xref, res, smask, smaskPromise);
else
smaskPromise.resolve(null);
};
/**
* Resize an image using the nearest neighbor algorithm. Currently only
* supports one and three component images.
* @param {TypedArray} pixels The original image with one component.
* @param {Number} bpc Number of bits per component.
* @param {Number} components Number of color components, 1 or 3 is supported.
* @param {Number} w1 Original width.
* @param {Number} h1 Original height.
* @param {Number} w2 New width.
* @param {Number} h2 New height.
* @return {TypedArray} Resized image data.
*/
PDFImage.resize = function resize(pixels, bpc, components, w1, h1, w2, h2) {
var length = w2 * h2 * components;
var temp = bpc <= 8 ? new Uint8Array(length) :
bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length);
var xRatio = w1 / w2;
var yRatio = h1 / h2;
var px, py, newIndex, oldIndex;
for (var i = 0; i < h2; i++) {
for (var j = 0; j < w2; j++) {
px = Math.floor(j * xRatio);
py = Math.floor(i * yRatio);
newIndex = (i * w2) + j;
oldIndex = ((py * w1) + px);
if (components === 1) {
temp[newIndex] = pixels[oldIndex];
} else if (components === 3) {
newIndex *= 3;
oldIndex *= 3;
temp[newIndex] = pixels[oldIndex];
temp[newIndex + 1] = pixels[oldIndex + 1];
temp[newIndex + 2] = pixels[oldIndex + 2];
}
}
}
return temp;
};
PDFImage.prototype = {
get drawWidth() {
if (!this.smask)
return this.width;
return Math.max(this.width, this.smask.width);
},
get drawHeight() {
if (!this.smask)
return this.height;
return Math.max(this.height, this.smask.height);
},
getComponents: function getComponents(buffer) {
var bpc = this.bpc;
if (bpc == 8)
var needsDecode = this.needsDecode;
var decodeMap = this.decode;
// This image doesn't require any extra work.
if (bpc == 8 && !needsDecode)
return buffer;
var bufferLength = buffer.length;
var width = this.width;
var height = this.height;
var numComps = this.numComps;
var length = width * height;
var length = width * height * numComps;
var bufferPos = 0;
var output = bpc <= 8 ? new Uint8Array(length) :
bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length);
var rowComps = width * numComps;
var decodeAddends, decodeCoefficients;
if (needsDecode) {
decodeAddends = this.decodeAddends;
decodeCoefficients = this.decodeCoefficients;
}
var max = (1 << bpc) - 1;
if (bpc == 1) {
if (bpc == 8) {
// Optimization for reading 8 bpc images that have a decode.
for (var i = 0, ii = length; i < ii; ++i) {
var compIndex = i % numComps;
var value = buffer[i];
value = decodeAndClamp(value, decodeAddends[compIndex],
decodeCoefficients[compIndex], max);
output[i] = value;
}
} else if (bpc == 1) {
// Optimization for reading 1 bpc images.
var valueZero = 0, valueOne = 1;
if (decodeMap) {
valueZero = decodeMap[0] ? 1 : 0;
@ -101,8 +238,7 @@ var PDFImage = (function pdfImage() {
output[i] = !(buf & mask) ? valueZero : valueOne;
}
} else {
if (decodeMap != null)
TODO('interpolate component values');
// The general case that handles all other bpc values.
var bits = 0, buf = 0;
for (var i = 0, ii = length; i < ii; ++i) {
if (i % rowComps == 0) {
@ -116,41 +252,34 @@ var PDFImage = (function pdfImage() {
}
var remainingBits = bits - bpc;
output[i] = buf >> remainingBits;
var value = buf >> remainingBits;
if (needsDecode) {
var compIndex = i % numComps;
value = decodeAndClamp(value, decodeAddends[compIndex],
decodeCoefficients[compIndex], max);
}
output[i] = value;
buf = buf & ((1 << remainingBits) - 1);
bits = remainingBits;
}
}
return output;
},
getOpacity: function getOpacity() {
getOpacity: function getOpacity(width, height) {
var smask = this.smask;
var width = this.width;
var height = this.height;
var buf = new Uint8Array(width * height);
var originalWidth = this.width;
var originalHeight = this.height;
var buf;
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);
buf = new Uint8Array(sw * sh);
smask.fillGrayBuffer(buf);
return buf;
if (sw != width || sh != height)
buf = PDFImage.resize(buf, smask.bps, 1, sw, sh, width, height);
} else {
buf = new Uint8Array(width * height);
for (var i = 0, ii = width * height; i < ii; ++i)
buf[i] = 255;
}
@ -159,8 +288,7 @@ var PDFImage = (function pdfImage() {
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 imgArray = this.getImageBytes(bitStrideLength * height);
var imgArrayPos = 0;
var i, j, mask, buf;
// removing making non-masked pixels transparent
@ -180,21 +308,23 @@ var PDFImage = (function pdfImage() {
}
}
},
fillRgbaBuffer: function fillRgbaBuffer(buffer, decodeMap) {
fillRgbaBuffer: function fillRgbaBuffer(buffer, width, height) {
var numComps = this.numComps;
var width = this.width;
var height = this.height;
var originalWidth = this.width;
var originalHeight = 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 rowBytes = (originalWidth * numComps * bpc + 7) >> 3;
var imgArray = this.getImageBytes(originalHeight * rowBytes);
var comps = this.colorSpace.getRgbBuffer(
this.getComponents(imgArray, decodeMap), bpc);
this.getComponents(imgArray), bpc);
if (originalWidth != width || originalHeight != height)
comps = PDFImage.resize(comps, this.bpc, 3, originalWidth,
originalHeight, width, height);
var compsPos = 0;
var opacity = this.getOpacity();
var opacity = this.getOpacity(width, height);
var opacityPos = 0;
var length = width * height * 4;
@ -216,42 +346,28 @@ var PDFImage = (function pdfImage() {
// rows start at byte boundary;
var rowBytes = (width * numComps * bpc + 7) >> 3;
this.image.reset();
var imgArray = this.image.getBytes(height * rowBytes);
var imgArray = this.getImageBytes(height * rowBytes);
var comps = this.getComponents(imgArray);
var length = width * height;
// we aren't using a colorspace so we need to scale the value
var scale = 255 / ((1 << bpc) - 1);
for (var i = 0; i < length; ++i)
buffer[i] = comps[i];
buffer[i] = (scale * comps[i]) | 0;
},
getImageBytes: function getImageBytes(length) {
this.image.reset();
return this.image.getBytes(length);
}
};
return constructor;
return PDFImage;
})();
var JpegImageLoader = (function jpegImage() {
function JpegImageLoader(objId, imageData, objs) {
var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
var img = new Image();
img.onload = (function jpegImageLoaderOnload() {
this.loaded = true;
objs.resolve(objId, this);
if (this.onLoad)
this.onLoad();
}).bind(this);
img.src = src;
this.domImage = img;
}
JpegImageLoader.prototype = {
getImage: function jpegImageLoaderGetImage() {
return this.domImage;
}
};
return JpegImageLoader;
})();
function loadJpegStream(id, imageData, objs) {
var img = new Image();
img.onload = (function jpegImageLoaderOnload() {
objs.resolve(id, img);
});
img.src = 'data:image/jpeg;base64,' + window.btoa(imageData);
}

View File

@ -3,6 +3,9 @@
'use strict';
// The Metrics object contains glyph widths (in glyph space units).
// As per PDF spec, for most fonts (Type 3 being an exception) a glyph
// space unit corresponds to 1/1000th of text space unit.
var Metrics = {
'Courier': 600,
'Courier-Bold': 600,

View File

@ -3,34 +3,42 @@
'use strict';
var Name = (function nameName() {
function constructor(name) {
var Name = (function NameClosure() {
function Name(name) {
this.name = name;
}
constructor.prototype = {
};
Name.prototype = {};
return constructor;
return Name;
})();
var Cmd = (function cmdCmd() {
function constructor(cmd) {
var Cmd = (function CmdClosure() {
function Cmd(cmd) {
this.cmd = cmd;
}
constructor.prototype = {
Cmd.prototype = {};
var cmdCache = {};
Cmd.get = function cmdGet(cmd) {
var cmdValue = cmdCache[cmd];
if (cmdValue)
return cmdValue;
return cmdCache[cmd] = new Cmd(cmd);
};
return constructor;
return Cmd;
})();
var Dict = (function dictDict() {
function constructor() {
var Dict = (function DictClosure() {
function Dict() {
this.map = Object.create(null);
}
constructor.prototype = {
Dict.prototype = {
get: function dictGet(key1, key2, key3) {
var value;
if (typeof (value = this.map[key1]) != 'undefined' || key1 in this.map ||
@ -60,29 +68,28 @@ var Dict = (function dictDict() {
}
};
return constructor;
return Dict;
})();
var Ref = (function refRef() {
function constructor(num, gen) {
var Ref = (function RefClosure() {
function Ref(num, gen) {
this.num = num;
this.gen = gen;
}
constructor.prototype = {
};
Ref.prototype = {};
return constructor;
return Ref;
})();
// The reference is identified by number and generation,
// this structure stores only one instance of the reference.
var RefSet = (function refSet() {
function constructor() {
var RefSet = (function RefSetClosure() {
function RefSet() {
this.dict = {};
}
constructor.prototype = {
RefSet.prototype = {
has: function refSetHas(ref) {
return !!this.dict['R' + ref.num + '.' + ref.gen];
},
@ -92,18 +99,18 @@ var RefSet = (function refSet() {
}
};
return constructor;
return RefSet;
})();
var Catalog = (function catalogCatalog() {
function constructor(xref) {
var Catalog = (function CatalogClosure() {
function Catalog(xref) {
this.xref = xref;
var obj = xref.getCatalogObj();
assertWellFormed(isDict(obj), 'catalog object is not a dictionary');
this.catDict = obj;
}
constructor.prototype = {
Catalog.prototype = {
get toplevelPagesDict() {
var pagesObj = this.catDict.get('Pages');
assertWellFormed(isRef(pagesObj), 'invalid top-level pages reference');
@ -253,16 +260,16 @@ var Catalog = (function catalogCatalog() {
}
};
return constructor;
return Catalog;
})();
var XRef = (function xRefXRef() {
function constructor(stream, startXRef, mainXRefEntriesOffset) {
var XRef = (function XRefClosure() {
function XRef(stream, startXRef, mainXRefEntriesOffset) {
this.stream = stream;
this.entries = [];
this.xrefstms = {};
var trailerDict = this.readXRef(startXRef);
this.trailer = trailerDict;
// prepare the XRef cache
this.cache = [];
@ -278,7 +285,7 @@ var XRef = (function xRefXRef() {
error('Invalid root reference');
}
constructor.prototype = {
XRef.prototype = {
readXRefTable: function readXRefTable(parser) {
var obj;
while (true) {
@ -518,20 +525,29 @@ var XRef = (function xRefXRef() {
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');
try {
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.readXRefStream(obj);
} catch (e) {
log('Reading of the xref table/stream failed: ' + e);
}
warn('Indexing all PDF objects');
return this.indexObjects();
},
getEntry: function xRefGetEntry(i) {
@ -589,7 +605,7 @@ var XRef = (function xRefXRef() {
e = parser.getObj();
}
// Don't cache streams since they are mutable (except images).
if (!isStream(e) || e.getImage)
if (!isStream(e) || e instanceof JpegStream)
this.cache[num] = e;
return e;
}
@ -633,7 +649,7 @@ var XRef = (function xRefXRef() {
}
};
return constructor;
return XRef;
})();
/**
@ -642,7 +658,7 @@ var XRef = (function xRefXRef() {
* inside of a worker. The `PDFObjects` implements some basic functions to
* manage these objects.
*/
var PDFObjects = (function pdfObjects() {
var PDFObjects = (function PDFObjectsClosure() {
function PDFObjects() {
this.objs = {};
}

View File

@ -9,8 +9,8 @@ function isEOF(v) {
return v == EOF;
}
var Parser = (function parserParser() {
function constructor(lexer, allowStreams, xref) {
var Parser = (function ParserClosure() {
function Parser(lexer, allowStreams, xref) {
this.lexer = lexer;
this.allowStreams = allowStreams;
this.xref = xref;
@ -18,7 +18,7 @@ var Parser = (function parserParser() {
this.refill();
}
constructor.prototype = {
Parser.prototype = {
refill: function parserRefill() {
this.buf1 = this.lexer.getObj();
this.buf2 = this.lexer.getObj();
@ -157,7 +157,7 @@ var Parser = (function parserParser() {
imageStream = this.filter(imageStream, dict, length);
imageStream.parameters = dict;
this.buf2 = new Cmd('EI');
this.buf2 = Cmd.get('EI');
this.shift();
return imageStream;
@ -225,7 +225,8 @@ var Parser = (function parserParser() {
return new PredictorStream(new FlateStream(stream), params);
}
return new FlateStream(stream);
} else if (name == 'LZWDecode' || name == 'LZW') {
}
if (name == 'LZWDecode' || name == 'LZW') {
var earlyChange = 1;
if (params) {
if (params.has('EarlyChange'))
@ -234,31 +235,34 @@ var Parser = (function parserParser() {
new LZWStream(stream, earlyChange), params);
}
return new LZWStream(stream, earlyChange);
} else if (name == 'DCTDecode' || name == 'DCT') {
}
if (name == 'DCTDecode' || name == 'DCT') {
var bytes = stream.getBytes(length);
return new JpegStream(bytes, stream.dict, this.xref);
} 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');
}
if (name == 'ASCII85Decode' || name == 'A85') {
return new Ascii85Stream(stream);
}
if (name == 'ASCIIHexDecode' || name == 'AHx') {
return new AsciiHexStream(stream);
}
if (name == 'CCITTFaxDecode' || name == 'CCF') {
return new CCITTFaxStream(stream, params);
}
warn('filter "' + name + '" not supported yet');
return stream;
}
};
return constructor;
return Parser;
})();
var Lexer = (function lexer() {
function constructor(stream) {
var Lexer = (function LexerClosure() {
function Lexer(stream) {
this.stream = stream;
}
constructor.isSpace = function lexerIsSpace(ch) {
Lexer.isSpace = function lexerIsSpace(ch) {
return ch == ' ' || ch == '\t' || ch == '\x0d' || ch == '\x0a';
};
@ -292,7 +296,7 @@ var Lexer = (function lexer() {
return -1;
}
constructor.prototype = {
Lexer.prototype = {
getNumber: function lexerGetNumber(ch) {
var floating = false;
var str = ch;
@ -492,14 +496,14 @@ var Lexer = (function lexer() {
// array punctuation
case '[':
case ']':
return new Cmd(ch);
return Cmd.get(ch);
// hex string or dict punctuation
case '<':
ch = stream.lookChar();
if (ch == '<') {
// dict punctuation
stream.skip();
return new Cmd('<<');
return Cmd.get('<<');
}
return this.getHexString(ch);
// dict punctuation
@ -507,11 +511,11 @@ var Lexer = (function lexer() {
ch = stream.lookChar();
if (ch == '>') {
stream.skip();
return new Cmd('>>');
return Cmd.get('>>');
}
case '{':
case '}':
return new Cmd(ch);
return Cmd.get(ch);
// fall through
case ')':
error('Illegal character: ' + ch);
@ -534,7 +538,7 @@ var Lexer = (function lexer() {
return false;
if (str == 'null')
return null;
return new Cmd(str);
return Cmd.get(str);
},
skipToNextLine: function lexerSkipToNextLine() {
var stream = this.stream;
@ -554,11 +558,11 @@ var Lexer = (function lexer() {
}
};
return constructor;
return Lexer;
})();
var Linearization = (function linearizationLinearization() {
function constructor(stream) {
var Linearization = (function LinearizationClosure() {
function Linearization(stream) {
this.parser = new Parser(new Lexer(stream), false);
var obj1 = this.parser.getObj();
var obj2 = this.parser.getObj();
@ -572,7 +576,7 @@ var Linearization = (function linearizationLinearization() {
}
}
constructor.prototype = {
Linearization.prototype = {
getInt: function linearizationGetInt(name) {
var linDict = this.linDict;
var obj;
@ -631,6 +635,6 @@ var Linearization = (function linearizationLinearization() {
}
};
return constructor;
return Linearization;
})();

View File

@ -3,13 +3,18 @@
'use strict';
var Pattern = (function patternPattern() {
var PatternType = {
AXIAL: 2,
RADIAL: 3
};
var Pattern = (function PatternClosure() {
// Constructor should define this.getPattern
function constructor() {
function Pattern() {
error('should not call Pattern constructor');
}
constructor.prototype = {
Pattern.prototype = {
// Input: current Canvas context
// Output: the appropriate fillStyle or strokeStyle
getPattern: function pattern_getStyle(ctx) {
@ -17,34 +22,34 @@ var Pattern = (function patternPattern() {
}
};
constructor.shadingFromIR = function pattern_shadingFromIR(ctx, raw) {
Pattern.shadingFromIR = function pattern_shadingFromIR(ctx, raw) {
return Shadings[raw[0]].fromIR(ctx, raw);
};
constructor.parseShading = function pattern_shading(shading, matrix, xref,
Pattern.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
case PatternType.AXIAL:
case PatternType.RADIAL:
// 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;
return Pattern;
})();
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) {
Shadings.RadialAxial = (function RadialAxialClosure() {
function RadialAxial(dict, matrix, xref, res, ctx) {
this.matrix = matrix;
this.coordsArr = dict.get('Coords');
this.shadingType = dict.get('ShadingType');
@ -97,7 +102,7 @@ Shadings.RadialAxial = (function radialAxialShading() {
this.colorStops = colorStops;
}
constructor.fromIR = function radialAxialShadingGetIR(ctx, raw) {
RadialAxial.fromIR = function radialAxialShadingGetIR(ctx, raw) {
var type = raw[1];
var colorStops = raw[2];
var p0 = raw[3];
@ -117,9 +122,9 @@ Shadings.RadialAxial = (function radialAxialShading() {
}
var grad;
if (type == 2)
if (type == PatternType.AXIAL)
grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]);
else if (type == 3)
else if (type == PatternType.RADIAL)
grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1);
for (var i = 0, ii = colorStops.length; i < ii; ++i) {
@ -129,16 +134,16 @@ Shadings.RadialAxial = (function radialAxialShading() {
return grad;
};
constructor.prototype = {
RadialAxial.prototype = {
getIR: function radialAxialShadingGetIR() {
var coordsArr = this.coordsArr;
var type = this.shadingType;
if (type == 2) {
if (type == PatternType.AXIAL) {
var p0 = [coordsArr[0], coordsArr[1]];
var p1 = [coordsArr[2], coordsArr[3]];
var r0 = null;
var r1 = null;
} else if (type == 3) {
} else if (type == PatternType.RADIAL) {
var p0 = [coordsArr[0], coordsArr[1]];
var p1 = [coordsArr[3], coordsArr[4]];
var r0 = coordsArr[2];
@ -157,28 +162,32 @@ Shadings.RadialAxial = (function radialAxialShading() {
}
};
return constructor;
return RadialAxial;
})();
Shadings.Dummy = (function dummyShading() {
function constructor() {
Shadings.Dummy = (function DummyClosure() {
function Dummy() {
this.type = 'Pattern';
}
constructor.fromIR = function dummyShadingFromIR() {
Dummy.fromIR = function dummyShadingFromIR() {
return 'hotpink';
};
constructor.prototype = {
Dummy.prototype = {
getIR: function dummyShadingGetIR() {
return ['Dummy'];
}
};
return constructor;
return Dummy;
})();
var TilingPattern = (function tilingPattern() {
var PAINT_TYPE_COLORED = 1, PAINT_TYPE_UNCOLORED = 2;
var TilingPattern = (function TilingPatternClosure() {
var PaintType = {
COLORED: 1,
UNCOLORED: 2
};
var MAX_PATTERN_SIZE = 512;
function TilingPattern(IR, color, ctx, objs) {
var IRQueue = IR[2];
@ -204,13 +213,13 @@ var TilingPattern = (function tilingPattern() {
var width = botRight[0] - topLeft[0];
var height = botRight[1] - topLeft[1];
// TODO: hack to avoid OOM, we would idealy compute the tiling
// TODO: hack to avoid OOM, we would ideally 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;
while (Math.abs(width) > MAX_PATTERN_SIZE ||
Math.abs(height) > MAX_PATTERN_SIZE) {
width = height = MAX_PATTERN_SIZE;
}
var tmpCanvas = new ScratchCanvas(width, height);
@ -220,11 +229,11 @@ var TilingPattern = (function tilingPattern() {
var graphics = new CanvasGraphics(tmpCtx, objs);
switch (paintType) {
case PAINT_TYPE_COLORED:
case PaintType.COLORED:
tmpCtx.fillStyle = ctx.fillStyle;
tmpCtx.strokeStyle = ctx.strokeStyle;
break;
case PAINT_TYPE_UNCOLORED:
case PaintType.UNCOLORED:
color = Util.makeCssRgb.apply(this, color);
tmpCtx.fillStyle = color;
tmpCtx.strokeStyle = color;

View File

@ -3,8 +3,8 @@
'use strict';
var Stream = (function streamStream() {
function constructor(arrayBuffer, start, length, dict) {
var Stream = (function StreamClosure() {
function Stream(arrayBuffer, start, length, dict) {
this.bytes = new Uint8Array(arrayBuffer);
this.start = start || 0;
this.pos = this.start;
@ -14,7 +14,7 @@ var Stream = (function streamStream() {
// required methods for a stream. if a particular stream does not
// implement these, an error should be thrown
constructor.prototype = {
Stream.prototype = {
get length() {
return this.end - this.start;
},
@ -67,11 +67,11 @@ var Stream = (function streamStream() {
isStream: true
};
return constructor;
return Stream;
})();
var StringStream = (function stringStream() {
function constructor(str) {
var StringStream = (function StringStreamClosure() {
function StringStream(str) {
var length = str.length;
var bytes = new Uint8Array(length);
for (var n = 0; n < length; ++n)
@ -79,21 +79,21 @@ var StringStream = (function stringStream() {
Stream.call(this, bytes);
}
constructor.prototype = Stream.prototype;
StringStream.prototype = Stream.prototype;
return constructor;
return StringStream;
})();
// super class for the decoding streams
var DecodeStream = (function decodeStream() {
function constructor() {
var DecodeStream = (function DecodeStreamClosure() {
function DecodeStream() {
this.pos = 0;
this.bufferLength = 0;
this.eof = false;
this.buffer = null;
}
constructor.prototype = {
DecodeStream.prototype = {
ensureBuffer: function decodestream_ensureBuffer(requested) {
var buffer = this.buffer;
var current = buffer ? buffer.byteLength : 0;
@ -178,24 +178,24 @@ var DecodeStream = (function decodeStream() {
}
};
return constructor;
return DecodeStream;
})();
var FakeStream = (function fakeStream() {
function constructor(stream) {
var FakeStream = (function FakeStreamClosure() {
function FakeStream(stream) {
this.dict = stream.dict;
DecodeStream.call(this);
}
constructor.prototype = Object.create(DecodeStream.prototype);
constructor.prototype.readBlock = function fakeStreamReadBlock() {
FakeStream.prototype = Object.create(DecodeStream.prototype);
FakeStream.prototype.readBlock = function fakeStreamReadBlock() {
var bufferLength = this.bufferLength;
bufferLength += 1024;
var buffer = this.ensureBuffer(bufferLength);
this.bufferLength = bufferLength;
};
constructor.prototype.getBytes = function fakeStreamGetBytes(length) {
FakeStream.prototype.getBytes = function fakeStreamGetBytes(length) {
var end, pos = this.pos;
if (length) {
@ -217,18 +217,20 @@ var FakeStream = (function fakeStream() {
return this.buffer.subarray(pos, end);
};
return constructor;
return FakeStream;
})();
var StreamsSequenceStream = (function streamSequenceStream() {
function constructor(streams) {
var StreamsSequenceStream = (function StreamsSequenceStreamClosure() {
function StreamsSequenceStream(streams) {
this.streams = streams;
DecodeStream.call(this);
}
constructor.prototype = Object.create(DecodeStream.prototype);
StreamsSequenceStream.prototype = Object.create(DecodeStream.prototype);
StreamsSequenceStream.prototype.readBlock =
function streamSequenceStreamReadBlock() {
constructor.prototype.readBlock = function streamSequenceStreamReadBlock() {
var streams = this.streams;
if (streams.length == 0) {
this.eof = true;
@ -243,10 +245,10 @@ var StreamsSequenceStream = (function streamSequenceStream() {
this.bufferLength = newLength;
};
return constructor;
return StreamsSequenceStream;
})();
var FlateStream = (function flateStream() {
var FlateStream = (function FlateStreamClosure() {
var codeLenCodeMap = new Uint32Array([
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
]);
@ -339,7 +341,7 @@ var FlateStream = (function flateStream() {
0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000
]), 5];
function constructor(stream) {
function FlateStream(stream) {
var bytes = stream.getBytes();
var bytesPos = 0;
@ -364,9 +366,9 @@ var FlateStream = (function flateStream() {
DecodeStream.call(this);
}
constructor.prototype = Object.create(DecodeStream.prototype);
FlateStream.prototype = Object.create(DecodeStream.prototype);
constructor.prototype.getBits = function flateStreamGetBits(bits) {
FlateStream.prototype.getBits = function flateStreamGetBits(bits) {
var codeSize = this.codeSize;
var codeBuf = this.codeBuf;
var bytes = this.bytes;
@ -386,7 +388,7 @@ var FlateStream = (function flateStream() {
return b;
};
constructor.prototype.getCode = function flateStreamGetCode(table) {
FlateStream.prototype.getCode = function flateStreamGetCode(table) {
var codes = table[0];
var maxLen = table[1];
var codeSize = this.codeSize;
@ -412,7 +414,7 @@ var FlateStream = (function flateStream() {
return codeVal;
};
constructor.prototype.generateHuffmanTable =
FlateStream.prototype.generateHuffmanTable =
function flateStreamGenerateHuffmanTable(lengths) {
var n = lengths.length;
@ -451,7 +453,7 @@ var FlateStream = (function flateStream() {
return [codes, maxLen];
};
constructor.prototype.readBlock = function flateStreamReadBlock() {
FlateStream.prototype.readBlock = function flateStreamReadBlock() {
// read block header
var hdr = this.getBits(3);
if (hdr & 1)
@ -582,11 +584,11 @@ var FlateStream = (function flateStream() {
}
};
return constructor;
return FlateStream;
})();
var PredictorStream = (function predictorStream() {
function constructor(stream, params) {
var PredictorStream = (function PredictorStreamClosure() {
function PredictorStream(stream, params) {
var predictor = this.predictor = params.get('Predictor') || 1;
if (predictor <= 1)
@ -613,9 +615,9 @@ var PredictorStream = (function predictorStream() {
return this;
}
constructor.prototype = Object.create(DecodeStream.prototype);
PredictorStream.prototype = Object.create(DecodeStream.prototype);
constructor.prototype.readBlockTiff =
PredictorStream.prototype.readBlockTiff =
function predictorStreamReadBlockTiff() {
var rowBytes = this.rowBytes;
@ -676,7 +678,9 @@ var PredictorStream = (function predictorStream() {
this.bufferLength += rowBytes;
};
constructor.prototype.readBlockPng = function predictorStreamReadBlockPng() {
PredictorStream.prototype.readBlockPng =
function predictorStreamReadBlockPng() {
var rowBytes = this.rowBytes;
var pixBytes = this.pixBytes;
@ -753,7 +757,7 @@ var PredictorStream = (function predictorStream() {
this.bufferLength += rowBytes;
};
return constructor;
return PredictorStream;
})();
/**
@ -763,7 +767,7 @@ var PredictorStream = (function predictorStream() {
* a library to decode these images and the stream behaves like all the other
* DecodeStreams.
*/
var JpegStream = (function jpegStream() {
var JpegStream = (function JpegStreamClosure() {
function isAdobeImage(bytes) {
var maxBytesScanned = Math.max(bytes.length - 16, 1024);
// Looking for APP14, 'Adobe'
@ -794,7 +798,7 @@ var JpegStream = (function jpegStream() {
return newBytes;
}
function constructor(bytes, dict, xref) {
function JpegStream(bytes, dict, xref) {
// TODO: per poppler, some images may have 'junk' before that
// need to be removed
this.dict = dict;
@ -825,9 +829,9 @@ var JpegStream = (function jpegStream() {
DecodeStream.call(this);
}
constructor.prototype = Object.create(DecodeStream.prototype);
JpegStream.prototype = Object.create(DecodeStream.prototype);
constructor.prototype.ensureBuffer = function jpegStreamEnsureBuffer(req) {
JpegStream.prototype.ensureBuffer = function jpegStreamEnsureBuffer(req) {
if (this.bufferLength)
return;
var jpegImage = new JpegImage();
@ -839,18 +843,18 @@ var JpegStream = (function jpegStream() {
this.buffer = data;
this.bufferLength = data.length;
};
constructor.prototype.getIR = function jpegStreamGetIR() {
JpegStream.prototype.getIR = function jpegStreamGetIR() {
return this.src;
};
constructor.prototype.getChar = function jpegStreamGetChar() {
JpegStream.prototype.getChar = function jpegStreamGetChar() {
error('internal error: getChar is not valid on JpegStream');
};
return constructor;
return JpegStream;
})();
var DecryptStream = (function decryptStream() {
function constructor(str, decrypt) {
var DecryptStream = (function DecryptStreamClosure() {
function DecryptStream(str, decrypt) {
this.str = str;
this.dict = str.dict;
this.decrypt = decrypt;
@ -860,9 +864,9 @@ var DecryptStream = (function decryptStream() {
var chunkSize = 512;
constructor.prototype = Object.create(DecodeStream.prototype);
DecryptStream.prototype = Object.create(DecodeStream.prototype);
constructor.prototype.readBlock = function decryptStreamReadBlock() {
DecryptStream.prototype.readBlock = function decryptStreamReadBlock() {
var chunk = this.str.getBytes(chunkSize);
if (!chunk || chunk.length == 0) {
this.eof = true;
@ -879,11 +883,11 @@ var DecryptStream = (function decryptStream() {
this.bufferLength = bufferLength;
};
return constructor;
return DecryptStream;
})();
var Ascii85Stream = (function ascii85Stream() {
function constructor(str) {
var Ascii85Stream = (function Ascii85StreamClosure() {
function Ascii85Stream(str) {
this.str = str;
this.dict = str.dict;
this.input = new Uint8Array(5);
@ -891,9 +895,9 @@ var Ascii85Stream = (function ascii85Stream() {
DecodeStream.call(this);
}
constructor.prototype = Object.create(DecodeStream.prototype);
Ascii85Stream.prototype = Object.create(DecodeStream.prototype);
constructor.prototype.readBlock = function ascii85StreamReadBlock() {
Ascii85Stream.prototype.readBlock = function ascii85StreamReadBlock() {
var tildaCode = '~'.charCodeAt(0);
var zCode = 'z'.charCodeAt(0);
var str = this.str;
@ -948,11 +952,11 @@ var Ascii85Stream = (function ascii85Stream() {
}
};
return constructor;
return Ascii85Stream;
})();
var AsciiHexStream = (function asciiHexStream() {
function constructor(str) {
var AsciiHexStream = (function AsciiHexStreamClosure() {
function AsciiHexStream(str) {
this.str = str;
this.dict = str.dict;
@ -986,9 +990,9 @@ var AsciiHexStream = (function asciiHexStream() {
102: 15
};
constructor.prototype = Object.create(DecodeStream.prototype);
AsciiHexStream.prototype = Object.create(DecodeStream.prototype);
constructor.prototype.readBlock = function asciiHexStreamReadBlock() {
AsciiHexStream.prototype.readBlock = function asciiHexStreamReadBlock() {
var gtCode = '>'.charCodeAt(0), bytes = this.str.getBytes(), c, n,
decodeLength, buffer, bufferLength, i, length;
@ -1018,10 +1022,10 @@ var AsciiHexStream = (function asciiHexStream() {
this.eof = true;
};
return constructor;
return AsciiHexStream;
})();
var CCITTFaxStream = (function ccittFaxStream() {
var CCITTFaxStream = (function CCITTFaxStreamClosure() {
var ccittEOL = -2;
var twoDimPass = 0;
@ -1449,7 +1453,7 @@ var CCITTFaxStream = (function ccittFaxStream() {
[2, 2], [2, 2], [2, 2], [2, 2]
];
function constructor(str, params) {
function CCITTFaxStream(str, params) {
this.str = str;
this.dict = str.dict;
@ -1494,9 +1498,9 @@ var CCITTFaxStream = (function ccittFaxStream() {
DecodeStream.call(this);
}
constructor.prototype = Object.create(DecodeStream.prototype);
CCITTFaxStream.prototype = Object.create(DecodeStream.prototype);
constructor.prototype.readBlock = function ccittFaxStreamReadBlock() {
CCITTFaxStream.prototype.readBlock = function ccittFaxStreamReadBlock() {
while (!this.eof) {
var c = this.lookChar();
this.buf = EOF;
@ -1505,7 +1509,7 @@ var CCITTFaxStream = (function ccittFaxStream() {
}
};
constructor.prototype.addPixels =
CCITTFaxStream.prototype.addPixels =
function ccittFaxStreamAddPixels(a1, blackPixels) {
var codingLine = this.codingLine;
var codingPos = this.codingPos;
@ -1525,7 +1529,7 @@ var CCITTFaxStream = (function ccittFaxStream() {
this.codingPos = codingPos;
};
constructor.prototype.addPixelsNeg =
CCITTFaxStream.prototype.addPixelsNeg =
function ccittFaxStreamAddPixelsNeg(a1, blackPixels) {
var codingLine = this.codingLine;
var codingPos = this.codingPos;
@ -1554,7 +1558,7 @@ var CCITTFaxStream = (function ccittFaxStream() {
this.codingPos = codingPos;
};
constructor.prototype.lookChar = function ccittFaxStreamLookChar() {
CCITTFaxStream.prototype.lookChar = function ccittFaxStreamLookChar() {
if (this.buf != EOF)
return this.buf;
@ -1852,10 +1856,10 @@ var CCITTFaxStream = (function ccittFaxStream() {
// values. The first array element indicates whether a valid code is being
// returned. The second array element is the actual code. The third array
// element indicates whether EOF was reached.
var findTableCode = function ccittFaxStreamFindTableCode(start, end, table,
limit) {
var limitValue = limit || 0;
CCITTFaxStream.prototype.findTableCode =
function ccittFaxStreamFindTableCode(start, end, table, limit) {
var limitValue = limit || 0;
for (var i = start; i <= end; ++i) {
var code = this.lookBits(i);
if (code == EOF)
@ -1873,7 +1877,9 @@ var CCITTFaxStream = (function ccittFaxStream() {
return [false, 0, false];
};
constructor.prototype.getTwoDimCode = function ccittFaxStreamGetTwoDimCode() {
CCITTFaxStream.prototype.getTwoDimCode =
function ccittFaxStreamGetTwoDimCode() {
var code = 0;
var p;
if (this.eoblock) {
@ -1884,7 +1890,7 @@ var CCITTFaxStream = (function ccittFaxStream() {
return p[1];
}
} else {
var result = findTableCode(1, 7, twoDimTable);
var result = this.findTableCode(1, 7, twoDimTable);
if (result[0] && result[2])
return result[1];
}
@ -1892,7 +1898,9 @@ var CCITTFaxStream = (function ccittFaxStream() {
return EOF;
};
constructor.prototype.getWhiteCode = function ccittFaxStreamGetWhiteCode() {
CCITTFaxStream.prototype.getWhiteCode =
function ccittFaxStreamGetWhiteCode() {
var code = 0;
var p;
var n;
@ -1911,11 +1919,11 @@ var CCITTFaxStream = (function ccittFaxStream() {
return p[1];
}
} else {
var result = findTableCode(1, 9, whiteTable2);
var result = this.findTableCode(1, 9, whiteTable2);
if (result[0])
return result[1];
result = findTableCode(11, 12, whiteTable1);
result = this.findTableCode(11, 12, whiteTable1);
if (result[0])
return result[1];
}
@ -1924,7 +1932,9 @@ var CCITTFaxStream = (function ccittFaxStream() {
return 1;
};
constructor.prototype.getBlackCode = function ccittFaxStreamGetBlackCode() {
CCITTFaxStream.prototype.getBlackCode =
function ccittFaxStreamGetBlackCode() {
var code, p;
if (this.eoblock) {
code = this.lookBits(13);
@ -1942,15 +1952,15 @@ var CCITTFaxStream = (function ccittFaxStream() {
return p[1];
}
} else {
var result = findTableCode(2, 6, blackTable3);
var result = this.findTableCode(2, 6, blackTable3);
if (result[0])
return result[1];
result = findTableCode(7, 12, blackTable2, 64);
result = this.findTableCode(7, 12, blackTable2, 64);
if (result[0])
return result[1];
result = findTableCode(10, 13, blackTable1);
result = this.findTableCode(10, 13, blackTable1);
if (result[0])
return result[1];
}
@ -1959,7 +1969,7 @@ var CCITTFaxStream = (function ccittFaxStream() {
return 1;
};
constructor.prototype.lookBits = function ccittFaxStreamLookBits(n) {
CCITTFaxStream.prototype.lookBits = function ccittFaxStreamLookBits(n) {
var c;
while (this.inputBits < n) {
if ((c = this.str.getByte()) == null) {
@ -1974,16 +1984,16 @@ var CCITTFaxStream = (function ccittFaxStream() {
return (this.inputBuf >> (this.inputBits - n)) & (0xFFFF >> (16 - n));
};
constructor.prototype.eatBits = function ccittFaxStreamEatBits(n) {
CCITTFaxStream.prototype.eatBits = function ccittFaxStreamEatBits(n) {
if ((this.inputBits -= n) < 0)
this.inputBits = 0;
};
return constructor;
return CCITTFaxStream;
})();
var LZWStream = (function lzwStream() {
function constructor(str, earlyChange) {
var LZWStream = (function LZWStreamClosure() {
function LZWStream(str, earlyChange) {
this.str = str;
this.dict = str.dict;
this.cachedData = 0;
@ -2009,9 +2019,9 @@ var LZWStream = (function lzwStream() {
DecodeStream.call(this);
}
constructor.prototype = Object.create(DecodeStream.prototype);
LZWStream.prototype = Object.create(DecodeStream.prototype);
constructor.prototype.readBits = function lzwStreamReadBits(n) {
LZWStream.prototype.readBits = function lzwStreamReadBits(n) {
var bitsCached = this.bitsCached;
var cachedData = this.cachedData;
while (bitsCached < n) {
@ -2029,7 +2039,7 @@ var LZWStream = (function lzwStream() {
return (cachedData >>> bitsCached) & ((1 << n) - 1);
};
constructor.prototype.readBlock = function lzwStreamReadBlock() {
LZWStream.prototype.readBlock = function lzwStreamReadBlock() {
var blockSize = 512;
var estimatedDecodedSize = blockSize * 2, decodedSizeDelta = blockSize;
var i, j, q;
@ -2108,6 +2118,6 @@ var LZWStream = (function lzwStream() {
this.bufferLength = currentBufferLength;
};
return constructor;
return LZWStream;
})();

View File

@ -76,24 +76,24 @@ function stringToBytes(str) {
var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
var Util = (function utilUtil() {
function constructor() {}
constructor.makeCssRgb = function makergb(r, g, b) {
var Util = (function UtilClosure() {
function Util() {}
Util.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) {
Util.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) {
Util.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;
return Util;
})();
var PDFStringTranslateTable = [
@ -197,7 +197,7 @@ function isPDFFunction(v) {
* 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 Promise = (function PromiseClosure() {
var EMPTY_PROMISE = {};
/**
@ -206,6 +206,8 @@ var Promise = (function promise() {
*/
function Promise(name, data) {
this.name = name;
this.isRejected = false;
this.error = null;
// If you build a promise and pass in some data it's already resolved.
if (data != null) {
this.isResolved = true;
@ -216,8 +218,35 @@ var Promise = (function promise() {
this._data = EMPTY_PROMISE;
}
this.callbacks = [];
this.errbacks = [];
};
/**
* Builds a promise that is resolved when all the passed in promises are
* resolved.
* @param {Promise[]} promises Array of promises to wait for.
* @return {Promise} New dependant promise.
*/
Promise.all = function(promises) {
var deferred = new Promise();
var unresolved = promises.length;
var results = [];
if (unresolved === 0) {
deferred.resolve(results);
return deferred;
}
for (var i = 0; i < unresolved; ++i) {
var promise = promises[i];
promise.then((function(i) {
return function(value) {
results[i] = value;
unresolved--;
if (unresolved === 0)
deferred.resolve(results);
};
})(i));
}
return deferred;
};
Promise.prototype = {
hasData: false,
@ -256,9 +285,12 @@ var Promise = (function promise() {
if (this.isResolved) {
throw 'A Promise can be resolved only once ' + this.name;
}
if (this.isRejected) {
throw 'The Promise was already rejected ' + this.name;
}
this.isResolved = true;
this.data = data;
this.data = data || null;
var callbacks = this.callbacks;
for (var i = 0, ii = callbacks.length; i < ii; i++) {
@ -266,7 +298,24 @@ var Promise = (function promise() {
}
},
then: function promiseThen(callback) {
reject: function proimseReject(reason) {
if (this.isRejected) {
throw 'A Promise can be rejected only once ' + this.name;
}
if (this.isResolved) {
throw 'The Promise was already resolved ' + this.name;
}
this.isRejected = true;
this.error = reason || null;
var errbacks = this.errbacks;
for (var i = 0, ii = errbacks.length; i < ii; i++) {
errbacks[i].call(null, reason);
}
},
then: function promiseThen(callback, errback) {
if (!callback) {
throw 'Requiring callback' + this.name;
}
@ -275,8 +324,13 @@ var Promise = (function promise() {
if (this.isResolved) {
var data = this.data;
callback.call(null, data);
} else if (this.isRejected && errorback) {
var error = this.error;
errback.call(null, error);
} else {
this.callbacks.push(callback);
if (errback)
this.errbacks.push(errback);
}
}
};

View File

@ -6,6 +6,8 @@
function MessageHandler(name, comObj) {
this.name = name;
this.comObj = comObj;
this.callbackIndex = 1;
var callbacks = this.callbacks = {};
var ah = this.actionHandler = {};
ah['console_log'] = [function ahConsoleLog(data) {
@ -17,9 +19,30 @@ function MessageHandler(name, comObj) {
comObj.onmessage = function messageHandlerComObjOnMessage(event) {
var data = event.data;
if (data.action in ah) {
if (data.isReply) {
var callbackId = data.callbackId;
if (data.callbackId in callbacks) {
var callback = callbacks[callbackId];
delete callbacks[callbackId];
callback(data.data);
} else {
throw 'Cannot resolve callback ' + callbackId;
}
} else if (data.action in ah) {
var action = ah[data.action];
action[0].call(action[1], data.data);
if (data.callbackId) {
var promise = new Promise();
promise.then(function(resolvedData) {
comObj.postMessage({
isReply: true,
callbackId: data.callbackId,
data: resolvedData
});
});
action[0].call(action[1], data.data, promise);
} else {
action[0].call(action[1], data.data);
}
} else {
throw 'Unkown action from worker: ' + data.action;
}
@ -34,12 +57,23 @@ MessageHandler.prototype = {
}
ah[actionName] = [handler, scope];
},
send: function messageHandlerSend(actionName, data) {
this.comObj.postMessage({
/**
* Sends a message to the comObj to invoke the action with the supplied data.
* @param {String} actionName Action to call.
* @param {JSON} data JSON data to send.
* @param {function} [callback] Optional callback that will handle a reply.
*/
send: function messageHandlerSend(actionName, data, callback) {
var message = {
action: actionName,
data: data
});
};
if (callback) {
var callbackId = this.callbackIndex++;
this.callbacks[callbackId] = callback;
message.callbackId = callbackId;
}
this.comObj.postMessage(message);
}
};
@ -67,7 +101,6 @@ var WorkerMessageHandler = {
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
@ -77,9 +110,23 @@ var WorkerMessageHandler = {
var start = Date.now();
var dependency = [];
// Pre compile the pdf page and fetch the fonts/images.
var IRQueue = page.getIRQueue(handler, dependency);
var IRQueue = null;
try {
var page = pdfDoc.getPage(pageNum);
// Pre compile the pdf page and fetch the fonts/images.
IRQueue = page.getIRQueue(handler, dependency);
} catch (e) {
// Turn the error into an obj that can be serialized
e = {
message: typeof e === 'object' ? e.message : e,
stack: typeof e === 'object' ? e.stack : null
};
handler.send('page_error', {
pageNum: pageNum,
error: e
});
return;
}
console.log('page=%d - getIRQueue: time=%dms, len=%d', pageNum,
Date.now() - start, IRQueue.fnArray.length);

View File

@ -139,6 +139,11 @@ function nextPage(task, loadError) {
if (task.skipPages && task.skipPages.indexOf(task.pageNum) >= 0) {
log(' skipping page ' + task.pageNum + '/' + task.pdfDoc.numPages +
'... ');
// empty the canvas
canvas.width = 1;
canvas.height = 1;
clear(canvas.getContext('2d'));
snapshotCurrentPage(task, '');
return;
}
@ -160,12 +165,24 @@ function nextPage(task, loadError) {
canvas.height = pageHeight * pdfToCssUnitsCoef;
clear(ctx);
// using the text layer builder that does nothing to test
// text layer creation operations
var textLayerBuilder = {
beginLayout: function nullTextLayerBuilderBeginLayout() {},
endLayout: function nullTextLayerBuilderEndLayout() {},
appendText: function nullTextLayerBuilderAppendText(text, fontName,
fontSize) {}
};
page.startRendering(
ctx,
function nextPageStartRendering(e) {
snapshotCurrentPage(task, (!failure && e) ?
('render : ' + e) : failure);
}
function nextPageStartRendering(error) {
var failureMessage = false;
if (error)
failureMessage = 'render : ' + error.message;
snapshotCurrentPage(task, failureMessage);
},
textLayerBuilder
);
} catch (e) {
failure = 'page setup : ' + e.toString();

View File

@ -16,3 +16,9 @@
!alphatrans.pdf
!devicen.pdf
!cmykjpeg.pdf
!issue840.pdf
!scan-bad.pdf
!freeculture.pdf
!issue918.pdf
!smaskdim.pdf
!type4psfunc.pdf

View File

@ -0,0 +1 @@
http://greenhousechallenge.org/media/item/313/38/About-Stacks.pdf

View File

@ -0,0 +1 @@
http://h20000.www2.hp.com/bc/docs/support/SupportManual/bpl13210/bpl13210.pdf

BIN
test/pdfs/freeculture.pdf Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
http://geothermal.inel.gov/publications/future_of_geothermal_energy.pdf

View File

@ -0,0 +1 @@
http://www.myhillsapartment.com/island_club/floorplans/images/links/Island_IC_brochure.pdf

View File

@ -0,0 +1 @@
http://faculty.washington.edu/fidelr/RayaPubs/TheCaseStudyMethod.pdf

BIN
test/pdfs/issue840.pdf Normal file

Binary file not shown.

BIN
test/pdfs/issue918.pdf Normal file

Binary file not shown.

View File

@ -0,0 +1 @@
http://agb.traviangames.com/Travian_AR_Terms.pdf

View File

@ -0,0 +1 @@
http://www.lfg.com.br/concursodebolsas/lista_preliminar_classificao.pdf

1
test/pdfs/ocs.pdf.link Normal file
View File

@ -0,0 +1 @@
http://www.unibuc.ro/uploads_en/29535/10/Cyrillic_Alphabets-Chars.pdf

View File

@ -0,0 +1 @@
http://www.erowid.org/archive/rhodium/chemistry/3base/piperonal.pepper/piperine.pepper/465e03piperine.pdf

View File

@ -0,0 +1 @@
http://leahy.senate.gov/imo/media/doc/BillText-PROTECTIPAct.pdf

BIN
test/pdfs/scan-bad.pdf Executable file

Binary file not shown.

BIN
test/pdfs/smaskdim.pdf Executable file

Binary file not shown.

View File

@ -0,0 +1 @@
http://cplusplus.com/files/tutorial.pdf

BIN
test/pdfs/type4psfunc.pdf Executable file

Binary file not shown.

View File

@ -12,6 +12,7 @@ DOC_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__),".."))
ANAL = True
DEFAULT_MANIFEST_FILE = 'test_manifest.json'
EQLOG_FILE = 'eq.log'
BROWSERLOG_FILE = 'browser.log'
REFDIR = 'ref'
TMPDIR = 'tmp'
VERBOSE = False
@ -229,6 +230,7 @@ class BaseBrowserCommand(object):
def setup(self):
self.tempDir = tempfile.mkdtemp()
self.profileDir = os.path.join(self.tempDir, "profile")
self.browserLog = open(BROWSERLOG_FILE, "w")
def teardown(self):
# If the browser is still running, wait up to ten seconds for it to quit
@ -245,6 +247,8 @@ class BaseBrowserCommand(object):
if self.tempDir is not None and os.path.exists(self.tempDir):
shutil.rmtree(self.tempDir)
self.browserLog.close()
def start(self, url):
raise Exception("Can't start BaseBrowserCommand")
@ -262,7 +266,7 @@ class FirefoxBrowserCommand(BaseBrowserCommand):
if platform.system() == "Darwin":
cmds.append("-foreground")
cmds.extend(["-no-remote", "-profile", self.profileDir, url])
self.process = subprocess.Popen(cmds)
self.process = subprocess.Popen(cmds, stdout = self.browserLog, stderr = self.browserLog)
class ChromeBrowserCommand(BaseBrowserCommand):
def _fixupMacPath(self):
@ -272,7 +276,7 @@ class ChromeBrowserCommand(BaseBrowserCommand):
cmds = [self.path]
cmds.extend(["--user-data-dir=%s" % self.profileDir,
"--no-first-run", "--disable-sync", url])
self.process = subprocess.Popen(cmds)
self.process = subprocess.Popen(cmds, stdout = self.browserLog, stderr = self.browserLog)
def makeBrowserCommand(browser):
path = browser["path"].lower()

View File

@ -17,13 +17,13 @@
"rounds": 1,
"type": "load"
},
{ "id": "intelisa-load",
{ "id": "intelisa-eq",
"file": "pdfs/intelisa.pdf",
"md5": "f5712097d29287a97f1278839814f682",
"md5": "f3ed5487d1afa34d8b77c0c734a95c79",
"link": true,
"pageLimit": 100,
"rounds": 1,
"type": "load"
"type": "eq"
},
{ "id": "pdfspec-load",
"file": "pdfs/pdf.pdf",
@ -88,6 +88,13 @@
"rounds": 1,
"type": "eq"
},
{ "id": "freeculture",
"file": "pdfs/freeculture.pdf",
"md5": "dcdf3a8268e6a18938a42d5149efcfca",
"rounds": 1,
"pageLimit": 5,
"type": "eq"
},
{ "id": "wnv_chinese-pdf",
"file": "pdfs/wnv_chinese.pdf",
"md5": "db682638e68391125e8982d3c984841e",
@ -221,6 +228,12 @@
"rounds": 1,
"type": "load"
},
{ "id": "scan-bad",
"file": "pdfs/scan-bad.pdf",
"md5": "4cf988f01ab83f61aca57f406dfd6584",
"rounds": 1,
"type": "load"
},
{ "id": "ibwa-bad",
"file": "pdfs/ibwa-bad.pdf",
"md5": "6ca059d32b74ac2688ae06f727fee755",
@ -276,5 +289,111 @@
"link": false,
"rounds": 1,
"type": "eq"
},
{ "id": "protectip",
"file": "pdfs/protectip.pdf",
"md5": "676e7a7b8f96d04825361832b1838a93",
"link": true,
"rounds": 1,
"type": "eq"
},
{ "id": "piperine",
"file": "pdfs/piperine.pdf",
"md5": "603ca43dc5732dbba1579f122958c0c2",
"link": true,
"rounds": 1,
"type": "eq"
},
{ "id": "issue840",
"file": "pdfs/issue840.pdf",
"md5": "20d88011dd7e3c4fb5274979094dab93",
"rounds": 1,
"type": "eq"
},
{ "id": "bpl13210",
"file": "pdfs/bpl13210.pdf",
"md5": "8a08512baa9fa95378d9ad4b995947c7",
"link": true,
"pageLimit": 5,
"rounds": 1,
"type": "eq"
},
{ "id": "tutorial",
"file": "pdfs/tutorial.pdf",
"md5": "6e122f618c27f3aa9a689423e3be6b8d",
"link": true,
"rounds": 1,
"type": "eq"
},
{ "id": "geothermal.pdf",
"file": "pdfs/geothermal.pdf",
"md5": "ecffc0ce38ffdf1e90dc952f186e9a91",
"rounds": 1,
"link": true,
"pageLimit": 5,
"skipPages": [1],
"type": "eq"
},
{ "id": "lista_preliminar",
"file": "pdfs/lista_preliminar.pdf",
"md5": "4eff251319eeb660ba8a7a5cfac7787d",
"rounds": 1,
"link": true,
"pageLimit": 3,
"type": "eq"
},
{ "id": "issue919",
"file": "pdfs/issue919.pdf",
"md5": "3a1716a512aca4d7a8d6106bd4885d14",
"rounds": 1,
"link": true,
"pageLimit": 3,
"type": "eq"
},
{ "id": "issue918",
"file": "pdfs/issue918.pdf",
"md5": "d582cc0f2592ae82936589ced2a47e55",
"rounds": 1,
"type": "eq"
},
{ "id": "issue1001",
"file": "pdfs/issue1001.pdf",
"md5": "0f1496e80a82a923e91d9e74c55ad94e",
"rounds": 1,
"link": true,
"type": "eq"
},
{ "id": "aboutstacks",
"file": "pdfs/aboutstacks.pdf",
"md5": "6e7c8416a293ba2d83bc8dd20c6ccf51",
"rounds": 1,
"link": true,
"type": "eq"
},
{ "id": "smaskdim",
"file": "pdfs/smaskdim.pdf",
"md5": "de80aeca7cbf79940189fd34d59671ee",
"rounds": 1,
"type": "eq"
},
{ "id": "type4psfunc",
"file": "pdfs/type4psfunc.pdf",
"md5": "7e6027a02ff78577f74dccdf84e37189",
"rounds": 1,
"type": "eq"
},
{ "id": "ocs",
"file": "pdfs/ocs.pdf",
"md5": "2ade57e954ae7632749cf328daeaa7a8",
"rounds": 1,
"link": true,
"type": "load"
},
{ "id": "issue1015",
"file": "pdfs/issue1015.pdf",
"md5": "b61503d1b445742b665212866afb60e2",
"rounds": 1,
"link": true,
"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

@ -12,5 +12,120 @@ describe('obj', function() {
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

@ -5,11 +5,16 @@
// Checking if the typed arrays are supported
(function checkTypedArrayCompatibility() {
if (typeof Uint8Array !== 'undefined')
if (typeof Uint8Array !== 'undefined') {
// some mobile version might not support Float64Array
if (typeof Float64Array === 'undefined')
window.Float64Array = Float32Array;
return;
}
function subarray(start, end) {
return this.slice(start, end);
return new TypedArray(this.slice(start, end));
}
function setArrayOffset(array, offset) {
@ -46,6 +51,8 @@
window.Uint32Array = TypedArray;
window.Int32Array = TypedArray;
window.Uint16Array = TypedArray;
window.Float32Array = TypedArray;
window.Float64Array = TypedArray;
})();
// Object.create() ?
@ -205,3 +212,15 @@
});
})();
// HTMLElement dataset property
(function checkDatasetProperty() {
var div = document.createElement('div');
if ('dataset' in div)
return; // dataset property exists
Object.defineProperty(HTMLElement.prototype, 'dataset', {
get: function htmlElementDatasetGetter() {
// adding dataset field to the actual object
return (this.dataset = {});
}
});
})();

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

@ -15,6 +15,7 @@ body {
/* === Toolbar === */
#controls {
background-color: #eee;
background: -o-linear-gradient(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));
border-bottom: 1px solid #666;
@ -82,6 +83,7 @@ span#info {
bottom: 18px;
left: -290px;
transition: left 0.25s ease-in-out 1s;
-o-transition: left 0.25s ease-in-out 1s;
-moz-transition: left 0.25s ease-in-out 1s;
-webkit-transition: left 0.25s ease-in-out 1s;
z-index: 1;
@ -90,6 +92,7 @@ span#info {
#sidebar:hover {
left: 0px;
transition: left 0.25s ease-in-out 0s;
-o-transition: left 0.25s ease-in-out 0s;
-moz-transition: left 0.25s ease-in-out 0s;
-webkit-transition: left 0.25s ease-in-out 0s;
}
@ -232,6 +235,56 @@ canvas {
-webkit-box-shadow: 0px 2px 10px #ff0;
}
.textLayer {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
color: #000;
}
.textLayer > div {
color: transparent;
position: absolute;
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 */
::selection { background:rgba(0,0,255,0.3); }
::-moz-selection { background:rgba(0,0,255,0.3); }
#viewer {
margin: 44px 0px 0px;
padding: 8px 0px;
@ -252,6 +305,38 @@ canvas {
display: none;
}
#errorWrapper {
background: none repeat scroll 0 0 #FF5555;
color: white;
left: 0;
position: fixed;
right: 0;
top: 30px;
z-index: 1000;
padding: 3px;
font-size: 0.8em;
}
#errorMessageLeft {
float: left;
}
#errorMessageRight {
float: right;
}
#errorMoreInfo {
background-color: #FFFFFF;
color: black;
padding: 3px;
margin: 3px;
width: 98%;
}
.clearBoth {
clear: both;
}
/* === Printed media overrides === */
@media print {
#sidebar {

View File

@ -27,9 +27,9 @@
<script type="text/javascript" src="../src/worker.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="../external/jpgjs/jpg.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript">PDFJS.workerSrc = '../src/worker_loader.js';</script> <!-- PDFJSSCRIPT_REMOVE -->
<script type="text/javascript" src="viewer.js"></script>
</head>
</head>
<body>
<div id="controls">
@ -67,10 +67,11 @@
<option value="0.75">75%</option>
<option value="1">100%</option>
<option value="1.25">125%</option>
<option value="1.5" selected="selected">150%</option>
<option value="1.5">150%</option>
<option value="2">200%</option>
<option id="pageWidthOption" value="page-width">Page Width</option>
<option id="pageFitOption" value="page-fit">Page Fit</option>
<option id="pageAutoOption" value="auto" selected="selected">Auto</option>
</select>
<div class="separator"></div>
@ -97,6 +98,24 @@
<span id="info">--</span>
</div>
<div id="errorWrapper" hidden='true'>
<div id="errorMessageLeft">
<span id="errorMessage"></span>
<button id="errorShowMore" onclick="" oncontextmenu="return false;">
More Information
</button>
<button id="errorShowLess" onclick="" oncontextmenu="return false;" hidden='true'>
Less Information
</button>
</div>
<div id="errorMessageRight">
<button id="errorClose" oncontextmenu="return false;">
Close
</button>
</div>
<div class="clearBoth"></div>
<textarea id="errorMoreInfo" hidden='true' readonly="readonly"></textarea>
</div>
<div id="sidebar">
<div id="sidebarBox">
@ -121,4 +140,3 @@
<div id="viewer"></div>
</body>
</html>

View File

@ -4,14 +4,15 @@
'use strict';
var kDefaultURL = 'compressed.tracemonkey-pldi-09.pdf';
var kDefaultScale = 1.5;
var kDefaultScale = 'auto';
var kDefaultScaleDelta = 1.1;
var kCacheSize = 20;
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,16 +26,136 @@ var Cache = function cacheCache(size) {
};
};
var RenderingQueue = (function RenderingQueueClosure() {
function RenderingQueue() {
this.items = [];
}
RenderingQueue.prototype = {
enqueueDraw: function RenderingQueueEnqueueDraw(item) {
if (!item.drawingRequired())
return; // as no redraw required, no need for queueing.
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,
currentScaleValue: null,
initialBookmark: document.location.hash.substring(1),
setScale: function pdfViewSetScale(val, resetAutoSettings) {
if (val == this.currentScale)
return;
var pages = this.pages;
for (var i = 0; i < pages.length; i++)
pages[i].update(val * kCssUnits);
@ -55,6 +176,7 @@ var PDFView = {
return;
var scale = parseFloat(value);
this.currentScaleValue = value;
if (scale) {
this.setScale(scale, true);
return;
@ -73,6 +195,10 @@ var PDFView = {
this.setScale(
Math.min(pageWidthScale, pageHeightScale), resetAutoSettings);
}
if ('auto' == value)
this.setScale(Math.min(1.0, pageWidthScale), resetAutoSettings);
selectScaleOption(value);
},
zoomIn: function pdfViewZoomIn() {
@ -129,7 +255,14 @@ var PDFView = {
if (evt.lengthComputable)
self.progress(evt.loaded / evt.total);
},
error: self.error
error: function getPdfError(e) {
var loadingIndicator = document.getElementById('loading');
loadingIndicator.innerHTML = 'Error';
var moreInfo = {
message: 'Unexpected server response of ' + e.target.status + '.'
};
self.error('An error occurred while loading the PDF.', moreInfo);
}
},
function getPdfLoad(data) {
self.loading = true;
@ -168,7 +301,8 @@ var PDFView = {
(destRef + 1);
if (pageNumber) {
var pdfOpenParams = '#page=' + pageNumber;
if (isName(dest[1], 'XYZ')) {
var destKind = dest[1];
if ('name' in destKind && destKind.name == 'XYZ') {
var scale = (dest[4] || this.currentScale);
pdfOpenParams += '&zoom=' + (scale * 100);
if (dest[2] || dest[3]) {
@ -181,9 +315,48 @@ var PDFView = {
return '';
},
error: function pdfViewError() {
var loadingIndicator = document.getElementById('loading');
loadingIndicator.innerHTML = 'Error';
/**
* Show the error box.
* @param {String} message A message that is human readable.
* @param {Object} moreInfo (optional) Further information about the error
* that is more technical. Should have a 'message'
* and optionally a 'stack' property.
*/
error: function pdfViewError(message, moreInfo) {
var errorWrapper = document.getElementById('errorWrapper');
errorWrapper.removeAttribute('hidden');
var errorMessage = document.getElementById('errorMessage');
errorMessage.innerHTML = message;
var closeButton = document.getElementById('errorClose');
closeButton.onclick = function() {
errorWrapper.setAttribute('hidden', 'true');
};
var errorMoreInfo = document.getElementById('errorMoreInfo');
var moreInfoButton = document.getElementById('errorShowMore');
var lessInfoButton = document.getElementById('errorShowLess');
moreInfoButton.onclick = function() {
errorMoreInfo.removeAttribute('hidden');
moreInfoButton.setAttribute('hidden', 'true');
lessInfoButton.removeAttribute('hidden');
};
lessInfoButton.onclick = function() {
errorMoreInfo.setAttribute('hidden', 'true');
moreInfoButton.removeAttribute('hidden');
lessInfoButton.setAttribute('hidden', 'true');
};
moreInfoButton.removeAttribute('hidden');
lessInfoButton.setAttribute('hidden', 'true');
errorMoreInfo.value = 'PDF.JS Build: ' + PDFJS.build + '\n';
if (moreInfo) {
errorMoreInfo.value += 'Message: ' + moreInfo.message;
if (moreInfo.stack)
errorMoreInfo.value += '\n' + 'Stack: ' + moreInfo.stack;
}
errorMoreInfo.rows = errorMoreInfo.value.split('\n').length - 1;
},
progress: function pdfViewProgress(level) {
@ -193,6 +366,17 @@ var PDFView = {
},
load: function pdfViewLoad(data, scale) {
function bindOnAfterDraw(pageView, thumbnailView) {
// when page is painted, using the image as thumbnail base
pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
thumbnailView.setImage(pageView.canvas);
preDraw();
};
}
var errorWrapper = document.getElementById('errorWrapper');
errorWrapper.setAttribute('hidden', 'true');
var loadingIndicator = document.getElementById('loading');
loadingIndicator.setAttribute('hidden', 'true');
@ -209,28 +393,48 @@ var PDFView = {
while (container.hasChildNodes())
container.removeChild(container.lastChild);
var pdf = new PDFJS.PDFDoc(data);
var pdf;
try {
pdf = new PDFJS.PDFDoc(data);
} catch (e) {
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 = {};
var thumbnails = this.thumbnails = [];
for (var i = 1; i <= pagesCount; i++) {
var page = pdf.getPage(i);
pages.push(new PageView(container, page, i, page.width, page.height,
page.stats, this.navigateTo.bind(this)));
thumbnails.push(new ThumbnailView(sidebar, page, i,
page.width / page.height));
var pageView = new PageView(container, page, i, page.width, page.height,
page.stats, this.navigateTo.bind(this));
var thumbnailView = new ThumbnailView(sidebar, page, i,
page.width / page.height);
bindOnAfterDraw(pageView, thumbnailView);
pages.push(pageView);
thumbnails.push(thumbnailView);
var pageRef = page.ref;
pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i;
}
this.setScale(scale || kDefaultScale, true);
this.pagesRefMap = pagesRefMap;
this.destinations = pdf.catalog.destinations;
if (pdf.catalog.documentOutline) {
this.outline = new DocumentOutlineView(pdf.catalog.documentOutline);
var outlineSwitchButton = document.getElementById('outlineSwitch');
@ -238,12 +442,20 @@ var PDFView = {
this.switchSidebarView('outline');
}
// Reset the current scale, as otherwise the page's scale might not get
// updated if the zoom level stayed the same.
this.currentScale = 0;
this.currentScaleValue = null;
if (this.initialBookmark) {
this.setHash(this.initialBookmark);
this.initialBookmark = null;
}
else
else if (storedHash)
this.setHash(storedHash);
else {
this.parseScale(scale || kDefaultScale, true);
this.page = 1;
}
},
setHash: function pdfViewSetHash(hash) {
@ -269,8 +481,16 @@ 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),
(zoomArgs[2] | 0), (zoomArgs[0] | 0) / 100];
// If the zoom value, it has to get divided by 100. If it is a string,
// it should stay as it is.
var zoomArg = zoomArgs[0];
var zoomArgNumber = parseFloat(zoomArg);
if (zoomArgNumber)
zoomArg = zoomArgNumber / 100;
var dest = [null, {name: 'XYZ'}, (zoomArgs[1] | 0),
(zoomArgs[2] | 0), zoomArg];
var currentPage = this.pages[pageNumber - 1];
currentPage.scrollIntoView(dest);
} else
@ -294,6 +514,7 @@ var PDFView = {
outlineScrollView.setAttribute('hidden', 'true');
thumbsSwitchButton.setAttribute('data-selected', true);
outlineSwitchButton.removeAttribute('data-selected');
updateThumbViewArea();
break;
case 'outline':
thumbsScrollView.setAttribute('hidden', 'true');
@ -328,6 +549,34 @@ var PDFView = {
currentHeight += singlePage.height * singlePage.scale + kBottomMargin;
}
return visiblePages;
},
getVisibleThumbs: function pdfViewGetVisibleThumbs() {
var thumbs = this.thumbnails;
var kBottomMargin = 5;
var visibleThumbs = [];
var view = document.getElementById('sidebarScrollView');
var currentHeight = kBottomMargin;
var top = view.scrollTop;
for (var i = 1; i <= thumbs.length; ++i) {
var thumb = thumbs[i - 1];
var thumbHeight = thumb.height * thumb.scaleY + kBottomMargin;
if (currentHeight + thumbHeight > top)
break;
currentHeight += thumbHeight;
}
var bottom = top + view.clientHeight;
for (; i <= thumbs.length && currentHeight < bottom; ++i) {
var singleThumb = thumbs[i - 1];
visibleThumbs.push({ id: singleThumb.id, y: currentHeight,
view: singleThumb });
currentHeight += singleThumb.height * singleThumb.scaleY + kBottomMargin;
}
return visibleThumbs;
}
};
@ -360,9 +609,11 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
while (div.hasChildNodes())
div.removeChild(div.lastChild);
div.removeAttribute('data-loaded');
delete this.canvas;
};
function setupLinks(content, scale) {
function setupAnnotations(content, scale) {
function bindLink(link, dest) {
link.href = PDFView.getDestinationHash(dest);
link.onclick = function pageViewSetupLinksOnclick() {
@ -371,18 +622,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;
}
}
}
@ -441,7 +741,7 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
];
if (scale && scale !== PDFView.currentScale)
PDFView.setScale(scale, true);
PDFView.parseScale(scale, true);
setTimeout(function pageViewScrollIntoViewRelayout() {
// letting page to re-layout before scrolling
@ -464,16 +764,26 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
}, 0);
};
this.draw = function pageviewDraw() {
if (div.hasChildNodes()) {
this.drawingRequired = function() {
return !div.hasChildNodes();
};
this.draw = function pageviewDraw(callback) {
if (!this.drawingRequired()) {
this.updateStats();
return false;
callback();
return;
}
var canvas = document.createElement('canvas');
canvas.id = 'page' + this.id;
canvas.mozOpaque = true;
div.appendChild(canvas);
this.canvas = canvas;
var textLayer = document.createElement('div');
textLayer.className = 'textLayer';
div.appendChild(textLayer);
var scale = this.scale;
canvas.width = pageWidth * scale;
@ -487,12 +797,21 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
ctx.translate(-this.x * scale, -this.y * scale);
stats.begin = Date.now();
this.content.startRendering(ctx, this.updateStats);
this.content.startRendering(ctx,
(function pageViewDrawCallback(error) {
if (error)
PDFView.error('An error occurred while rendering the page.', error);
this.updateStats();
if (this.onAfterDraw)
this.onAfterDraw();
setupLinks(this.content, this.scale);
cache.push(this);
callback();
}).bind(this), new TextLayerBuilder(textLayer)
);
setupAnnotations(this.content, this.scale);
div.setAttribute('data-loaded', true);
return true;
};
this.updateStats = function pageViewUpdateStats() {
@ -511,6 +830,19 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) {
return false;
};
var view = page.view;
this.width = view.width;
this.height = view.height;
this.id = id;
var maxThumbSize = 134;
var canvasWidth = pageRatio >= 1 ? maxThumbSize :
maxThumbSize * pageRatio;
var canvasHeight = pageRatio <= 1 ? maxThumbSize :
maxThumbSize / pageRatio;
var scaleX = this.scaleX = (canvasWidth / this.width);
var scaleY = this.scaleY = (canvasHeight / this.height);
var div = document.createElement('div');
div.id = 'thumbnailContainer' + id;
div.className = 'thumbnail';
@ -518,19 +850,15 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) {
anchor.appendChild(div);
container.appendChild(anchor);
this.draw = function thumbnailViewDraw() {
if (div.hasChildNodes())
return;
this.hasImage = false;
function getPageDrawContext() {
var canvas = document.createElement('canvas');
canvas.id = 'thumbnail' + id;
canvas.mozOpaque = true;
var maxThumbSize = 134;
canvas.width = pageRatio >= 1 ? maxThumbSize :
maxThumbSize * pageRatio;
canvas.height = pageRatio <= 1 ? maxThumbSize :
maxThumbSize / pageRatio;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
div.setAttribute('data-loaded', true);
div.appendChild(canvas);
@ -542,14 +870,37 @@ var ThumbnailView = function thumbnailView(container, page, id, pageRatio) {
ctx.restore();
var view = page.view;
var scaleX = (canvas.width / page.width);
var scaleY = (canvas.height / page.height);
ctx.translate(-view.x * scaleX, -view.y * scaleY);
div.style.width = (view.width * scaleX) + 'px';
div.style.height = (view.height * scaleY) + 'px';
div.style.lineHeight = (view.height * scaleY) + 'px';
page.startRendering(ctx, function thumbnailViewDrawStartRendering() {});
return ctx;
}
this.draw = function thumbnailViewDraw(callback) {
if (this.hasImage) {
callback();
return;
}
var ctx = getPageDrawContext();
page.startRendering(ctx, function thumbnailViewDrawStartRendering() {
callback();
});
this.hasImage = true;
};
this.setImage = function thumbnailViewSetImage(img) {
if (this.hasImage)
return;
var ctx = getPageDrawContext();
ctx.drawImage(img, 0, 0, img.width, img.height,
0, 0, ctx.canvas.width, ctx.canvas.height);
this.hasImage = true;
};
};
@ -589,6 +940,53 @@ var DocumentOutlineView = function documentOutlineView(outline) {
}
};
var TextLayerBuilder = function textLayerBuilder(textLayerDiv) {
this.textLayerDiv = textLayerDiv;
this.beginLayout = function textLayerBuilderBeginLayout() {
this.textDivs = [];
this.textLayerQueue = [];
};
this.endLayout = function textLayerBuilderEndLayout() {
var self = this;
var textDivs = this.textDivs;
var textLayerDiv = this.textLayerDiv;
this.textLayerTimer = setInterval(function renderTextLayer() {
if (textDivs.length === 0) {
clearInterval(self.textLayerTimer);
return;
}
var textDiv = textDivs.shift();
if (textDiv.dataset.textLength >= 1) { // avoid div by zero
textLayerDiv.appendChild(textDiv);
// Adjust div width (via letterSpacing) to match canvas text
// Due to the .offsetWidth calls, this is slow
textDiv.style.letterSpacing =
((textDiv.dataset.canvasWidth - textDiv.offsetWidth) /
(textDiv.dataset.textLength - 1)) + 'px';
}
}, 0);
};
this.appendText = function textLayerBuilderAppendText(text,
fontName, fontSize) {
var textDiv = document.createElement('div');
// vScale and hScale already contain the scaling to pixel units
var fontHeight = fontSize * text.geom.vScale;
textDiv.dataset.canvasWidth = text.canvasWidth * text.geom.hScale;
textDiv.style.fontSize = fontHeight + 'px';
textDiv.style.fontFamily = fontName || 'sans-serif';
textDiv.style.left = text.geom.x + 'px';
textDiv.style.top = (text.geom.y - fontHeight) + 'px';
textDiv.textContent = text.str;
textDiv.dataset.textLength = text.length;
this.textDivs.push(textDiv);
};
};
window.addEventListener('load', function webViewerLoad(evt) {
var params = document.location.search.substring(1).split('&');
for (var i = 0; i < params.length; i++) {
@ -596,44 +994,95 @@ window.addEventListener('load', function webViewerLoad(evt) {
params[unescape(param[0])] = unescape(param[1]);
}
var scale = ('scale' in params) ? params.scale : kDefaultScale;
var scale = ('scale' in params) ? params.scale : 0;
PDFView.open(params.file || kDefaultURL, parseFloat(scale));
if (!window.File || !window.FileReader || !window.FileList || !window.Blob)
document.getElementById('fileInput').setAttribute('hidden', 'true');
else
document.getElementById('fileInput').value = null;
if ('disableWorker' in params)
PDFJS.disableWorker = params['disableWorker'] === 'true' ? true : false;
var sidebarScrollView = document.getElementById('sidebarScrollView');
sidebarScrollView.addEventListener('scroll', updateThumbViewArea, true);
}, true);
window.addEventListener('unload', function webViewerUnload(evt) {
window.scrollTo(0, 0);
}, true);
/**
* Render the next not yet visible page already such that it is
* hopefully ready once the user scrolls to it.
*/
function preDraw() {
var pages = PDFView.pages;
var visible = PDFView.getVisiblePages();
var last = visible[visible.length - 1];
// PageView.id is the actual page number, which is + 1 compared
// to the index in `pages`. That means, pages[last.id] is the next
// PageView instance.
if (pages[last.id] && pages[last.id].drawingRequired()) {
renderingQueue.enqueueDraw(pages[last.id]);
return;
}
// If there is nothing to draw on the next page, maybe the user
// is scrolling up, so, let's try to render the next page *before*
// the first visible page
if (pages[visible[0].id - 2]) {
renderingQueue.enqueueDraw(pages[visible[0].id - 2]);
}
}
function updateViewarea() {
var visiblePages = PDFView.getVisiblePages();
var pageToDraw;
for (var i = 0; i < visiblePages.length; i++) {
var page = visiblePages[i];
if (PDFView.pages[page.id - 1].draw())
cache.push(page.view);
var pageObj = PDFView.pages[page.id - 1];
pageToDraw |= pageObj.drawingRequired();
renderingQueue.enqueueDraw(pageObj);
}
if (!visiblePages.length)
return;
// If there is no need to draw a page that is currenlty visible, preDraw the
// next page the user might scroll to.
if (!pageToDraw) {
preDraw();
}
updateViewarea.inProgress = true; // used in "set page"
var currentId = PDFView.page;
var firstPage = visiblePages[0];
PDFView.page = firstPage.id;
updateViewarea.inProgress = false;
var currentScale = PDFView.currentScale;
var currentScaleValue = PDFView.currentScaleValue;
var normalizedScaleValue = currentScaleValue == currentScale ?
currentScale * 100 : currentScaleValue;
var kViewerTopMargin = 52;
var pageNumber = firstPage.id;
var pdfOpenParams = '#page=' + pageNumber;
pdfOpenParams += '&zoom=' + Math.round(PDFView.currentScale * 100);
pdfOpenParams += '&zoom=' + normalizedScaleValue;
var currentPage = PDFView.pages[pageNumber - 1];
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', normalizedScaleValue);
store.set('scrollLeft', Math.round(topLeft.x));
store.set('scrollTop', Math.round(topLeft.y));
document.getElementById('viewBookmark').href = pdfOpenParams;
}
@ -641,9 +1090,33 @@ window.addEventListener('scroll', function webViewerScroll(evt) {
updateViewarea();
}, true);
var thumbnailTimer;
function updateThumbViewArea() {
// Only render thumbs after pausing scrolling for this amount of time
// (makes UI more responsive)
var delay = 50; // in ms
if (thumbnailTimer)
clearTimeout(thumbnailTimer);
thumbnailTimer = setTimeout(function() {
var visibleThumbs = PDFView.getVisibleThumbs();
for (var i = 0; i < visibleThumbs.length; i++) {
var thumb = visibleThumbs[i];
renderingQueue.enqueueDraw(PDFView.thumbnails[thumb.id - 1]);
}
}, delay);
}
window.addEventListener('transitionend', updateThumbViewArea, true);
window.addEventListener('webkitTransitionEnd', updateThumbViewArea, true);
window.addEventListener('resize', function webViewerResize(evt) {
if (document.getElementById('pageWidthOption').selected ||
document.getElementById('pageFitOption').selected)
document.getElementById('pageFitOption').selected ||
document.getElementById('pageAutoOption').selected)
PDFView.parseScale(document.getElementById('scaleSelect').value);
updateViewarea();
});
@ -673,7 +1146,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.
@ -681,35 +1153,9 @@ window.addEventListener('change', function webViewerChange(evt) {
document.getElementById('download').setAttribute('hidden', 'true');
}, true);
window.addEventListener('transitionend', function webViewerTransitionend(evt) {
var pageIndex = 0;
var pagesCount = PDFView.pages.length;
var container = document.getElementById('sidebarView');
container._interval = window.setInterval(function interval() {
if (pageIndex >= pagesCount) {
window.clearInterval(container._interval);
return;
}
PDFView.thumbnails[pageIndex++].draw();
}, 500);
}, true);
window.addEventListener('scalechange', function scalechange(evt) {
var customScaleOption = document.getElementById('customScaleOption');
customScaleOption.selected = false;
if (!evt.resetAutoSettings &&
(document.getElementById('pageWidthOption').selected ||
document.getElementById('pageFitOption').selected)) {
updateViewarea();
return;
}
function selectScaleOption(value) {
var options = document.getElementById('scaleSelect').options;
var predefinedValueFound = false;
var value = '' + evt.scale;
for (var i = 0; i < options.length; i++) {
var option = options[i];
if (option.value != value) {
@ -719,7 +1165,22 @@ window.addEventListener('scalechange', function scalechange(evt) {
option.selected = true;
predefinedValueFound = true;
}
return predefinedValueFound;
}
window.addEventListener('scalechange', function scalechange(evt) {
var customScaleOption = document.getElementById('customScaleOption');
customScaleOption.selected = false;
if (!evt.resetAutoSettings &&
(document.getElementById('pageWidthOption').selected ||
document.getElementById('pageFitOption').selected ||
document.getElementById('pageAutoOption').selected)) {
updateViewarea();
return;
}
var predefinedValueFound = selectScaleOption('' + evt.scale);
if (!predefinedValueFound) {
customScaleOption.textContent = Math.round(evt.scale * 10000) / 100 + '%';
customScaleOption.selected = true;
@ -737,25 +1198,49 @@ 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;
var controlsElement = document.getElementById('controls');
while (curElement) {
if (curElement === controlsElement)
return; // ignoring if the 'controls' element is focused
curElement = curElement.parentNode;
}
var handled = false;
switch (evt.keyCode) {
case 61: // FF/Mac '='
case 107: // FF '+' and '='
case 187: // Chrome '+'
PDFView.zoomIn();
handled = true;
break;
case 109: // FF '-'
case 189: // Chrome '-'
PDFView.zoomOut();
handled = true;
break;
case 48: // '0'
PDFView.setScale(kDefaultScale, true);
handled = true;
break;
case 37: // left arrow
case 75: // 'k'
case 80: // 'p'
PDFView.page--;
handled = true;
break;
case 39: // right arrow
case 74: // 'j'
case 78: // 'n'
PDFView.page++;
handled = true;
break;
}
if (handled) {
evt.preventDefault();
}
});