diff --git a/canvas_proxy.js b/canvas_proxy.js
new file mode 100644
index 000000000..d6f5a0a25
--- /dev/null
+++ b/canvas_proxy.js
@@ -0,0 +1,250 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+"use strict";
+
+var JpegStreamProxyCounter = 0;
+// WebWorker Proxy for JpegStream.
+var JpegStreamProxy = (function() {
+ function constructor(bytes, dict) {
+ this.id = JpegStreamProxyCounter++;
+ this.dict = dict;
+
+ // Tell the main thread to create an image.
+ postMessage({
+ action: "jpeg_stream",
+ data: {
+ id: this.id,
+ raw: bytesToString(bytes)
+ }
+ });
+ }
+
+ constructor.prototype = {
+ getImage: function() {
+ return this;
+ },
+ getChar: function() {
+ error("internal error: getChar is not valid on JpegStream");
+ }
+ };
+
+ return constructor;
+})();
+
+// Really simple GradientProxy. There is currently only one active gradient at
+// the time, meaning you can't create a gradient, create a second one and then
+// use the first one again. As this isn't used in pdf.js right now, it's okay.
+function GradientProxy(cmdQueue, x0, y0, x1, y1) {
+ cmdQueue.push(["$createLinearGradient", [x0, y0, x1, y1]]);
+ this.addColorStop = function(i, rgba) {
+ cmdQueue.push(["$addColorStop", [i, rgba]]);
+ }
+}
+
+// Really simple PatternProxy.
+var patternProxyCounter = 0;
+function PatternProxy(cmdQueue, object, kind) {
+ this.id = patternProxyCounter++;
+
+ if (!(object instanceof CanvasProxy) ) {
+ throw "unkown type to createPattern";
+ }
+
+ // Flush the object here to ensure it's available on the main thread.
+ // TODO: Make some kind of dependency management, such that the object
+ // gets flushed only if needed.
+ object.flush();
+ cmdQueue.push(["$createPatternFromCanvas", [this.id, object.id, kind]]);
+}
+
+var canvasProxyCounter = 0;
+function CanvasProxy(width, height) {
+ this.id = canvasProxyCounter++;
+
+ // The `stack` holds the rendering calls and gets flushed to the main thead.
+ var cmdQueue = this.cmdQueue = [];
+
+ // Dummy context that gets exposed.
+ var ctx = {};
+ this.getContext = function(type) {
+ if (type != "2d") {
+ throw "CanvasProxy can only provide a 2d context.";
+ }
+ return ctx;
+ }
+
+ // Expose only the minimum of the canvas object - there is no dom to do
+ // more here.
+ this.width = width;
+ this.height = height;
+ ctx.canvas = this;
+
+ // Setup function calls to `ctx`.
+ var ctxFunc = [
+ "createRadialGradient",
+ "arcTo",
+ "arc",
+ "fillText",
+ "strokeText",
+ "createImageData",
+ "drawWindow",
+ "save",
+ "restore",
+ "scale",
+ "rotate",
+ "translate",
+ "transform",
+ "setTransform",
+ "clearRect",
+ "fillRect",
+ "strokeRect",
+ "beginPath",
+ "closePath",
+ "moveTo",
+ "lineTo",
+ "quadraticCurveTo",
+ "bezierCurveTo",
+ "rect",
+ "fill",
+ "stroke",
+ "clip",
+ "measureText",
+ "isPointInPath",
+
+ // These functions are necessary to track the rendering currentX state.
+ // The exact values can be computed on the main thread only, as the
+ // worker has no idea about text width.
+ "$setCurrentX",
+ "$addCurrentX",
+ "$saveCurrentX",
+ "$restoreCurrentX",
+ "$showText"
+ ];
+
+ function buildFuncCall(name) {
+ return function() {
+ // console.log("funcCall", name)
+ cmdQueue.push([name, Array.prototype.slice.call(arguments)]);
+ }
+ }
+ var name;
+ for (var i = 0; i < ctxFunc.length; i++) {
+ name = ctxFunc[i];
+ ctx[name] = buildFuncCall(name);
+ }
+
+ // Some function calls that need more work.
+
+ ctx.createPattern = function(object, kind) {
+ return new PatternProxy(cmdQueue, object, kind);
+ }
+
+ ctx.createLinearGradient = function(x0, y0, x1, y1) {
+ return new GradientProxy(cmdQueue, x0, y0, x1, y1);
+ }
+
+ ctx.getImageData = function(x, y, w, h) {
+ return {
+ width: w,
+ height: h,
+ data: Uint8ClampedArray(w * h * 4)
+ };
+ }
+
+ ctx.putImageData = function(data, x, y, width, height) {
+ cmdQueue.push(["$putImageData", [data, x, y, width, height]]);
+ }
+
+ ctx.drawImage = function(image, x, y, width, height, sx, sy, swidth, sheight) {
+ if (image instanceof CanvasProxy) {
+ // Send the image/CanvasProxy to the main thread.
+ image.flush();
+ cmdQueue.push(["$drawCanvas", [image.id, x, y, sx, sy, swidth, sheight]]);
+ } else if(image instanceof JpegStreamProxy) {
+ cmdQueue.push(["$drawImage", [image.id, x, y, sx, sy, swidth, sheight]])
+ } else {
+ throw "unkown type to drawImage";
+ }
+ }
+
+ // Setup property access to `ctx`.
+ var ctxProp = {
+ // "canvas"
+ "globalAlpha": "1",
+ "globalCompositeOperation": "source-over",
+ "strokeStyle": "#000000",
+ "fillStyle": "#000000",
+ "lineWidth": "1",
+ "lineCap": "butt",
+ "lineJoin": "miter",
+ "miterLimit": "10",
+ "shadowOffsetX": "0",
+ "shadowOffsetY": "0",
+ "shadowBlur": "0",
+ "shadowColor": "rgba(0, 0, 0, 0)",
+ "font": "10px sans-serif",
+ "textAlign": "start",
+ "textBaseline": "alphabetic",
+ "mozTextStyle": "10px sans-serif",
+ "mozImageSmoothingEnabled": "true"
+ }
+
+ function buildGetter(name) {
+ return function() {
+ return ctx["$" + name];
+ }
+ }
+
+ function buildSetter(name) {
+ return function(value) {
+ cmdQueue.push(["$", name, value]);
+ return ctx["$" + name] = value;
+ }
+ }
+
+ // Setting the value to `stroke|fillStyle` needs special handling, as it
+ // might gets an gradient/pattern.
+ function buildSetterStyle(name) {
+ return function(value) {
+ if (value instanceof GradientProxy) {
+ cmdQueue.push(["$" + name + "Gradient"]);
+ } else if (value instanceof PatternProxy) {
+ cmdQueue.push(["$" + name + "Pattern", [value.id]]);
+ } else {
+ cmdQueue.push(["$", name, value]);
+ return ctx["$" + name] = value;
+ }
+ }
+ }
+
+ for (var name in ctxProp) {
+ ctx["$" + name] = ctxProp[name];
+ ctx.__defineGetter__(name, buildGetter(name));
+
+ // Special treatment for `fillStyle` and `strokeStyle`: The passed style
+ // might be a gradient. Need to check for that.
+ if (name == "fillStyle" || name == "strokeStyle") {
+ ctx.__defineSetter__(name, buildSetterStyle(name));
+ } else {
+ ctx.__defineSetter__(name, buildSetter(name));
+ }
+ }
+}
+
+/**
+* Sends the current cmdQueue of the CanvasProxy over to the main thread and
+* resets the cmdQueue.
+*/
+CanvasProxy.prototype.flush = function() {
+ postMessage({
+ action: "canvas_proxy_cmd_queue",
+ data: {
+ id: this.id,
+ cmdQueue: this.cmdQueue,
+ width: this.width,
+ height: this.height
+ }
+ });
+ this.cmdQueue.length = 0;
+}
diff --git a/fonts.js b/fonts.js
index ad3d4fd35..ac06b76ae 100644
--- a/fonts.js
+++ b/fonts.js
@@ -80,6 +80,36 @@ var Fonts = {
}
};
+var FontLoader = {
+ bind: function(fonts) {
+ var worker = (typeof window == "undefined");
+ var ready = true;
+
+ for (var i = 0; i < fonts.length; i++) {
+ var font = fonts[i];
+ if (Fonts[font.name]) {
+ ready = ready && !Fonts[font.name].loading;
+ continue;
+ }
+
+ ready = false;
+
+ var obj = new Font(font.name, font.file, font.properties);
+
+ var str = "";
+ var data = Fonts[font.name].data;
+ var length = data.length;
+ for (var j = 0; j < length; j++)
+ str += String.fromCharCode(data[j]);
+
+ worker ? obj.bindWorker(str) : obj.bindDOM(str);
+ }
+
+ return ready;
+ }
+};
+
+
/**
* 'Font' is the class the outside world should use, it encapsulate all the font
* decoding logics whatever type it is (assuming the font type is supported).
@@ -103,7 +133,7 @@ var Font = (function () {
// If the font is to be ignored, register it like an already loaded font
// to avoid the cost of waiting for it be be loaded by the platform.
- if (properties.ignore || properties.type == "TrueType" || kDisableFonts) {
+ if (properties.ignore || kDisableFonts) {
Fonts[name] = {
data: file,
loading: false,
@@ -113,13 +143,14 @@ var Font = (function () {
return;
}
+ var data;
switch (properties.type) {
case "Type1":
var cff = new CFF(name, file, properties);
this.mimetype = "font/opentype";
// Wrap the CFF data inside an OTF font file
- this.font = this.convert(name, cff, properties);
+ data = this.convert(name, cff, properties);
break;
case "TrueType":
@@ -127,7 +158,7 @@ var Font = (function () {
// Repair the TrueType file if it is can be damaged in the point of
// view of the sanitizer
- this.font = this.checkAndRepair(name, file, properties);
+ data = this.checkAndRepair(name, file, properties);
break;
default:
@@ -136,14 +167,11 @@ var Font = (function () {
}
Fonts[name] = {
- data: this.font,
+ data: data,
properties: properties,
loading: true,
cache: Object.create(null)
- }
-
- // Attach the font to the document
- this.bind();
+ };
};
function stringToArray(str) {
@@ -242,7 +270,7 @@ var Font = (function () {
return ranges;
};
- function createCMAPTable(glyphs) {
+ function createCMapTable(glyphs) {
var ranges = getRanges(glyphs);
var headerSize = (12 * 2 + (ranges.length * 4 * 2));
@@ -274,7 +302,7 @@ var Font = (function () {
var bias = 0;
for (var i = 0; i < segCount - 1; i++) {
var range = ranges[i];
- var start = range[0];
+ var start = range[0];
var end = range[1];
var delta = (((start - 1) - bias) ^ 0xffff) + 1;
bias += (end - start + 1);
@@ -284,8 +312,8 @@ var Font = (function () {
idDeltas += string16(delta);
idRangeOffsets += string16(0);
- for (var j = start; j <= end; j++)
- glyphsIds += String.fromCharCode(j);
+ for (var j = 0; j < range.length; j++)
+ glyphsIds += String.fromCharCode(range[j]);
}
startCount += "\xFF\xFF";
@@ -297,57 +325,60 @@ var Font = (function () {
idDeltas + idRangeOffsets + glyphsIds);
};
- function createOS2Table() {
- var OS2 = stringToArray(
- "\x00\x03" + // version
- "\x02\x24" + // xAvgCharWidth
- "\x01\xF4" + // usWeightClass
- "\x00\x05" + // usWidthClass
- "\x00\x00" + // fstype
- "\x02\x8A" + // ySubscriptXSize
- "\x02\xBB" + // ySubscriptYSize
- "\x00\x00" + // ySubscriptXOffset
- "\x00\x8C" + // ySubscriptYOffset
- "\x02\x8A" + // ySuperScriptXSize
- "\x02\xBB" + // ySuperScriptYSize
- "\x00\x00" + // ySuperScriptXOffset
- "\x01\xDF" + // ySuperScriptYOffset
- "\x00\x31" + // yStrikeOutSize
- "\x01\x02" + // yStrikeOutPosition
- "\x00\x00" + // sFamilyClass
- "\x02\x00\x06\x03\x00\x00\x00\x00\x00\x00" + // Panose
- "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 0-31)
- "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 32-63)
- "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 64-95)
- "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 96-127)
- "\x2A\x32\x31\x2A" + // achVendID
- "\x00\x20" + // fsSelection
- "\x00\x2D" + // usFirstCharIndex
- "\x00\x7A" + // usLastCharIndex
- "\x00\x03" + // sTypoAscender
- "\x00\x20" + // sTypeDescender
- "\x00\x38" + // sTypoLineGap
- "\x00\x5A" + // usWinAscent
- "\x02\xB4" + // usWinDescent
- "\x00\xCE\x00\x00" + // ulCodePageRange1 (Bits 0-31)
- "\x00\x01\x00\x00" + // ulCodePageRange2 (Bits 32-63)
- "\x00\x00" + // sxHeight
- "\x00\x00" + // sCapHeight
- "\x00\x01" + // usDefaultChar
- "\x00\xCD" + // usBreakChar
- "\x00\x02" // usMaxContext
- );
- return OS2;
+ function createOS2Table(properties) {
+ return "\x00\x03" + // version
+ "\x02\x24" + // xAvgCharWidth
+ "\x01\xF4" + // usWeightClass
+ "\x00\x05" + // usWidthClass
+ "\x00\x00" + // fstype
+ "\x02\x8A" + // ySubscriptXSize
+ "\x02\xBB" + // ySubscriptYSize
+ "\x00\x00" + // ySubscriptXOffset
+ "\x00\x8C" + // ySubscriptYOffset
+ "\x02\x8A" + // ySuperScriptXSize
+ "\x02\xBB" + // ySuperScriptYSize
+ "\x00\x00" + // ySuperScriptXOffset
+ "\x01\xDF" + // ySuperScriptYOffset
+ "\x00\x31" + // yStrikeOutSize
+ "\x01\x02" + // yStrikeOutPosition
+ "\x00\x00" + // sFamilyClass
+ "\x02\x00\x06\x03\x00\x00\x00\x00\x00\x00" + // Panose
+ "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 0-31)
+ "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 32-63)
+ "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 64-95)
+ "\xFF\xFF\xFF\xFF" + // ulUnicodeRange1 (Bits 96-127)
+ "\x2A\x32\x31\x2A" + // achVendID
+ "\x00\x20" + // fsSelection
+ "\x00\x2D" + // usFirstCharIndex
+ "\x00\x7A" + // usLastCharIndex
+ "\x00\x03" + // sTypoAscender
+ "\x00\x20" + // sTypeDescender
+ "\x00\x38" + // sTypoLineGap
+ string16(properties.ascent) + // usWinAscent
+ string16(properties.descent) + // usWinDescent
+ "\x00\xCE\x00\x00" + // ulCodePageRange1 (Bits 0-31)
+ "\x00\x01\x00\x00" + // ulCodePageRange2 (Bits 32-63)
+ string16(properties.xHeight) + // sxHeight
+ string16(properties.capHeight) + // sCapHeight
+ "\x00\x01" + // usDefaultChar
+ "\x00\xCD" + // usBreakChar
+ "\x00\x02"; // usMaxContext
+ };
+
+ function createPostTable(properties) {
+ TODO("Fill with real values from the font dict");
+
+ return "\x00\x03\x00\x00" + // Version number
+ string32(properties.italicAngle) + // italicAngle
+ "\x00\x00" + // underlinePosition
+ "\x00\x00" + // underlineThickness
+ "\x00\x00\x00\x00" + // isFixedPitch
+ "\x00\x00\x00\x00" + // minMemType42
+ "\x00\x00\x00\x00" + // maxMemType42
+ "\x00\x00\x00\x00" + // minMemType1
+ "\x00\x00\x00\x00"; // maxMemType1
};
- /**
- * A bunch of the OpenType code is duplicate between this class and the
- * TrueType code, this is intentional and will merge in a future version
- * where all the code relative to OpenType will probably have its own
- * class and will take decision without the Fonts consent.
- * But at the moment it allows to develop around the TrueType rewriting
- * on the fly without messing up with the 'regular' Type1 to OTF conversion.
- */
constructor.prototype = {
name: null,
font: null,
@@ -368,11 +399,11 @@ var Font = (function () {
var length = FontsUtils.bytesToInteger(file.getBytes(4));
// Read the table associated data
- var currentPosition = file.pos;
- file.pos = file.start + offset;
-
+ var previousPosition = file.pos;
+ file.pos = file.start ? file.start : 0;
+ file.skip(offset);
var data = file.getBytes(length);
- file.pos = currentPosition;
+ file.pos = previousPosition;
return {
tag: tag,
@@ -393,6 +424,77 @@ var Font = (function () {
}
};
+ function replaceCMapTable(cmap, font, properties) {
+ var version = FontsUtils.bytesToInteger(font.getBytes(2));
+ var numTables = FontsUtils.bytesToInteger(font.getBytes(2));
+
+ for (var i = 0; i < numTables; i++) {
+ var platformID = FontsUtils.bytesToInteger(font.getBytes(2));
+ var encodingID = FontsUtils.bytesToInteger(font.getBytes(2));
+ var offset = FontsUtils.bytesToInteger(font.getBytes(4));
+ var format = FontsUtils.bytesToInteger(font.getBytes(2));
+ var length = FontsUtils.bytesToInteger(font.getBytes(2));
+ var language = FontsUtils.bytesToInteger(font.getBytes(2));
+
+ if ((format == 0 && numTables == 1) ||
+ (format == 6 && numTables == 1 && !properties.encoding.empty)) {
+ // Format 0 alone is not allowed by the sanitizer so let's rewrite
+ // that to a 3-1-4 Unicode BMP table
+ TODO("Use an other source of informations than charset here, it is not reliable");
+ var charset = properties.charset;
+ var glyphs = [];
+ for (var j = 0; j < charset.length; j++) {
+ glyphs.push({
+ unicode: GlyphsUnicode[charset[j]] || 0
+ });
+ }
+
+ cmap.data = createCMapTable(glyphs);
+ } else if (format == 6 && numTables == 1) {
+ // 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
+ // table. (This looks weird, so I can have missed something), this
+ // works on Linux but seems to fails on Mac so let's rewrite the
+ // cmap table to a 3-1-4 style
+ var firstCode = FontsUtils.bytesToInteger(font.getBytes(2));
+ var entryCount = FontsUtils.bytesToInteger(font.getBytes(2));
+
+ var glyphs = [];
+ var min = 0xffff, max = 0;
+ for (var j = 0; j < entryCount; j++) {
+ var charcode = FontsUtils.bytesToInteger(font.getBytes(2));
+ glyphs.push(charcode);
+
+ if (charcode < min)
+ min = charcode;
+ if (charcode > max)
+ max = charcode;
+ }
+
+ // Since Format 6 is a dense array, check for gaps
+ for (var j = min; j < max; j++) {
+ if (glyphs.indexOf(j) == -1)
+ glyphs.push(j);
+ }
+
+ for (var j = 0; j < glyphs.length; j++)
+ glyphs[j] = { unicode: glyphs[j] + firstCode };
+
+ var ranges= getRanges(glyphs);
+ assert(ranges.length == 1, "Got " + ranges.length + " ranges in a dense array");
+
+ var encoding = properties.encoding;
+ var denseRange = ranges[0];
+ var start = denseRange[0];
+ var end = denseRange[1];
+ var index = firstCode;
+ for (var j = start; j <= end; j++)
+ encoding[index++] = glyphs[j - firstCode - 1].unicode;
+ cmap.data = createCMapTable(glyphs);
+ }
+ }
+ };
+
// Check that required tables are present
var requiredTables = [ "OS/2", "cmap", "head", "hhea",
"hmtx", "maxp", "name", "post" ];
@@ -425,7 +527,7 @@ var Font = (function () {
if (requiredTables.length && requiredTables[0] == "OS/2") {
// Create a new file to hold the new version of our truetype with a new
// header and new offsets
- var ttf = Uint8Array(kMaxFontFileSize);
+ var ttf = new Uint8Array(kMaxFontFileSize);
// The offsets object holds at the same time a representation of where
// to write the table entry information about a table and another offset
@@ -442,41 +544,19 @@ var Font = (function () {
createOpenTypeHeader("\x00\x01\x00\x00", ttf, offsets, numTables);
// Insert the missing table
- var OS2 = createOS2Table();
tables.push({
tag: "OS/2",
- data: OS2
+ data: stringToArray(createOS2Table(properties))
});
- // If the font is missing a OS/2 table it's could be an old mac font
- // without a 3-1-4 Unicode BMP table, so let's rewrite it.
- var charset = properties.charset;
- var glyphs = [];
- for (var i = 0; i < charset.length; i++) {
- glyphs.push({
- unicode: GlyphsUnicode[charset[i]]
- });
- }
-
// Replace the old CMAP table with a shiny new one
- cmap.data = createCMAPTable(glyphs);
+ replaceCMapTable(cmap, font, properties);
// Rewrite the 'post' table if needed
if (!post) {
- post =
- "\x00\x03\x00\x00" + // Version number
- "\x00\x00\x01\x00" + // italicAngle
- "\x00\x00" + // underlinePosition
- "\x00\x00" + // underlineThickness
- "\x00\x00\x00\x00" + // isFixedPitch
- "\x00\x00\x00\x00" + // minMemType42
- "\x00\x00\x00\x00" + // maxMemType42
- "\x00\x00\x00\x00" + // minMemType1
- "\x00\x00\x00\x00"; // maxMemType1
-
- tables.unshift({
+ tables.push({
tag: "post",
- data: stringToArray(post)
+ data: stringToArray(createPostTable(properties))
});
}
@@ -520,16 +600,16 @@ var Font = (function () {
return font.getBytes();
},
- convert: function font_convert(name, font, properties) {
- var otf = Uint8Array(kMaxFontFileSize);
+ convert: function font_convert(fontName, font, properties) {
+ var otf = new Uint8Array(kMaxFontFileSize);
function createNameTable(name) {
var names = [
"See original licence", // Copyright
- name, // Font family
+ fontName, // Font family
"undefined", // Font subfamily (font weight)
"uniqueID", // Unique ID
- name, // Full font name
+ fontName, // Full font name
"0.1", // Version
"undefined", // Postscript name
"undefined", // Trademark
@@ -537,7 +617,7 @@ var Font = (function () {
"undefined" // Designer
];
- var name =
+ var nameTable =
"\x00\x00" + // format
"\x00\x0A" + // Number of names Record
"\x00\x7E"; // Storage
@@ -554,21 +634,21 @@ var Font = (function () {
"\x00\x00" + // name ID
string16(str.length) +
string16(strOffset);
- name += nameRecord;
+ nameTable += nameRecord;
strOffset += str.length;
}
- name += names.join("");
- return name;
+ nameTable += names.join("");
+ return nameTable;
}
// Required Tables
var CFF =
- font.data, // PostScript Font Program
+ font.data, // PostScript Font Program
OS2, // OS/2 and Windows Specific metrics
cmap, // Character to glyphs mapping
- head, // Font eader
+ head, // Font header
hhea, // Horizontal header
hmtx, // Horizontal metrics
maxp, // Maximum profile
@@ -592,14 +672,12 @@ var Font = (function () {
createTableEntry(otf, offsets, "CFF ", CFF);
/** OS/2 */
- OS2 = createOS2Table();
+ OS2 = stringToArray(createOS2Table(properties));
createTableEntry(otf, offsets, "OS/2", OS2);
- //XXX Getting charstrings here seems wrong since this is another CFF glue
- var charstrings = font.getOrderedCharStrings(properties.glyphs);
-
/** CMAP */
- cmap = createCMAPTable(charstrings);
+ var charstrings = font.charstrings;
+ cmap = createCMapTable(charstrings);
createTableEntry(otf, offsets, "cmap", cmap);
/** HEAD */
@@ -647,11 +725,15 @@ var Font = (function () {
createTableEntry(otf, offsets, "hhea", hhea);
/** HMTX */
- hmtx = "\x01\xF4\x00\x00";
+ /* For some reasons, probably related to how the backend handle fonts,
+ * Linux seems to ignore this file and prefer the data from the CFF itself
+ * while Windows use this data. So be careful if you hack on Linux and
+ * have to touch the 'hmtx' table
+ */
+ hmtx = "\x01\xF4\x00\x00"; // Fake .notdef
+ var width = 0, lsb = 0;
for (var i = 0; i < charstrings.length; i++) {
- var charstring = charstrings[i].charstring;
- var width = charstring[1];
- var lsb = charstring[0];
+ width = charstrings[i].charstring[1];
hmtx += string16(width) + string16(lsb);
}
hmtx = stringToArray(hmtx);
@@ -668,17 +750,7 @@ var Font = (function () {
createTableEntry(otf, offsets, "name", name);
/** POST */
- // TODO: get those informations from the FontInfo structure
- post = "\x00\x03\x00\x00" + // Version number
- "\x00\x00\x01\x00" + // italicAngle
- "\x00\x00" + // underlinePosition
- "\x00\x00" + // underlineThickness
- "\x00\x00\x00\x00" + // isFixedPitch
- "\x00\x00\x00\x00" + // minMemType42
- "\x00\x00\x00\x00" + // maxMemType42
- "\x00\x00\x00\x00" + // minMemType1
- "\x00\x00\x00\x00"; // maxMemType1
- post = stringToArray(post);
+ post = stringToArray(createPostTable(properties));
createTableEntry(otf, offsets, "post", post);
// Once all the table entries header are written, dump the data!
@@ -695,54 +767,28 @@ var Font = (function () {
return fontData;
},
- bind: function font_bind() {
- var data = this.font;
+ bindWorker: function font_bindWorker(data) {
+ postMessage({
+ action: "font",
+ data: {
+ raw: data,
+ fontName: this.name,
+ mimetype: this.mimetype
+ }
+ });
+ },
+
+ bindDOM: function font_bindDom(data) {
var fontName = this.name;
/** Hack begin */
-
// Actually there is not event when a font has finished downloading so
// the following code are a dirty hack to 'guess' when a font is ready
+ // This code could go away when bug 471915 has landed
var canvas = document.createElement("canvas");
- var style = "border: 1px solid black; position:absolute; top: " +
- (debug ? (100 * fontCount) : "-200") + "px; left: 2px; width: 340px; height: 100px";
- canvas.setAttribute("style", style);
- canvas.setAttribute("width", 340);
- canvas.setAttribute("heigth", 100);
- document.body.appendChild(canvas);
-
- // Get the font size canvas think it will be for 'spaces'
var ctx = canvas.getContext("2d");
- ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
- var testString = " ";
-
- // When debugging use the characters provided by the charsets to visually
- // see what's happening instead of 'spaces'
- var debug = false;
- if (debug) {
- var name = document.createElement("font");
- name.setAttribute("style", "position: absolute; left: 20px; top: " +
- (100 * fontCount + 60) + "px");
- name.innerHTML = fontName;
- document.body.appendChild(name);
-
- // Retrieve font charset
- var charset = Fonts[fontName].properties.charset || [];
-
- // if the charset is too small make it repeat a few times
- var count = 30;
- while (count-- && charset.length <= 30)
- charset = charset.concat(charset.slice());
-
- for (var i = 0; i < charset.length; i++) {
- var unicode = GlyphsUnicode[charset[i]];
- if (!unicode)
- continue;
- testString += String.fromCharCode(unicode);
- }
-
- ctx.fillText(testString, 20, 20);
- }
+ ctx.font = "bold italic 20px " + fontName + ", Symbol";
+ var testString = " ";
// Periodicaly check for the width of the testString, it will be
// different once the real font has loaded
@@ -750,37 +796,26 @@ var Font = (function () {
var interval = window.setInterval(function canvasInterval(self) {
this.start = this.start || Date.now();
- ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial";
+ ctx.font = "bold italic 20px " + fontName + ", Symbol";
// For some reasons the font has not loaded, so mark it loaded for the
// page to proceed but cry
if ((Date.now() - this.start) >= kMaxWaitForFontFace) {
window.clearInterval(interval);
Fonts[fontName].loading = false;
- warn("Is " + fontName + " for charset: " + charset + " loaded?");
+ warn("Is " + fontName + " loaded?");
this.start = 0;
} else if (textWidth != ctx.measureText(testString).width) {
window.clearInterval(interval);
Fonts[fontName].loading = false;
this.start = 0;
}
-
- if (debug)
- ctx.fillText(testString, 20, 50);
}, 30, this);
/** Hack end */
- // Get the base64 encoding of the binary font data
- var str = "";
- var length = data.length;
- for (var i = 0; i < length; ++i)
- str += String.fromCharCode(data[i]);
-
- var base64 = window.btoa(str);
-
// Add the @font-face rule to the document
- var url = "url(data:" + this.mimetype + ";base64," + base64 + ");";
+ var url = "url(data:" + this.mimetype + ";base64," + window.btoa(data) + ");";
var rule = "@font-face { font-family:'" + fontName + "';src:" + url + "}";
var styleSheet = document.styleSheets[0];
styleSheet.insertRule(rule, styleSheet.length);
@@ -810,6 +845,9 @@ var FontsUtils = {
bytes.set([value >> 24, value >> 16, value >> 8, value]);
return [bytes[0], bytes[1], bytes[2], bytes[3]];
}
+
+ error("This number of bytes " + bytesCount + " is not supported");
+ return null;
},
bytesToInteger: function fu_bytesToInteger(bytesArray) {
@@ -1088,7 +1126,7 @@ var Type1Parser = function() {
* The CFF class takes a Type1 file and wrap it into a 'Compact Font Format',
* which itself embed Type2 charstrings.
*/
-const CFFStrings = [
+var CFFStrings = [
".notdef","space","exclam","quotedbl","numbersign","dollar","percent","ampersand",
"quoteright","parenleft","parenright","asterisk","plus","comma","hyphen","period",
"slash","zero","one","two","three","four","five","six","seven","eight","nine",
@@ -1146,6 +1184,8 @@ const CFFStrings = [
"001.003","Black","Bold","Book","Light","Medium","Regular","Roman","Semibold"
];
+var type1Parser = new Type1Parser();
+
var CFF = function(name, file, properties) {
// Get the data block containing glyphs and subrs informations
var length1 = file.dict.get("Length1");
@@ -1153,17 +1193,15 @@ var CFF = function(name, file, properties) {
file.skip(length1);
var eexecBlock = file.getBytes(length2);
- // Decrypt the data blocks and retrieve the informations from it
- var parser = new Type1Parser();
- var fontInfo = parser.extractFontProgram(eexecBlock);
+ // Decrypt the data blocks and retrieve it's content
+ var data = type1Parser.extractFontProgram(eexecBlock);
- properties.subrs = fontInfo.subrs;
- properties.glyphs = fontInfo.charstrings;
- this.data = this.wrap(name, properties);
+ this.charstrings = this.getOrderedCharStrings(data.charstrings);
+ this.data = this.wrap(name, this.charstrings, data.subrs, properties);
};
CFF.prototype = {
- createCFFIndexHeader: function(objects, isByte) {
+ createCFFIndexHeader: function cff_createCFFIndexHeader(objects, isByte) {
// First 2 bytes contains the number of objects contained into this index
var count = objects.length;
@@ -1200,18 +1238,18 @@ CFF.prototype = {
return data;
},
- encodeNumber: function(value) {
+ encodeNumber: function cff_encodeNumber(value) {
var x = 0;
if (value >= -32768 && value <= 32767) {
return [ 28, value >> 8, value & 0xFF ];
} else if (value >= (-2147483647-1) && value <= 2147483647) {
return [ 0xFF, value >> 24, Value >> 16, value >> 8, value & 0xFF ];
- } else {
- error("Value: " + value + " is not allowed");
}
+ error("Value: " + value + " is not allowed");
+ return null;
},
- getOrderedCharStrings: function(glyphs) {
+ getOrderedCharStrings: function cff_getOrderedCharStrings(glyphs) {
var charstrings = [];
for (var i = 0; i < glyphs.length; i++) {
@@ -1224,7 +1262,7 @@ CFF.prototype = {
charstrings.push({
glyph: glyph,
unicode: unicode,
- charstring: glyphs[i].data.slice()
+ charstring: glyphs[i].data
});
}
};
@@ -1257,7 +1295,7 @@ CFF.prototype = {
"hvcurveto": 31,
},
- flattenCharstring: function flattenCharstring(glyph, charstring, subrs) {
+ flattenCharstring: function flattenCharstring(charstring, subrs) {
var i = 0;
while (true) {
var obj = charstring[i];
@@ -1267,9 +1305,9 @@ CFF.prototype = {
if (obj.charAt) {
switch (obj) {
case "callsubr":
- var subr = subrs[charstring[i - 1]].slice();
+ var subr = subrs[charstring[i - 1]];
if (subr.length > 1) {
- subr = this.flattenCharstring(glyph, subr, subrs);
+ subr = this.flattenCharstring(subr, subrs);
subr.pop();
charstring.splice(i - 1, 2, subr);
} else {
@@ -1359,23 +1397,16 @@ CFF.prototype = {
i++;
}
error("failing with i = " + i + " in charstring:" + charstring + "(" + charstring.length + ")");
+ return [];
},
- wrap: function wrap(name, properties) {
- var charstrings = this.getOrderedCharStrings(properties.glyphs);
-
+ wrap: function wrap(name, charstrings, subrs, properties) {
// Starts the conversion of the Type1 charstrings to Type2
- var charstringsCount = 0;
- var charstringsDataLength = 0;
var glyphs = [];
- for (var i = 0; i < charstrings.length; i++) {
- var charstring = charstrings[i].charstring.slice();
- var glyph = charstrings[i].glyph;
-
- var flattened = this.flattenCharstring(glyph, charstring, properties.subrs);
- glyphs.push(flattened);
- charstringsCount++;
- charstringsDataLength += flattened.length;
+ var glyphsCount = charstrings.length;
+ for (var i = 0; i < glyphsCount; i++) {
+ var charstring = charstrings[i].charstring;
+ glyphs.push(this.flattenCharstring(charstring.slice(), subrs));
}
// Create a CFF font data
@@ -1410,17 +1441,16 @@ CFF.prototype = {
// Fill the charset header (first byte is the encoding)
var charset = [0x00];
- for (var i = 0; i < glyphs.length; i++) {
+ for (var i = 0; i < glyphsCount; i++) {
var index = CFFStrings.indexOf(charstrings[i].glyph);
if (index == -1)
- index = CFFStrings.length + strings.indexOf(glyph);
+ index = CFFStrings.length + strings.indexOf(charstrings[i].glyph);
var bytes = FontsUtils.integerToBytes(index, 2);
charset.push(bytes[0]);
charset.push(bytes[1]);
}
var charstringsIndex = this.createCFFIndexHeader([[0x40, 0x0E]].concat(glyphs), true);
- charstringsIndex = charstringsIndex.join(" ").split(" "); // XXX why?
//Top Dict Index
var topDictIndex = [
@@ -1446,7 +1476,7 @@ CFF.prototype = {
topDictIndex = topDictIndex.concat([28, 0, 0, 16]) // Encoding
- var charstringsOffset = charsetOffset + (charstringsCount * 2) + 1;
+ var charstringsOffset = charsetOffset + (glyphsCount * 2) + 1;
topDictIndex = topDictIndex.concat(this.encodeNumber(charstringsOffset));
topDictIndex.push(17); // charstrings
@@ -1454,7 +1484,6 @@ CFF.prototype = {
var privateOffset = charstringsOffset + charstringsIndex.length;
topDictIndex = topDictIndex.concat(this.encodeNumber(privateOffset));
topDictIndex.push(18); // Private
- topDictIndex = topDictIndex.join(" ").split(" ");
var indexes = [
topDictIndex, stringsIndex,
@@ -1484,7 +1513,6 @@ CFF.prototype = {
139, 12, 14,
28, 0, 55, 19
]);
- privateData = privateData.join(" ").split(" ");
cff.set(privateData, currentOffset);
currentOffset += privateData.length;
diff --git a/images/buttons.png b/images/buttons.png
index 682212660..3357b47d6 100644
Binary files a/images/buttons.png and b/images/buttons.png differ
diff --git a/images/source/FileButton.psd.zip b/images/source/FileButton.psd.zip
new file mode 100644
index 000000000..1f2b51cee
Binary files /dev/null and b/images/source/FileButton.psd.zip differ
diff --git a/multi-page-viewer.css b/multi-page-viewer.css
deleted file mode 100644
index f9a7837b1..000000000
--- a/multi-page-viewer.css
+++ /dev/null
@@ -1,146 +0,0 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
-/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
-
-body {
- background-color: #929292;
- font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif;
- margin: 0px;
- padding: 0px;
-}
-
-canvas {
- box-shadow: 0px 4px 10px #000;
- -moz-box-shadow: 0px 4px 10px #000;
- -webkit-box-shadow: 0px 4px 10px #000;
-}
-
-span {
- font-size: 0.8em;
-}
-
-.control {
- display: inline-block;
- float: left;
- margin: 0px 20px 0px 0px;
- padding: 0px 4px 0px 0px;
-}
-
-.control > input {
- float: left;
- border: 1px solid #4d4d4d;
- height: 20px;
- padding: 0px;
- margin: 0px 2px 0px 0px;
- border-radius: 4px;
- -moz-border-radius: 4px;
- -webkit-border-radius: 4px;
- box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
- -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
- -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-}
-
-.control > select {
- float: left;
- border: 1px solid #4d4d4d;
- height: 22px;
- padding: 2px 0px 0px;
- margin: 0px 0px 1px;
- border-radius: 4px;
- -moz-border-radius: 4px;
- -webkit-border-radius: 4px;
- box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
- -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
- -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
-}
-
-.control > span {
- cursor: default;
- float: left;
- height: 18px;
- margin: 5px 2px 0px;
- padding: 0px;
- user-select: none;
- -moz-user-select: none;
- -webkit-user-select: none;
-}
-
-.control .label {
- clear: both;
- float: left;
- font-size: 0.65em;
- margin: 2px 0px 0px;
- position: relative;
- text-align: center;
- width: 100%;
-}
-
-.page {
- width: 816px;
- height: 1056px;
- margin: 10px auto;
-}
-
-#controls {
- background-color: #eee;
- border-bottom: 1px solid #666;
- padding: 4px 0px 0px 8px;
- position:fixed;
- left: 0px;
- top: 0px;
- height: 40px;
- width: 100%;
- box-shadow: 0px 2px 8px #000;
- -moz-box-shadow: 0px 2px 8px #000;
- -webkit-box-shadow: 0px 2px 8px #000;
-}
-
-#controls input {
- user-select: text;
- -moz-user-select: text;
- -webkit-user-select: text;
-}
-
-#previousPageButton {
- background: url('images/buttons.png') no-repeat 0px -23px;
- cursor: default;
- display: inline-block;
- float: left;
- margin: 0px;
- width: 28px;
- height: 23px;
-}
-
-#previousPageButton.down {
- background: url('images/buttons.png') no-repeat 0px -46px;
-}
-
-#previousPageButton.disabled {
- background: url('images/buttons.png') no-repeat 0px 0px;
-}
-
-#nextPageButton {
- background: url('images/buttons.png') no-repeat -28px -23px;
- cursor: default;
- display: inline-block;
- float: left;
- margin: 0px;
- width: 28px;
- height: 23px;
-}
-
-#nextPageButton.down {
- background: url('images/buttons.png') no-repeat -28px -46px;
-}
-
-#nextPageButton.disabled {
- background: url('images/buttons.png') no-repeat -28px 0px;
-}
-
-#pageNumber {
- text-align: right;
-}
-
-#viewer {
- margin: 44px 0px 0px;
- padding: 8px 0px;
-}
diff --git a/multi-page-viewer.html b/multi-page-viewer.html
deleted file mode 100644
index 4e15cf4f8..000000000
--- a/multi-page-viewer.html
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-pdf.js Multi-Page Viewer
-
-
-
-
-
-
-
-
-
-
-
-
- Previous/Next
-
-
-
- /
- --
- Page Number
-
-
-
- Zoom
-
-
-
-
-
diff --git a/multi-page-viewer.js b/multi-page-viewer.js
deleted file mode 100644
index 9d9cec702..000000000
--- a/multi-page-viewer.js
+++ /dev/null
@@ -1,404 +0,0 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
-/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
-
-"use strict";
-
-var PDFViewer = {
- queryParams: {},
-
- element: null,
-
- previousPageButton: null,
- nextPageButton: null,
- pageNumberInput: null,
- scaleSelect: null,
-
- willJumpToPage: false,
-
- pdf: null,
-
- url: 'compressed.tracemonkey-pldi-09.pdf',
- pageNumber: 1,
- numberOfPages: 1,
-
- scale: 1.0,
-
- pageWidth: function() {
- return 816 * PDFViewer.scale;
- },
-
- pageHeight: function() {
- return 1056 * PDFViewer.scale;
- },
-
- lastPagesDrawn: [],
-
- visiblePages: function() {
- var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins.
- var windowTop = window.pageYOffset;
- var windowBottom = window.pageYOffset + window.innerHeight;
- var pageStartIndex = Math.floor(windowTop / pageHeight);
- var pageStopIndex = Math.ceil(windowBottom / pageHeight);
-
- var pages = [];
-
- for (var i = pageStartIndex; i <= pageStopIndex; i++) {
- pages.push(i + 1);
- }
-
- return pages;
- },
-
- createPage: function(num) {
- var anchor = document.createElement('a');
- anchor.name = '' + num;
-
- var div = document.createElement('div');
- div.id = 'pageContainer' + num;
- div.className = 'page';
- div.style.width = PDFViewer.pageWidth() + 'px';
- div.style.height = PDFViewer.pageHeight() + 'px';
-
- PDFViewer.element.appendChild(anchor);
- PDFViewer.element.appendChild(div);
- },
-
- removePage: function(num) {
- var div = document.getElementById('pageContainer' + num);
-
- if (div) {
- while (div.hasChildNodes()) {
- div.removeChild(div.firstChild);
- }
- }
- },
-
- drawPage: function(num) {
- if (!PDFViewer.pdf) {
- return;
- }
-
- var div = document.getElementById('pageContainer' + num);
- var canvas = document.createElement('canvas');
-
- if (div && !div.hasChildNodes()) {
- div.appendChild(canvas);
-
- var page = PDFViewer.pdf.getPage(num);
-
- canvas.id = 'page' + num;
- canvas.mozOpaque = true;
-
- // Canvas dimensions must be specified in CSS pixels. CSS pixels
- // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
- canvas.width = PDFViewer.pageWidth();
- canvas.height = PDFViewer.pageHeight();
-
- var ctx = canvas.getContext('2d');
- ctx.save();
- ctx.fillStyle = 'rgb(255, 255, 255)';
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- ctx.restore();
-
- var gfx = new CanvasGraphics(ctx);
- var fonts = [];
-
- // page.compile will collect all fonts for us, once we have loaded them
- // we can trigger the actual page rendering with page.display
- page.compile(gfx, fonts);
-
- var areFontsReady = true;
-
- // Inspect fonts and translate the missing one
- var fontCount = fonts.length;
-
- for (var i = 0; i < fontCount; i++) {
- var font = fonts[i];
-
- if (Fonts[font.name]) {
- areFontsReady = areFontsReady && !Fonts[font.name].loading;
- continue;
- }
-
- new Font(font.name, font.file, font.properties);
-
- areFontsReady = false;
- }
-
- var pageInterval;
-
- var delayLoadFont = function() {
- for (var i = 0; i < fontCount; i++) {
- if (Fonts[font.name].loading) {
- return;
- }
- }
-
- clearInterval(pageInterval);
-
- while (div.hasChildNodes()) {
- div.removeChild(div.firstChild);
- }
-
- PDFViewer.drawPage(num);
- }
-
- if (!areFontsReady) {
- pageInterval = setInterval(delayLoadFont, 10);
- return;
- }
-
- page.display(gfx);
- }
- },
-
- changeScale: function(num) {
- while (PDFViewer.element.hasChildNodes()) {
- PDFViewer.element.removeChild(PDFViewer.element.firstChild);
- }
-
- PDFViewer.scale = num / 100;
-
- var i;
-
- if (PDFViewer.pdf) {
- for (i = 1; i <= PDFViewer.numberOfPages; i++) {
- PDFViewer.createPage(i);
- }
-
- if (PDFViewer.numberOfPages > 0) {
- PDFViewer.drawPage(1);
- }
- }
-
- for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) {
- var option = PDFViewer.scaleSelect.childNodes[i];
-
- if (option.value == num) {
- if (!option.selected) {
- option.selected = 'selected';
- }
- } else {
- if (option.selected) {
- option.removeAttribute('selected');
- }
- }
- }
-
- PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%';
- },
-
- goToPage: function(num) {
- if (1 <= num && num <= PDFViewer.numberOfPages) {
- PDFViewer.pageNumber = num;
- PDFViewer.pageNumberInput.value = PDFViewer.pageNumber;
- PDFViewer.willJumpToPage = true;
-
- document.location.hash = PDFViewer.pageNumber;
-
- PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
- 'disabled' : '';
- PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
- 'disabled' : '';
- }
- },
-
- goToPreviousPage: function() {
- if (PDFViewer.pageNumber > 1) {
- PDFViewer.goToPage(--PDFViewer.pageNumber);
- }
- },
-
- goToNextPage: function() {
- if (PDFViewer.pageNumber < PDFViewer.numberOfPages) {
- PDFViewer.goToPage(++PDFViewer.pageNumber);
- }
- },
-
- open: function(url) {
- PDFViewer.url = url;
- document.title = url;
-
- var req = new XMLHttpRequest();
- req.open('GET', url);
- req.mozResponseType = req.responseType = 'arraybuffer';
- req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
-
- req.onreadystatechange = function() {
- if (req.readyState === 4 && req.status === req.expected) {
- var data = req.mozResponseArrayBuffer ||
- req.mozResponse ||
- req.responseArrayBuffer ||
- req.response;
-
- PDFViewer.pdf = new PDFDoc(new Stream(data));
- PDFViewer.numberOfPages = PDFViewer.pdf.numPages;
- document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString();
-
- for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
- PDFViewer.createPage(i);
- }
-
- if (PDFViewer.numberOfPages > 0) {
- PDFViewer.drawPage(1);
- }
-
- PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
- 'disabled' : '';
- PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
- 'disabled' : '';
- }
- };
-
- req.send(null);
- }
-};
-
-window.onload = function() {
-
- // Parse the URL query parameters into a cached object.
- PDFViewer.queryParams = function() {
- var qs = window.location.search.substring(1);
- var kvs = qs.split('&');
- var params = {};
- for (var i = 0; i < kvs.length; ++i) {
- var kv = kvs[i].split('=');
- params[unescape(kv[0])] = unescape(kv[1]);
- }
-
- return params;
- }();
-
- PDFViewer.element = document.getElementById('viewer');
-
- PDFViewer.pageNumberInput = document.getElementById('pageNumber');
- PDFViewer.pageNumberInput.onkeydown = function(evt) {
- var charCode = evt.charCode || evt.keyCode;
-
- // Up arrow key.
- if (charCode === 38) {
- PDFViewer.goToNextPage();
- this.select();
- }
-
- // Down arrow key.
- else if (charCode === 40) {
- PDFViewer.goToPreviousPage();
- this.select();
- }
-
- // All other non-numeric keys (excluding Left arrow, Right arrow,
- // Backspace, and Delete keys).
- else if ((charCode < 48 || charCode > 57) &&
- charCode !== 8 && // Backspace
- charCode !== 46 && // Delete
- charCode !== 37 && // Left arrow
- charCode !== 39 // Right arrow
- ) {
- return false;
- }
-
- return true;
- };
- PDFViewer.pageNumberInput.onkeyup = function(evt) {
- var charCode = evt.charCode || evt.keyCode;
-
- // All numeric keys, Backspace, and Delete.
- if ((charCode >= 48 && charCode <= 57) ||
- charCode === 8 || // Backspace
- charCode === 46 // Delete
- ) {
- PDFViewer.goToPage(this.value);
- }
-
- this.focus();
- };
-
- PDFViewer.previousPageButton = document.getElementById('previousPageButton');
- PDFViewer.previousPageButton.onclick = function(evt) {
- if (this.className.indexOf('disabled') === -1) {
- PDFViewer.goToPreviousPage();
- }
- };
- PDFViewer.previousPageButton.onmousedown = function(evt) {
- if (this.className.indexOf('disabled') === -1) {
- this.className = 'down';
- }
- };
- PDFViewer.previousPageButton.onmouseup = function(evt) {
- this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
- };
- PDFViewer.previousPageButton.onmouseout = function(evt) {
- this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
- };
-
- PDFViewer.nextPageButton = document.getElementById('nextPageButton');
- PDFViewer.nextPageButton.onclick = function(evt) {
- if (this.className.indexOf('disabled') === -1) {
- PDFViewer.goToNextPage();
- }
- };
- PDFViewer.nextPageButton.onmousedown = function(evt) {
- if (this.className.indexOf('disabled') === -1) {
- this.className = 'down';
- }
- };
- PDFViewer.nextPageButton.onmouseup = function(evt) {
- this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
- };
- PDFViewer.nextPageButton.onmouseout = function(evt) {
- this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
- };
-
- PDFViewer.scaleSelect = document.getElementById('scaleSelect');
- PDFViewer.scaleSelect.onchange = function(evt) {
- PDFViewer.changeScale(parseInt(this.value));
- };
-
- PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber;
- PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0;
-
- PDFViewer.open(PDFViewer.queryParams.file || PDFViewer.url);
-
- window.onscroll = function(evt) {
- var lastPagesDrawn = PDFViewer.lastPagesDrawn;
- var visiblePages = PDFViewer.visiblePages();
-
- var pagesToDraw = [];
- var pagesToKeep = [];
- var pagesToRemove = [];
-
- var i;
-
- // Determine which visible pages were not previously drawn.
- for (i = 0; i < visiblePages.length; i++) {
- if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) {
- pagesToDraw.push(visiblePages[i]);
- PDFViewer.drawPage(visiblePages[i]);
- } else {
- pagesToKeep.push(visiblePages[i]);
- }
- }
-
- // Determine which previously drawn pages are no longer visible.
- for (i = 0; i < lastPagesDrawn.length; i++) {
- if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) {
- pagesToRemove.push(lastPagesDrawn[i]);
- PDFViewer.removePage(lastPagesDrawn[i]);
- }
- }
-
- PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep);
-
- // Update the page number input with the current page number.
- if (!PDFViewer.willJumpToPage && visiblePages.length > 0) {
- PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0];
- PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ?
- 'disabled' : '';
- PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ?
- 'disabled' : '';
- } else {
- PDFViewer.willJumpToPage = false;
- }
- };
-};
diff --git a/multi_page_viewer.css b/multi_page_viewer.css
new file mode 100644
index 000000000..b3eaab792
--- /dev/null
+++ b/multi_page_viewer.css
@@ -0,0 +1,197 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+body {
+ background-color: #929292;
+ font-family: 'Lucida Grande', 'Lucida Sans Unicode', Helvetica, Arial, Verdana, sans-serif;
+ margin: 0px;
+ padding: 0px;
+}
+
+canvas {
+ box-shadow: 0px 4px 10px #000;
+ -moz-box-shadow: 0px 4px 10px #000;
+ -webkit-box-shadow: 0px 4px 10px #000;
+}
+
+span {
+ font-size: 0.8em;
+}
+
+.control {
+ display: inline-block;
+ float: left;
+ margin: 0px 20px 0px 0px;
+ padding: 0px 4px 0px 0px;
+}
+
+.control > input {
+ float: left;
+ border: 1px solid #4d4d4d;
+ height: 20px;
+ padding: 0px;
+ margin: 0px 2px 0px 0px;
+ border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+ -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+ -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+}
+
+.control > select {
+ float: left;
+ border: 1px solid #4d4d4d;
+ height: 22px;
+ padding: 2px 0px 0px;
+ margin: 0px 0px 1px;
+ border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+ -moz-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+ -webkit-box-shadow: 0px 1px 0px rgba(255, 255, 255, 0.25);
+}
+
+.control > span {
+ cursor: default;
+ float: left;
+ height: 18px;
+ margin: 5px 2px 0px;
+ padding: 0px;
+ user-select: none;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+}
+
+.control .label {
+ clear: both;
+ float: left;
+ font-size: 0.65em;
+ margin: 2px 0px 0px;
+ position: relative;
+ text-align: center;
+ width: 100%;
+}
+
+.page {
+ width: 816px;
+ height: 1056px;
+ margin: 10px auto;
+}
+
+#controls {
+ background-color: #eee;
+ border-bottom: 1px solid #666;
+ padding: 4px 0px 0px 8px;
+ position: fixed;
+ left: 0px;
+ top: 0px;
+ height: 40px;
+ width: 100%;
+ box-shadow: 0px 2px 8px #000;
+ -moz-box-shadow: 0px 2px 8px #000;
+ -webkit-box-shadow: 0px 2px 8px #000;
+}
+
+#controls input {
+ user-select: text;
+ -moz-user-select: text;
+ -webkit-user-select: text;
+}
+
+#previousPageButton {
+ background: url('images/buttons.png') no-repeat 0px -23px;
+ cursor: default;
+ display: inline-block;
+ float: left;
+ margin: 0px;
+ width: 28px;
+ height: 23px;
+}
+
+#previousPageButton.down {
+ background: url('images/buttons.png') no-repeat 0px -46px;
+}
+
+#previousPageButton.disabled {
+ background: url('images/buttons.png') no-repeat 0px 0px;
+}
+
+#nextPageButton {
+ background: url('images/buttons.png') no-repeat -28px -23px;
+ cursor: default;
+ display: inline-block;
+ float: left;
+ margin: 0px;
+ width: 28px;
+ height: 23px;
+}
+
+#nextPageButton.down {
+ background: url('images/buttons.png') no-repeat -28px -46px;
+}
+
+#nextPageButton.disabled {
+ background: url('images/buttons.png') no-repeat -28px 0px;
+}
+
+#openFileButton {
+ background: url('images/buttons.png') no-repeat -56px -23px;
+ cursor: default;
+ display: inline-block;
+ float: left;
+ margin: 0px 0px 0px 3px;
+ width: 29px;
+ height: 23px;
+}
+
+#openFileButton.down {
+ background: url('images/buttons.png') no-repeat -56px -46px;
+}
+
+#openFileButton.disabled {
+ background: url('images/buttons.png') no-repeat -56px 0px;
+}
+
+#fileInput {
+ display: none;
+}
+
+#pageNumber {
+ text-align: right;
+}
+
+#sidebar {
+ background-color: rgba(0, 0, 0, 0.8);
+ position: fixed;
+ width: 150px;
+ top: 62px;
+ bottom: 18px;
+ border-top-right-radius: 8px;
+ border-bottom-right-radius: 8px;
+ -moz-border-radius-topright: 8px;
+ -moz-border-radius-bottomright: 8px;
+ -webkit-border-top-right-radius: 8px;
+ -webkit-border-bottom-right-radius: 8px;
+}
+
+#sidebarScrollView {
+ position: absolute;
+ overflow: hidden;
+ overflow-y: auto;
+ top: 40px;
+ right: 10px;
+ bottom: 10px;
+ left: 10px;
+}
+
+#sidebarContentView {
+ height: auto;
+ width: 100px;
+}
+
+#viewer {
+ margin: 44px 0px 0px;
+ padding: 8px 0px;
+}
diff --git a/multi_page_viewer.html b/multi_page_viewer.html
new file mode 100644
index 000000000..47234686d
--- /dev/null
+++ b/multi_page_viewer.html
@@ -0,0 +1,51 @@
+
+
+
+pdf.js Multi-Page Viewer
+
+
+
+
+
+
+
+
+
+
+
+
+ Previous/Next
+
+
+
+ /
+ --
+ Page Number
+
+
+
+ Zoom
+
+
+
+
+ Open File
+
+
+
+
+
+
diff --git a/multi_page_viewer.js b/multi_page_viewer.js
new file mode 100644
index 000000000..f262734d3
--- /dev/null
+++ b/multi_page_viewer.js
@@ -0,0 +1,423 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+"use strict";
+
+var pageTimeout;
+
+var PDFViewer = {
+ queryParams: {},
+
+ element: null,
+
+ previousPageButton: null,
+ nextPageButton: null,
+ pageNumberInput: null,
+ scaleSelect: null,
+ fileInput: null,
+
+ willJumpToPage: false,
+
+ pdf: null,
+
+ url: 'compressed.tracemonkey-pldi-09.pdf',
+ pageNumber: 1,
+ numberOfPages: 1,
+
+ scale: 1.0,
+
+ pageWidth: function() {
+ return 816 * PDFViewer.scale;
+ },
+
+ pageHeight: function() {
+ return 1056 * PDFViewer.scale;
+ },
+
+ lastPagesDrawn: [],
+
+ visiblePages: function() {
+ var pageHeight = PDFViewer.pageHeight() + 20; // Add 20 for the margins.
+ var windowTop = window.pageYOffset;
+ var windowBottom = window.pageYOffset + window.innerHeight;
+ var pageStartIndex = Math.floor(windowTop / pageHeight);
+ var pageStopIndex = Math.ceil(windowBottom / pageHeight);
+
+ var pages = [];
+
+ for (var i = pageStartIndex; i <= pageStopIndex; i++) {
+ pages.push(i + 1);
+ }
+
+ return pages;
+ },
+
+ createPage: function(num) {
+ var anchor = document.createElement('a');
+ anchor.name = '' + num;
+
+ var div = document.createElement('div');
+ div.id = 'pageContainer' + num;
+ div.className = 'page';
+ div.style.width = PDFViewer.pageWidth() + 'px';
+ div.style.height = PDFViewer.pageHeight() + 'px';
+
+ PDFViewer.element.appendChild(anchor);
+ PDFViewer.element.appendChild(div);
+ },
+
+ removePage: function(num) {
+ var div = document.getElementById('pageContainer' + num);
+
+ if (div) {
+ while (div.hasChildNodes()) {
+ div.removeChild(div.firstChild);
+ }
+ }
+ },
+
+ drawPage: function(num) {
+ if (!PDFViewer.pdf)
+ return;
+
+ var div = document.getElementById('pageContainer' + num);
+ var canvas = document.createElement('canvas');
+
+ if (div && !div.hasChildNodes()) {
+ var page = PDFViewer.pdf.getPage(num);
+
+ canvas.id = 'page' + num;
+ canvas.mozOpaque = true;
+
+ // Canvas dimensions must be specified in CSS pixels. CSS pixels
+ // are always 96 dpi. These dimensions are 8.5in x 11in at 96dpi.
+ canvas.width = PDFViewer.pageWidth();
+ canvas.height = PDFViewer.pageHeight();
+ div.appendChild(canvas);
+
+ var ctx = canvas.getContext('2d');
+ ctx.save();
+ ctx.fillStyle = 'rgb(255, 255, 255)';
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ ctx.restore();
+
+ var gfx = new CanvasGraphics(ctx);
+
+ // page.compile will collect all fonts for us, once we have loaded them
+ // we can trigger the actual page rendering with page.display
+ var fonts = [];
+ page.compile(gfx, fonts);
+
+ var loadFont = function() {
+ if (!FontLoader.bind(fonts)) {
+ pageTimeout = window.setTimeout(loadFont, 10);
+ return;
+ }
+ page.display(gfx);
+ }
+ loadFont();
+ }
+ },
+
+ changeScale: function(num) {
+ while (PDFViewer.element.hasChildNodes()) {
+ PDFViewer.element.removeChild(PDFViewer.element.firstChild);
+ }
+
+ PDFViewer.scale = num / 100;
+
+ var i;
+
+ if (PDFViewer.pdf) {
+ for (i = 1; i <= PDFViewer.numberOfPages; i++) {
+ PDFViewer.createPage(i);
+ }
+
+ if (PDFViewer.numberOfPages > 0) {
+ PDFViewer.drawPage(1);
+ }
+ }
+
+ for (i = 0; i < PDFViewer.scaleSelect.childNodes; i++) {
+ var option = PDFViewer.scaleSelect.childNodes[i];
+
+ if (option.value == num) {
+ if (!option.selected) {
+ option.selected = 'selected';
+ }
+ } else {
+ if (option.selected) {
+ option.removeAttribute('selected');
+ }
+ }
+ }
+
+ PDFViewer.scaleSelect.value = Math.floor(PDFViewer.scale * 100) + '%';
+ },
+
+ goToPage: function(num) {
+ if (1 <= num && num <= PDFViewer.numberOfPages) {
+ PDFViewer.pageNumber = num;
+ PDFViewer.pageNumberInput.value = PDFViewer.pageNumber;
+ PDFViewer.willJumpToPage = true;
+
+ document.location.hash = PDFViewer.pageNumber;
+
+ PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
+ PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : '';
+ }
+ },
+
+ goToPreviousPage: function() {
+ if (PDFViewer.pageNumber > 1) {
+ PDFViewer.goToPage(--PDFViewer.pageNumber);
+ }
+ },
+
+ goToNextPage: function() {
+ if (PDFViewer.pageNumber < PDFViewer.numberOfPages) {
+ PDFViewer.goToPage(++PDFViewer.pageNumber);
+ }
+ },
+
+ openURL: function(url) {
+ PDFViewer.url = url;
+ document.title = url;
+
+ var req = new XMLHttpRequest();
+ req.open('GET', url);
+ req.mozResponseType = req.responseType = 'arraybuffer';
+ req.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
+
+ req.onreadystatechange = function() {
+ if (req.readyState === 4 && req.status === req.expected) {
+ var data = req.mozResponseArrayBuffer || req.mozResponse || req.responseArrayBuffer || req.response;
+
+ PDFViewer.readPDF(data);
+ }
+ };
+
+ req.send(null);
+ },
+
+ readPDF: function(data) {
+ while (PDFViewer.element.hasChildNodes()) {
+ PDFViewer.element.removeChild(PDFViewer.element.firstChild);
+ }
+
+ PDFViewer.pdf = new PDFDoc(new Stream(data));
+ PDFViewer.numberOfPages = PDFViewer.pdf.numPages;
+ document.getElementById('numPages').innerHTML = PDFViewer.numberOfPages.toString();
+
+ for (var i = 1; i <= PDFViewer.numberOfPages; i++) {
+ PDFViewer.createPage(i);
+ }
+
+ if (PDFViewer.numberOfPages > 0) {
+ PDFViewer.drawPage(1);
+ document.location.hash = 1;
+ }
+
+ PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
+ PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : '';
+ }
+};
+
+window.onload = function() {
+ // Parse the URL query parameters into a cached object.
+ PDFViewer.queryParams = function() {
+ var qs = window.location.search.substring(1);
+ var kvs = qs.split('&');
+ var params = {};
+
+ for (var i = 0; i < kvs.length; ++i) {
+ var kv = kvs[i].split('=');
+ params[unescape(kv[0])] = unescape(kv[1]);
+ }
+
+ return params;
+ }();
+
+ PDFViewer.element = document.getElementById('viewer');
+
+ PDFViewer.pageNumberInput = document.getElementById('pageNumber');
+ PDFViewer.pageNumberInput.onkeydown = function(evt) {
+ var charCode = evt.charCode || evt.keyCode;
+
+ // Up arrow key.
+ if (charCode === 38) {
+ PDFViewer.goToNextPage();
+ this.select();
+ }
+
+ // Down arrow key.
+ else if (charCode === 40) {
+ PDFViewer.goToPreviousPage();
+ this.select();
+ }
+
+ // All other non-numeric keys (excluding Left arrow, Right arrow,
+ // Backspace, and Delete keys).
+ else if ((charCode < 48 || charCode > 57) &&
+ charCode !== 8 && // Backspace
+ charCode !== 46 && // Delete
+ charCode !== 37 && // Left arrow
+ charCode !== 39 // Right arrow
+ ) {
+ return false;
+ }
+
+ return true;
+ };
+ PDFViewer.pageNumberInput.onkeyup = function(evt) {
+ var charCode = evt.charCode || evt.keyCode;
+
+ // All numeric keys, Backspace, and Delete.
+ if ((charCode >= 48 && charCode <= 57) ||
+ charCode === 8 || // Backspace
+ charCode === 46 // Delete
+ ) {
+ PDFViewer.goToPage(this.value);
+ }
+
+ this.focus();
+ };
+
+ PDFViewer.previousPageButton = document.getElementById('previousPageButton');
+ PDFViewer.previousPageButton.onclick = function(evt) {
+ if (this.className.indexOf('disabled') === -1) {
+ PDFViewer.goToPreviousPage();
+ }
+ };
+ PDFViewer.previousPageButton.onmousedown = function(evt) {
+ if (this.className.indexOf('disabled') === -1) {
+ this.className = 'down';
+ }
+ };
+ PDFViewer.previousPageButton.onmouseup = function(evt) {
+ this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+ };
+ PDFViewer.previousPageButton.onmouseout = function(evt) {
+ this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+ };
+
+ PDFViewer.nextPageButton = document.getElementById('nextPageButton');
+ PDFViewer.nextPageButton.onclick = function(evt) {
+ if (this.className.indexOf('disabled') === -1) {
+ PDFViewer.goToNextPage();
+ }
+ };
+ PDFViewer.nextPageButton.onmousedown = function(evt) {
+ if (this.className.indexOf('disabled') === -1) {
+ this.className = 'down';
+ }
+ };
+ PDFViewer.nextPageButton.onmouseup = function(evt) {
+ this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+ };
+ PDFViewer.nextPageButton.onmouseout = function(evt) {
+ this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+ };
+
+ PDFViewer.scaleSelect = document.getElementById('scaleSelect');
+ PDFViewer.scaleSelect.onchange = function(evt) {
+ PDFViewer.changeScale(parseInt(this.value));
+ };
+
+ if (window.File && window.FileReader && window.FileList && window.Blob) {
+ var openFileButton = document.getElementById('openFileButton');
+ openFileButton.onclick = function(evt) {
+ if (this.className.indexOf('disabled') === -1) {
+ PDFViewer.fileInput.click();
+ }
+ };
+ openFileButton.onmousedown = function(evt) {
+ if (this.className.indexOf('disabled') === -1) {
+ this.className = 'down';
+ }
+ };
+ openFileButton.onmouseup = function(evt) {
+ this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+ };
+ openFileButton.onmouseout = function(evt) {
+ this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : '';
+ };
+
+ PDFViewer.fileInput = document.getElementById('fileInput');
+ PDFViewer.fileInput.onchange = function(evt) {
+ var files = evt.target.files;
+
+ if (files.length > 0) {
+ var file = files[0];
+ var fileReader = new FileReader();
+
+ document.title = file.name;
+
+ // Read the local file into a Uint8Array.
+ fileReader.onload = function(evt) {
+ var data = evt.target.result;
+ var buffer = new ArrayBuffer(data.length);
+ var uint8Array = new Uint8Array(buffer);
+
+ for (var i = 0; i < data.length; i++) {
+ uint8Array[i] = data.charCodeAt(i);
+ }
+
+ PDFViewer.readPDF(uint8Array);
+ };
+
+ // Read as a binary string since "readAsArrayBuffer" is not yet
+ // implemented in Firefox.
+ fileReader.readAsBinaryString(file);
+ }
+ };
+ PDFViewer.fileInput.value = null;
+ } else {
+ document.getElementById('fileWrapper').style.display = 'none';
+ }
+
+ PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber;
+ PDFViewer.scale = parseInt(PDFViewer.scaleSelect.value) / 100 || 1.0;
+
+ PDFViewer.openURL(PDFViewer.queryParams.file || PDFViewer.url);
+
+ window.onscroll = function(evt) {
+ var lastPagesDrawn = PDFViewer.lastPagesDrawn;
+ var visiblePages = PDFViewer.visiblePages();
+
+ var pagesToDraw = [];
+ var pagesToKeep = [];
+ var pagesToRemove = [];
+
+ var i;
+
+ // Determine which visible pages were not previously drawn.
+ for (i = 0; i < visiblePages.length; i++) {
+ if (lastPagesDrawn.indexOf(visiblePages[i]) === -1) {
+ pagesToDraw.push(visiblePages[i]);
+ PDFViewer.drawPage(visiblePages[i]);
+ } else {
+ pagesToKeep.push(visiblePages[i]);
+ }
+ }
+
+ // Determine which previously drawn pages are no longer visible.
+ for (i = 0; i < lastPagesDrawn.length; i++) {
+ if (visiblePages.indexOf(lastPagesDrawn[i]) === -1) {
+ pagesToRemove.push(lastPagesDrawn[i]);
+ PDFViewer.removePage(lastPagesDrawn[i]);
+ }
+ }
+
+ PDFViewer.lastPagesDrawn = pagesToDraw.concat(pagesToKeep);
+
+ // Update the page number input with the current page number.
+ if (!PDFViewer.willJumpToPage && visiblePages.length > 0) {
+ PDFViewer.pageNumber = PDFViewer.pageNumberInput.value = visiblePages[0];
+ PDFViewer.previousPageButton.className = (PDFViewer.pageNumber === 1) ? 'disabled' : '';
+ PDFViewer.nextPageButton.className = (PDFViewer.pageNumber === PDFViewer.numberOfPages) ? 'disabled' : '';
+ } else {
+ PDFViewer.willJumpToPage = false;
+ }
+ };
+};
diff --git a/pdf.js b/pdf.js
index 320fc6913..3ebedd9e1 100644
--- a/pdf.js
+++ b/pdf.js
@@ -66,62 +66,63 @@ function stringToBytes(str) {
var Stream = (function() {
function constructor(arrayBuffer, start, length, dict) {
- this.bytes = Uint8Array(arrayBuffer);
+ this.bytes = new Uint8Array(arrayBuffer);
this.start = start || 0;
this.pos = this.start;
- this.end = (start + length) || this.bytes.byteLength;
+ this.end = (start + length) || this.bytes.length;
this.dict = dict;
}
+ // required methods for a stream. if a particular stream does not
+ // implement these, an error should be thrown
constructor.prototype = {
get length() {
return this.end - this.start;
},
- getByte: function() {
- var bytes = this.bytes;
+ getByte: function stream_getByte() {
if (this.pos >= this.end)
- return -1;
- return bytes[this.pos++];
+ return null;
+ return this.bytes[this.pos++];
},
// returns subarray of original buffer
// should only be read
- getBytes: function(length) {
+ getBytes: function stream_getBytes(length) {
var bytes = this.bytes;
var pos = this.pos;
+ var strEnd = this.end;
+
+ if (!length)
+ return bytes.subarray(pos, strEnd);
var end = pos + length;
- var strEnd = this.end;
- if (!end || end > strEnd)
+ if (end > strEnd)
end = strEnd;
this.pos = end;
return bytes.subarray(pos, end);
},
- lookChar: function() {
- var bytes = this.bytes;
+ lookChar: function stream_lookChar() {
if (this.pos >= this.end)
- return;
- return String.fromCharCode(bytes[this.pos]);
+ return null;
+ return String.fromCharCode(this.bytes[this.pos]);
},
- getChar: function() {
- var ch = this.lookChar();
- if (!ch)
- return ch;
- this.pos++;
- return ch;
+ getChar: function stream_getChar() {
+ if (this.pos >= this.end)
+ return null;
+ return String.fromCharCode(this.bytes[this.pos++]);
},
- skip: function(n) {
- if (!n && !IsNum(n))
+ skip: function stream_skip(n) {
+ if (!n)
n = 1;
this.pos += n;
},
- reset: function() {
+ reset: function stream_reset() {
this.pos = this.start;
},
- moveStart: function() {
+ moveStart: function stream_moveStart() {
this.start = this.pos;
},
- makeSubStream: function(start, length, dict) {
+ makeSubStream: function stream_makeSubstream(start, length, dict) {
return new Stream(this.bytes.buffer, start, length, dict);
}
};
@@ -132,7 +133,7 @@ var Stream = (function() {
var StringStream = (function() {
function constructor(str) {
var length = str.length;
- var bytes = Uint8Array(length);
+ var bytes = new Uint8Array(length);
for (var n = 0; n < length; ++n)
bytes[n] = str.charCodeAt(n);
Stream.call(this, bytes);
@@ -143,12 +144,134 @@ var StringStream = (function() {
return constructor;
})();
+// super class for the decoding streams
+var DecodeStream = (function() {
+ function constructor() {
+ this.pos = 0;
+ this.bufferLength = 0;
+ this.eof = false;
+ this.buffer = null;
+ }
+
+ constructor.prototype = {
+ ensureBuffer: function decodestream_ensureBuffer(requested) {
+ var buffer = this.buffer;
+ var current = buffer ? buffer.byteLength : 0;
+ if (requested < current)
+ return buffer;
+ var size = 512;
+ while (size < requested)
+ size <<= 1;
+ var buffer2 = new Uint8Array(size);
+ for (var i = 0; i < current; ++i)
+ buffer2[i] = buffer[i];
+ return this.buffer = buffer2;
+ },
+ getByte: function decodestream_getByte() {
+ var pos = this.pos;
+ while (this.bufferLength <= pos) {
+ if (this.eof)
+ return null;
+ this.readBlock();
+ }
+ return this.buffer[this.pos++];
+ },
+ getBytes: function decodestream_getBytes(length) {
+ var pos = this.pos;
+
+ if (length) {
+ this.ensureBuffer(pos + length);
+ var end = pos + length;
+
+ while (!this.eof && this.bufferLength < end)
+ this.readBlock();
+
+ var bufEnd = this.bufferLength;
+ if (end > bufEnd)
+ end = bufEnd;
+ } else {
+ while (!this.eof)
+ this.readBlock();
+
+ var end = this.bufferLength;
+ }
+
+ this.pos = end;
+ return this.buffer.subarray(pos, end)
+ },
+ lookChar: function decodestream_lookChar() {
+ var pos = this.pos;
+ while (this.bufferLength <= pos) {
+ if (this.eof)
+ return null;
+ this.readBlock();
+ }
+ return String.fromCharCode(this.buffer[this.pos]);
+ },
+ getChar: function decodestream_getChar() {
+ var pos = this.pos;
+ while (this.bufferLength <= pos) {
+ if (this.eof)
+ return null;
+ this.readBlock();
+ }
+ return String.fromCharCode(this.buffer[this.pos++]);
+ },
+ skip: function decodestream_skip(n) {
+ if (!n)
+ n = 1;
+ this.pos += n;
+ }
+ };
+
+ return constructor;
+})();
+
+
+var FakeStream = (function() {
+ function constructor(stream) {
+ this.dict = stream.dict;
+ DecodeStream.call(this);
+ };
+
+ constructor.prototype = Object.create(DecodeStream.prototype);
+ constructor.prototype.readBlock = function() {
+ var bufferLength = this.bufferLength;
+ bufferLength += 1024;
+ var buffer = this.ensureBuffer(bufferLength);
+ this.bufferLength = bufferLength;
+ };
+ constructor.prototype.getBytes = function(length) {
+ var pos = this.pos;
+
+ if (length) {
+ this.ensureBuffer(pos + length);
+ var end = pos + length;
+
+ while (!this.eof && this.bufferLength < end)
+ this.readBlock();
+
+ var bufEnd = this.bufferLength;
+ if (end > bufEnd)
+ end = bufEnd;
+ } else {
+ this.eof = true;
+ var end = this.bufferLength;
+ }
+
+ this.pos = end;
+ return this.buffer.subarray(pos, end)
+ };
+
+ return constructor;
+})();
+
var FlateStream = (function() {
- const codeLenCodeMap = Uint32Array([
+ var codeLenCodeMap = new Uint32Array([
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
]);
- const lengthDecode = Uint32Array([
+ var lengthDecode = new Uint32Array([
0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009,
0x0000a, 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017,
0x2001b, 0x2001f, 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043,
@@ -156,7 +279,7 @@ var FlateStream = (function() {
0x00102, 0x00102, 0x00102
]);
- const distDecode = Uint32Array([
+ var distDecode = new Uint32Array([
0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009,
0x2000d, 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061,
0x60081, 0x600c1, 0x70101, 0x70181, 0x80201, 0x80301, 0x90401,
@@ -164,7 +287,7 @@ var FlateStream = (function() {
0xd4001, 0xd6001
]);
- const fixedLitCodeTab = [Uint32Array([
+ var fixedLitCodeTab = [new Uint32Array([
0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030,
0x900c0, 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080,
0x80040, 0x900e0, 0x70104, 0x80058, 0x80018, 0x90090, 0x70114,
@@ -241,7 +364,7 @@ var FlateStream = (function() {
0x900ff
]), 9];
- const fixedDistCodeTab = [Uint32Array([
+ var fixedDistCodeTab = [new Uint32Array([
0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c,
0x5001c, 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016,
0x5000e, 0x00000, 0x50001, 0x50011, 0x50009, 0x50019, 0x50005,
@@ -267,273 +390,399 @@ var FlateStream = (function() {
this.bytes = bytes;
this.bytesPos = bytesPos;
- this.eof = false;
+
this.codeSize = 0;
this.codeBuf = 0;
-
- this.pos = 0;
- this.bufferLength = 0;
+
+ DecodeStream.call(this);
}
- constructor.prototype = {
- getBits: function(bits) {
- var codeSize = this.codeSize;
- var codeBuf = this.codeBuf;
- var bytes = this.bytes;
- var bytesPos = this.bytesPos;
+ constructor.prototype = Object.create(DecodeStream.prototype);
- var b;
- while (codeSize < bits) {
- if (typeof (b = bytes[bytesPos++]) == "undefined")
- error("Bad encoding in flate stream");
- codeBuf |= b << codeSize;
- codeSize += 8;
- }
- b = codeBuf & ((1 << bits) - 1);
- this.codeBuf = codeBuf >> bits;
- this.codeSize = codeSize -= bits;
- this.bytesPos = bytesPos;
- return b;
- },
- getCode: function(table) {
- var codes = table[0];
- var maxLen = table[1];
- var codeSize = this.codeSize;
- var codeBuf = this.codeBuf;
- var bytes = this.bytes;
- var bytesPos = this.bytesPos;
+ constructor.prototype.getBits = function(bits) {
+ var codeSize = this.codeSize;
+ var codeBuf = this.codeBuf;
+ var bytes = this.bytes;
+ var bytesPos = this.bytesPos;
- while (codeSize < maxLen) {
- var b;
- if (typeof (b = bytes[bytesPos++]) == "undefined")
- error("Bad encoding in flate stream");
- codeBuf |= (b << codeSize);
- codeSize += 8;
- }
- var code = codes[codeBuf & ((1 << maxLen) - 1)];
- var codeLen = code >> 16;
- var codeVal = code & 0xffff;
- if (codeSize == 0|| codeSize < codeLen || codeLen == 0)
+ var b;
+ while (codeSize < bits) {
+ if (typeof (b = bytes[bytesPos++]) == "undefined")
error("Bad encoding in flate stream");
- this.codeBuf = (codeBuf >> codeLen);
- this.codeSize = (codeSize - codeLen);
- this.bytesPos = bytesPos;
- return codeVal;
- },
- ensureBuffer: function(requested) {
- var buffer = this.buffer;
- var current = buffer ? buffer.byteLength : 0;
- if (requested < current)
- return buffer;
- var size = 512;
- while (size < requested)
- size <<= 1;
- var buffer2 = Uint8Array(size);
- for (var i = 0; i < current; ++i)
- buffer2[i] = buffer[i];
- return this.buffer = buffer2;
- },
- getByte: function() {
- var pos = this.pos;
- while (this.bufferLength <= pos) {
- if (this.eof)
- return;
- this.readBlock();
- }
- return this.buffer[this.pos++];
- },
- getBytes: function(length) {
- var pos = this.pos;
+ codeBuf |= b << codeSize;
+ codeSize += 8;
+ }
+ b = codeBuf & ((1 << bits) - 1);
+ this.codeBuf = codeBuf >> bits;
+ this.codeSize = codeSize -= bits;
+ this.bytesPos = bytesPos;
+ return b;
+ };
+ constructor.prototype.getCode = function(table) {
+ var codes = table[0];
+ var maxLen = table[1];
+ var codeSize = this.codeSize;
+ var codeBuf = this.codeBuf;
+ var bytes = this.bytes;
+ var bytesPos = this.bytesPos;
- while (!this.eof && this.bufferLength < pos + length)
- this.readBlock();
+ while (codeSize < maxLen) {
+ var b;
+ if (typeof (b = bytes[bytesPos++]) == "undefined")
+ error("Bad encoding in flate stream");
+ codeBuf |= (b << codeSize);
+ codeSize += 8;
+ }
+ var code = codes[codeBuf & ((1 << maxLen) - 1)];
+ var codeLen = code >> 16;
+ var codeVal = code & 0xffff;
+ if (codeSize == 0|| codeSize < codeLen || codeLen == 0)
+ error("Bad encoding in flate stream");
+ this.codeBuf = (codeBuf >> codeLen);
+ this.codeSize = (codeSize - codeLen);
+ this.bytesPos = bytesPos;
+ return codeVal;
+ };
+ constructor.prototype.generateHuffmanTable = function(lengths) {
+ var n = lengths.length;
- var end = pos + length;
- var bufEnd = this.bufferLength;
+ // find max code length
+ var maxLen = 0;
+ for (var i = 0; i < n; ++i) {
+ if (lengths[i] > maxLen)
+ maxLen = lengths[i];
+ }
- if (end > bufEnd)
- end = bufEnd;
-
- this.pos = end;
- return this.buffer.subarray(pos, end)
- },
- lookChar: function() {
- var pos = this.pos;
- while (this.bufferLength <= pos) {
- if (this.eof)
- return;
- this.readBlock();
- }
- return String.fromCharCode(this.buffer[pos]);
- },
- getChar: function() {
- var ch = this.lookChar();
- // shouldnt matter what the position is if we get past the eof
- // so no need to check if ch is undefined
- this.pos++;
- return ch;
- },
- skip: function(n) {
- if (!n)
- n = 1;
- this.pos += n;
- },
- generateHuffmanTable: function(lengths) {
- var n = lengths.length;
-
- // find max code length
- var maxLen = 0;
- for (var i = 0; i < n; ++i) {
- if (lengths[i] > maxLen)
- maxLen = lengths[i];
- }
-
- // build the table
- var size = 1 << maxLen;
- var codes = Uint32Array(size);
- for (var len = 1, code = 0, skip = 2;
- len <= maxLen;
- ++len, code <<= 1, skip <<= 1) {
- for (var val = 0; val < n; ++val) {
- if (lengths[val] == len) {
- // bit-reverse the code
- var code2 = 0;
- var t = code;
- for (var i = 0; i < len; ++i) {
- code2 = (code2 << 1) | (t & 1);
- t >>= 1;
- }
-
- // fill the table entries
- for (var i = code2; i < size; i += skip)
- codes[i] = (len << 16) | val;
-
- ++code;
+ // build the table
+ var size = 1 << maxLen;
+ var codes = new Uint32Array(size);
+ for (var len = 1, code = 0, skip = 2;
+ len <= maxLen;
+ ++len, code <<= 1, skip <<= 1) {
+ for (var val = 0; val < n; ++val) {
+ if (lengths[val] == len) {
+ // bit-reverse the code
+ var code2 = 0;
+ var t = code;
+ for (var i = 0; i < len; ++i) {
+ code2 = (code2 << 1) | (t & 1);
+ t >>= 1;
}
+
+ // fill the table entries
+ for (var i = code2; i < size; i += skip)
+ codes[i] = (len << 16) | val;
+
+ ++code;
}
}
+ }
- return [codes, maxLen];
- },
- readBlock: function() {
- function repeat(stream, array, len, offset, what) {
- var repeat = stream.getBits(len) + offset;
- while (repeat-- > 0)
- array[i++] = what;
- }
+ return [codes, maxLen];
+ };
+ constructor.prototype.readBlock = function() {
+ function repeat(stream, array, len, offset, what) {
+ var repeat = stream.getBits(len) + offset;
+ while (repeat-- > 0)
+ array[i++] = what;
+ }
+ // read block header
+ var hdr = this.getBits(3);
+ if (hdr & 1)
+ this.eof = true;
+ hdr >>= 1;
+
+ if (hdr == 0) { // uncompressed block
var bytes = this.bytes;
var bytesPos = this.bytesPos;
-
- // read block header
- var hdr = this.getBits(3);
- if (hdr & 1)
- this.eof = true;
- hdr >>= 1;
-
var b;
- if (hdr == 0) { // uncompressed block
- if (typeof (b = bytes[bytesPos++]) == "undefined")
- error("Bad block header in flate stream");
- var blockLen = b;
- if (typeof (b = bytes[bytesPos++]) == "undefined")
- error("Bad block header in flate stream");
- blockLen |= (b << 8);
- if (typeof (b = bytes[bytesPos++]) == "undefined")
- error("Bad block header in flate stream");
- var check = b;
- if (typeof (b = bytes[bytesPos++]) == "undefined")
- error("Bad block header in flate stream");
- check |= (b << 8);
- if (check != (~this.blockLen & 0xffff))
- error("Bad uncompressed block length in flate stream");
- var bufferLength = this.bufferLength;
- var buffer = this.ensureBuffer(bufferLength + blockLen);
- this.bufferLength = bufferLength + blockLen;
- for (var n = bufferLength; n < blockLen; ++n) {
- if (typeof (b = bytes[bytesPos++]) == "undefined") {
- this.eof = true;
- break;
- }
- buffer[n] = b;
+
+ if (typeof (b = bytes[bytesPos++]) == "undefined")
+ error("Bad block header in flate stream");
+ var blockLen = b;
+ if (typeof (b = bytes[bytesPos++]) == "undefined")
+ error("Bad block header in flate stream");
+ blockLen |= (b << 8);
+ if (typeof (b = bytes[bytesPos++]) == "undefined")
+ error("Bad block header in flate stream");
+ var check = b;
+ if (typeof (b = bytes[bytesPos++]) == "undefined")
+ error("Bad block header in flate stream");
+ check |= (b << 8);
+ if (check != (~blockLen & 0xffff))
+ error("Bad uncompressed block length in flate stream");
+
+ this.codeBuf = 0;
+ this.codeSize = 0;
+
+ var bufferLength = this.bufferLength;
+ var buffer = this.ensureBuffer(bufferLength + blockLen);
+ var end = bufferLength + blockLen;
+ this.bufferLength = end;
+ for (var n = bufferLength; n < end; ++n) {
+ if (typeof (b = bytes[bytesPos++]) == "undefined") {
+ this.eof = true;
+ break;
}
+ buffer[n] = b;
+ }
+ this.bytesPos = bytesPos;
+ return;
+ }
+
+ var litCodeTable;
+ var distCodeTable;
+ if (hdr == 1) { // compressed block, fixed codes
+ litCodeTable = fixedLitCodeTab;
+ distCodeTable = fixedDistCodeTab;
+ } else if (hdr == 2) { // compressed block, dynamic codes
+ var numLitCodes = this.getBits(5) + 257;
+ var numDistCodes = this.getBits(5) + 1;
+ var numCodeLenCodes = this.getBits(4) + 4;
+
+ // build the code lengths code table
+ var codeLenCodeLengths = Array(codeLenCodeMap.length);
+ var i = 0;
+ while (i < numCodeLenCodes)
+ codeLenCodeLengths[codeLenCodeMap[i++]] = this.getBits(3);
+ var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths);
+
+ // build the literal and distance code tables
+ var len = 0;
+ var i = 0;
+ var codes = numLitCodes + numDistCodes;
+ var codeLengths = new Array(codes);
+ while (i < codes) {
+ var code = this.getCode(codeLenCodeTab);
+ if (code == 16) {
+ repeat(this, codeLengths, 2, 3, len);
+ } else if (code == 17) {
+ repeat(this, codeLengths, 3, 3, len = 0);
+ } else if (code == 18) {
+ repeat(this, codeLengths, 7, 11, len = 0);
+ } else {
+ codeLengths[i++] = len = code;
+ }
+ }
+
+ litCodeTable =
+ this.generateHuffmanTable(codeLengths.slice(0, numLitCodes));
+ distCodeTable =
+ this.generateHuffmanTable(codeLengths.slice(numLitCodes, codes));
+ } else {
+ error("Unknown block type in flate stream");
+ }
+
+ var buffer = this.buffer;
+ var limit = buffer ? buffer.length : 0;
+ var pos = this.bufferLength;
+ while (true) {
+ var code1 = this.getCode(litCodeTable);
+ if (code1 < 256) {
+ if (pos + 1 >= limit) {
+ buffer = this.ensureBuffer(pos + 1);
+ limit = buffer.length;
+ }
+ buffer[pos++] = code1;
+ continue;
+ }
+ if (code1 == 256) {
+ this.bufferLength = pos;
return;
}
-
- var litCodeTable;
- var distCodeTable;
- if (hdr == 1) { // compressed block, fixed codes
- litCodeTable = fixedLitCodeTab;
- distCodeTable = fixedDistCodeTab;
- } else if (hdr == 2) { // compressed block, dynamic codes
- var numLitCodes = this.getBits(5) + 257;
- var numDistCodes = this.getBits(5) + 1;
- var numCodeLenCodes = this.getBits(4) + 4;
-
- // build the code lengths code table
- var codeLenCodeLengths = Array(codeLenCodeMap.length);
- var i = 0;
- while (i < numCodeLenCodes)
- codeLenCodeLengths[codeLenCodeMap[i++]] = this.getBits(3);
- var codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths);
-
- // build the literal and distance code tables
- var len = 0;
- var i = 0;
- var codes = numLitCodes + numDistCodes;
- var codeLengths = new Array(codes);
- while (i < codes) {
- var code = this.getCode(codeLenCodeTab);
- if (code == 16) {
- repeat(this, codeLengths, 2, 3, len);
- } else if (code == 17) {
- repeat(this, codeLengths, 3, 3, len = 0);
- } else if (code == 18) {
- repeat(this, codeLengths, 7, 11, len = 0);
- } else {
- codeLengths[i++] = len = code;
- }
- }
-
- litCodeTable = this.generateHuffmanTable(codeLengths.slice(0, numLitCodes));
- distCodeTable = this.generateHuffmanTable(codeLengths.slice(numLitCodes, codes));
- } else {
- error("Unknown block type in flate stream");
- }
-
- var pos = this.bufferLength;
- while (true) {
- var code1 = this.getCode(litCodeTable);
- if (code1 == 256) {
- this.bufferLength = pos;
- return;
- }
- if (code1 < 256) {
- var buffer = this.ensureBuffer(pos + 1);
- buffer[pos++] = code1;
- } else {
- code1 -= 257;
- code1 = lengthDecode[code1];
- var code2 = code1 >> 16;
- if (code2 > 0)
- code2 = this.getBits(code2);
- var len = (code1 & 0xffff) + code2;
- code1 = this.getCode(distCodeTable);
- code1 = distDecode[code1];
- code2 = code1 >> 16;
- if (code2 > 0)
- code2 = this.getBits(code2);
- var dist = (code1 & 0xffff) + code2;
- var buffer = this.ensureBuffer(pos + len);
- for (var k = 0; k < len; ++k, ++pos)
- buffer[pos] = buffer[pos - dist];
- }
+ code1 -= 257;
+ code1 = lengthDecode[code1];
+ var code2 = code1 >> 16;
+ if (code2 > 0)
+ code2 = this.getBits(code2);
+ var len = (code1 & 0xffff) + code2;
+ code1 = this.getCode(distCodeTable);
+ code1 = distDecode[code1];
+ code2 = code1 >> 16;
+ if (code2 > 0)
+ code2 = this.getBits(code2);
+ var dist = (code1 & 0xffff) + code2;
+ if (pos + len >= limit) {
+ buffer = this.ensureBuffer(pos + len);
+ limit = buffer.length;
}
+ for (var k = 0; k < len; ++k, ++pos)
+ buffer[pos] = buffer[pos - dist];
}
};
return constructor;
})();
+
+var PredictorStream = (function() {
+ function constructor(stream, params) {
+ var predictor = this.predictor = params.get("Predictor") || 1;
+
+ if (predictor <= 1)
+ return stream; // no prediction
+ if (predictor !== 2 && (predictor < 10 || predictor > 15))
+ error("Unsupported predictor");
+
+ if (predictor === 2)
+ this.readBlock = this.readBlockTiff;
+ else
+ this.readBlock = this.readBlockPng;
+
+ this.stream = stream;
+ this.dict = stream.dict;
+ if (params.has("EarlyChange")) {
+ error("EarlyChange predictor parameter is not supported");
+ }
+ var colors = this.colors = params.get("Colors") || 1;
+ var bits = this.bits = params.get("BitsPerComponent") || 8;
+ var columns = this.columns = params.get("Columns") || 1;
+
+ var pixBytes = this.pixBytes = (colors * bits + 7) >> 3;
+ // add an extra pixByte to represent the pixel left of column 0
+ var rowBytes = this.rowBytes = (columns * colors * bits + 7) >> 3;
+
+ DecodeStream.call(this);
+ return this;
+ }
+
+ constructor.prototype = Object.create(DecodeStream.prototype);
+
+ constructor.prototype.readBlockTiff = function() {
+ var rowBytes = this.rowBytes;
+ var pixBytes = this.pixBytes;
+
+ var bufferLength = this.bufferLength;
+ var buffer = this.ensureBuffer(bufferLength + rowBytes);
+ var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes);
+
+ var bits = this.bits;
+ var colors = this.colors;
+
+ var rawBytes = this.stream.getBytes(rowBytes);
+
+ if (bits === 1) {
+ var inbuf = 0;
+ for (var i = 0; i < rowBytes; ++i) {
+ var c = rawBytes[i];
+ inBuf = (inBuf << 8) | c;
+ // bitwise addition is exclusive or
+ // first shift inBuf and then add
+ currentRow[i] = (c ^ (inBuf >> colors)) & 0xFF;
+ // truncate inBuf (assumes colors < 16)
+ inBuf &= 0xFFFF;
+ }
+ } else if (bits === 8) {
+ for (var i = 0; i < colors; ++i)
+ currentRow[i] = rawBytes[i];
+ for (; i < rowBytes; ++i)
+ currentRow[i] = currentRow[i - colors] + rawBytes[i];
+ } else {
+ var compArray = new Uint8Array(colors + 1);
+ var bitMask = (1 << bits) - 1;
+ var inbuf = 0, outbut = 0;
+ var inbits = 0, outbits = 0;
+ var j = 0, k = 0;
+ var columns = this.columns;
+ for (var i = 0; i < columns; ++i) {
+ for (var kk = 0; kk < colors; ++kk) {
+ if (inbits < bits) {
+ inbuf = (inbuf << 8) | (rawBytes[j++] & 0xFF);
+ inbits += 8;
+ }
+ compArray[kk] = (compArray[kk] +
+ (inbuf >> (inbits - bits))) & bitMask;
+ inbits -= bits;
+ outbuf = (outbuf << bits) | compArray[kk];
+ outbits += bits;
+ if (outbits >= 8) {
+ currentRow[k++] = (outbuf >> (outbits - 8)) & 0xFF;
+ outbits -= 8;
+ }
+ }
+ }
+ if (outbits > 0) {
+ currentRow[k++] = (outbuf << (8 - outbits)) +
+ (inbuf & ((1 << (8 - outbits)) - 1))
+ }
+ }
+ this.bufferLength += rowBytes;
+ };
+ constructor.prototype.readBlockPng = function() {
+ var rowBytes = this.rowBytes;
+ var pixBytes = this.pixBytes;
+
+ var predictor = this.stream.getByte();
+ var rawBytes = this.stream.getBytes(rowBytes);
+
+ var bufferLength = this.bufferLength;
+ var buffer = this.ensureBuffer(bufferLength + pixBytes);
+
+ var currentRow = buffer.subarray(bufferLength, bufferLength + rowBytes);
+ var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength);
+ if (prevRow.length == 0)
+ prevRow = currentRow;
+
+ switch (predictor) {
+ case 0:
+ break;
+ case 1:
+ for (var i = 0; i < pixBytes; ++i)
+ currentRow[i] = rawBytes[i];
+ for (; i < rowBytes; ++i)
+ currentRow[i] = (currentRow[i - pixBytes] + rawBytes[i]) & 0xFF;
+ break;
+ case 2:
+ for (var i = 0; i < rowBytes; ++i)
+ currentRow[i] = (prevRow[i] + rawBytes[i]) & 0xFF;
+ break;
+ case 3:
+ for (var i = 0; i < pixBytes; ++i)
+ currentRow[i] = (prevRow[i] >> 1) + rawBytes[i];
+ for (; i < rowBytes; ++i)
+ currentRow[i] = (((prevRow[i] + currentRow[i - pixBytes])
+ >> 1) + rawBytes[i]) & 0xFF;
+ break;
+ case 4:
+ // we need to save the up left pixels values. the simplest way
+ // is to create a new buffer
+ for (var i = 0; i < pixBytes; ++i)
+ currentRow[i] = rawBytes[i];
+ for (; i < rowBytes; ++i) {
+ var up = prevRow[i];
+ var upLeft = lastRow[i - pixBytes];
+ var left = currentRow[i - pixBytes];
+ var p = left + up - upLeft;
+
+ var pa = p - left;
+ if (pa < 0)
+ pa = -pa;
+ var pb = p - up;
+ if (pb < 0)
+ pb = -pb;
+ var pc = p - upLeft;
+ if (pc < 0)
+ pc = -pc;
+
+ var c = rawBytes[i];
+ if (pa <= pb && pa <= pc)
+ currentRow[i] = left + c;
+ else if (pb <= pc)
+ currentRow[i] = up + c;
+ else
+ currentRow[i] = upLeft + c;
+ break;
+ }
+ default:
+ error("Unsupported predictor");
+ break;
+ }
+ this.bufferLength += rowBytes;
+ };
+
+ return constructor;
+})();
+
// A JpegStream can't be read directly. We use the platform to render the underlying
// JPEG data for us.
var JpegStream = (function() {
@@ -558,196 +807,6 @@ var JpegStream = (function() {
return constructor;
})();
-
-var PredictorStream = (function() {
- function constructor(stream, params) {
- var predictor = this.predictor = params.get("Predictor") || 1;
-
- if (predictor <= 1)
- return stream; // no prediction
- if (predictor !== 2 && (predictor < 10 || predictor > 15))
- error("Unsupported predictor");
-
- if (predictor === 2)
- this.readRow = this.readRowTiff;
- else
- this.readRow = this.readRowPng;
-
- this.stream = stream;
- this.dict = stream.dict;
- if (params.has("EarlyChange")) {
- error("EarlyChange predictor parameter is not supported");
- }
- var colors = this.colors = params.get("Colors") || 1;
- var bits = this.bits = params.get("BitsPerComponent") || 8;
- var columns = this.columns = params.get("Columns") || 1;
-
- var pixBytes = this.pixBytes = (colors * bits + 7) >> 3;
- // add an extra pixByte to represent the pixel left of column 0
- var rowBytes = this.rowBytes = ((columns * colors * bits + 7) >> 3) + pixBytes;
-
- this.currentRow = new Uint8Array(rowBytes);
- this.bufferLength = rowBytes;
- this.pos = rowBytes;
- }
-
- constructor.prototype = {
- readRowTiff : function() {
- var currentRow = this.currentRow;
- var rowBytes = this.rowBytes;
- var pixBytes = this.pixBytes;
- var bits = this.bits;
- var colors = this.colors;
-
- var rawBytes = this.stream.getBytes(rowBytes - pixBytes);
-
- if (bits === 1) {
- var inbuf = 0;
- for (var i = pixBytes, j = 0; i < rowBytes; ++i, ++j) {
- var c = rawBytes[j];
- inBuf = (inBuf << 8) | c;
- // bitwise addition is exclusive or
- // first shift inBuf and then add
- currentRow[i] = (c ^ (inBuf >> colors)) & 0xFF;
- // truncate inBuf (assumes colors < 16)
- inBuf &= 0xFFFF;
- }
- } else if (bits === 8) {
- for (var i = pixBytes, j = 0; i < rowBytes; ++i, ++j)
- currentRow[i] = currentRow[i - colors] + rawBytes[j];
- } else {
- var compArray = new Uint8Array(colors + 1);
- var bitMask = (1 << bits) - 1;
- var inbuf = 0, outbut = 0;
- var inbits = 0, outbits = 0;
- var j = 0, k = pixBytes;
- var columns = this.columns;
- for (var i = 0; i < columns; ++i) {
- for (var kk = 0; kk < colors; ++kk) {
- if (inbits < bits) {
- inbuf = (inbuf << 8) | (rawBytes[j++] & 0xFF);
- inbits += 8;
- }
- compArray[kk] = (compArray[kk] +
- (inbuf >> (inbits - bits))) & bitMask;
- inbits -= bits;
- outbuf = (outbuf << bits) | compArray[kk];
- outbits += bits;
- if (outbits >= 8) {
- currentRow[k++] = (outbuf >> (outbits - 8)) & 0xFF;
- outbits -= 8;
- }
- }
- }
- if (outbits > 0) {
- currentRow[k++] = (outbuf << (8 - outbits)) +
- (inbuf & ((1 << (8 - outbits)) - 1))
- }
- }
- this.pos = pixBytes;
- },
- readRowPng : function() {
- var currentRow = this.currentRow;
-
- var rowBytes = this.rowBytes;
- var pixBytes = this.pixBytes;
-
- var predictor = this.stream.getByte();
- var rawBytes = this.stream.getBytes(rowBytes - pixBytes);
-
- switch (predictor) {
- case 0:
- break;
- case 1:
- for (var i = pixBytes, j = 0; i < rowBytes; ++i, ++j)
- currentRow[i] = (currentRow[i - pixBytes] + rawBytes[j]) & 0xFF;
- break;
- case 2:
- for (var i = pixBytes, j = 0; i < rowBytes; ++i, ++j)
- currentRow[i] = (currentRow[i] + rawBytes[j]) & 0xFF;
- break;
- case 3:
- for (var i = pixBytes, j = 0; i < rowBytes; ++i, ++j)
- currentRow[i] = (((currentRow[i] + currentRow[i - pixBytes])
- >> 1) + rawBytes[j]) & 0xFF;
- break;
- case 4:
- // we need to save the up left pixels values. the simplest way
- // is to create a new buffer
- var lastRow = currentRow;
- var currentRow = new Uint8Array(rowBytes);
- for (var i = pixBytes, j = 0; i < rowBytes; ++i, ++j) {
- var up = lastRow[i];
- var upLeft = lastRow[i - pixBytes];
- var left = currentRow[i - pixBytes];
- var p = left + up - upLeft;
-
- var pa = p - left;
- if (pa < 0)
- pa = -pa;
- var pb = p - up;
- if (pb < 0)
- pb = -pb;
- var pc = p - upLeft;
- if (pc < 0)
- pc = -pc;
-
- var c = rawBytes[j];
- if (pa <= pb && pa <= pc)
- currentRow[i] = left + c;
- else if (pb <= pc)
- currentRow[i] = up + c;
- else
- currentRow[i] = upLeft + c;
- break;
- this.currentRow = currentRow;
- }
- default:
- error("Unsupported predictor");
- break;
- }
- this.pos = pixBytes;
- },
- getByte : function() {
- if (this.pos >= this.bufferLength)
- this.readRow();
- return this.currentRow[this.pos++];
- },
- getBytes : function(n) {
- var i, bytes;
- bytes = new Uint8Array(n);
- for (i = 0; i < n; ++i) {
- if (this.pos >= this.bufferLength)
- this.readRow();
- bytes[i] = this.currentRow[this.pos++];
- }
- return bytes;
- },
- getChar : function() {
- return String.formCharCode(this.getByte());
- },
- lookChar : function() {
- if (this.pos >= this.bufferLength)
- this.readRow();
- return String.formCharCode(this.currentRow[this.pos]);
- },
- skip : function(n) {
- var i;
- if (!n) {
- n = 1;
- }
- while (n > this.bufferLength - this.pos) {
- n -= this.bufferLength - this.pos;
- this.readRow();
- if (this.bufferLength === 0) break;
- }
- this.pos += n;
- }
- };
-
- return constructor;
-})();
-
var DecryptStream = (function() {
function constructor(str, decrypt) {
this.str = str;
@@ -781,6 +840,74 @@ var DecryptStream = (function() {
return constructor;
})();
+var Ascii85Stream = (function() {
+ function constructor(str) {
+ this.str = str;
+ this.dict = str.dict;
+ this.input = new Uint8Array(5);
+
+ DecodeStream.call(this);
+ }
+
+ constructor.prototype = Object.create(DecodeStream.prototype);
+ constructor.prototype.readBlock = function() {
+ var tildaCode = "~".charCodeAt(0);
+ var zCode = "z".charCodeAt(0);
+ var str = this.str;
+
+ var c = str.getByte();
+ while (Lexer.isSpace(String.fromCharCode(c)))
+ c = str.getByte();
+
+ if (!c || c === tildaCode) {
+ this.eof = true;
+ return;
+ }
+
+ var bufferLength = this.bufferLength;
+
+ // special code for z
+ if (c == zCode) {
+ var buffer = this.ensureBuffer(bufferLength + 4);
+ for (var i = 0; i < 4; ++i)
+ buffer[bufferLength + i] = 0;
+ this.bufferLength += 4;
+ } else {
+ var input = this.input;
+ input[0] = c;
+ for (var i = 1; i < 5; ++i){
+ c = str.getByte();
+ while (Lexer.isSpace(String.fromCharCode(c)))
+ c = str.getByte();
+
+ input[i] = c;
+
+ if (!c || c == tildaCode)
+ break;
+ }
+ var buffer = this.ensureBuffer(bufferLength + i - 1);
+ this.bufferLength += i - 1;
+
+ // partial ending;
+ if (i < 5) {
+ for (; i < 5; ++i)
+ input[i] = 0x21 + 84;
+ this.eof = true;
+ }
+ var t = 0;
+ for (var i = 0; i < 5; ++i)
+ t = t * 85 + (input[i] - 0x21);
+
+ for (var i = 3; i >= 0; --i){
+ buffer[bufferLength + i] = t & 0xFF;
+ t >>= 8;
+ }
+ }
+ };
+
+ return constructor;
+})();
+
var Name = (function() {
function constructor(name) {
this.name = name;
@@ -810,7 +937,9 @@ var Dict = (function() {
constructor.prototype = {
get: function(key) {
- return this.map[key];
+ if (key in this.map)
+ return this.map[key];
+ return null;
},
get2: function(key1, key2) {
return this.get(key1) || this.get(key2);
@@ -943,10 +1072,10 @@ var Lexer = (function() {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // fx
];
- const MIN_INT = (1<<31) | 0;
- const MAX_INT = (MIN_INT - 1) | 0;
- const MIN_UINT = 0;
- const MAX_UINT = ((1<<30) * 4) - 1;
+ var MIN_INT = (1<<31) | 0;
+ var MAX_INT = (MIN_INT - 1) | 0;
+ var MIN_UINT = 0;
+ var MAX_UINT = ((1<<30) * 4) - 1;
function ToHexDigit(ch) {
if (ch >= "0" && ch <= "9")
@@ -1352,8 +1481,8 @@ var Parser = (function() {
if (IsArray(filter)) {
var filterArray = filter;
var paramsArray = params;
- for (var i = 0, ii = filter.length; i < ii; ++i) {
- filter = filter[i];
+ for (var i = 0, ii = filterArray.length; i < ii; ++i) {
+ filter = filterArray[i];
if (!IsName(filter))
error("Bad filter name");
else {
@@ -1375,6 +1504,11 @@ var Parser = (function() {
} else if (name == "DCTDecode") {
var bytes = stream.getBytes(length);
return new JpegStream(bytes, stream.dict);
+ } else if (name == "ASCII85Decode") {
+ return new Ascii85Stream(stream);
+ } else if (name == "CCITTFaxDecode") {
+ TODO("implement fax stream");
+ return new FakeStream(stream);
} else {
error("filter '" + name + "' not supported yet");
}
@@ -1483,7 +1617,7 @@ var XRef = (function() {
}
constructor.prototype = {
- readXRefTable: function(parser) {
+ readXRefTable: function readXRefTable(parser) {
var obj;
while (true) {
if (IsCmd(obj = parser.getObj(), "trailer"))
@@ -1554,7 +1688,7 @@ var XRef = (function() {
return dict;
},
- readXRefStream: function(stream) {
+ readXRefStream: function readXRefStream(stream) {
var streamParameters = stream.parameters;
var length = streamParameters.get("Length");
var byteWidths = streamParameters.get("W");
@@ -1606,7 +1740,7 @@ var XRef = (function() {
this.readXRef(prev);
return streamParameters;
},
- readXRef: function(startXRef) {
+ readXRef: function readXref(startXRef) {
var stream = this.stream;
stream.pos = startXRef;
var parser = new Parser(new Lexer(stream), true);
@@ -1624,6 +1758,7 @@ var XRef = (function() {
return this.readXRefStream(obj);
}
error("Invalid XRef");
+ return null;
},
getEntry: function(i) {
var e = this.entries[i];
@@ -1964,7 +2099,7 @@ var PDFDoc = (function() {
return constructor;
})();
-const IDENTITY_MATRIX = [ 1, 0, 0, 1, 0, 0 ];
+var IDENTITY_MATRIX = [ 1, 0, 0, 1, 0, 0 ];
//