diff --git a/LICENSE b/LICENSE index 3d42ba007..81658476c 100644 --- a/LICENSE +++ b/LICENSE @@ -6,6 +6,7 @@ Shaon Barman Vivien Nicolas <21@vingtetun.org> Justin D'Arcangelo + Yury Delendik Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), diff --git a/fonts.js b/fonts.js index 7978cd60d..0c8725fb4 100644 --- a/fonts.js +++ b/fonts.js @@ -1,6 +1,8 @@ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ +"use strict"; + /** * Maximum file size of the font. */ @@ -203,7 +205,7 @@ Font.prototype = { } } ctx.font = "bold italic 20px " + fontName + ", Symbol, Arial"; - var textWidth = ctx.mozMeasureText(testString); + var textWidth = ctx.measureText(testString).width; if (debug) ctx.fillText(testString, 20, 20); @@ -218,7 +220,7 @@ Font.prototype = { window.clearInterval(interval); Fonts[fontName].loading = false; warn("Is " + fontName + " for charset: " + charset + " loaded?"); - } else if (textWidth != ctx.mozMeasureText(testString)) { + } else if (textWidth != ctx.measureText(testString).width) { window.clearInterval(interval); Fonts[fontName].loading = false; } @@ -1042,7 +1044,8 @@ var Type1Parser = function() { this.extractFontProgram = function t1_extractFontProgram(aStream) { var eexecString = decrypt(aStream, kEexecEncryptionKey, 4); var subrs = [], glyphs = []; - var inSubrs = inGlyphs = false; + var inGlyphs = false; + var inSubrs = false; var glyph = ""; var token = ""; diff --git a/glyphlist.js b/glyphlist.js index 1a0190133..72a90431f 100644 --- a/glyphlist.js +++ b/glyphlist.js @@ -1,3 +1,8 @@ +/* -*- 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 GlyphsUnicode = { A: 0x0041, AE: 0x00C6, diff --git a/images/combobox.png b/images/combobox.png new file mode 100644 index 000000000..f1527d6a2 Binary files /dev/null and b/images/combobox.png differ diff --git a/images/source/ComboBox.psd.zip b/images/source/ComboBox.psd.zip new file mode 100644 index 000000000..232604c75 Binary files /dev/null and b/images/source/ComboBox.psd.zip differ diff --git a/multi-page-viewer.css b/multi-page-viewer.css index 53a28f129..c96567465 100644 --- a/multi-page-viewer.css +++ b/multi-page-viewer.css @@ -113,6 +113,67 @@ span { background: url('images/buttons.png') no-repeat -28px 0px; } +#scaleComboBoxInput { + background: url('images/combobox.png') no-repeat 0px -23px; + display: inline-block; + float: left; + margin: 0px; + width: 35px; + height: 23px; +} + +#scaleComboBoxInput input { + background: none; + border: 0px; + margin: 3px 2px 0px; + width: 31px; +} + +#scaleComboBoxButton { + background: url('images/combobox.png') no-repeat -41px -23px; + cursor: pointer; + display: inline-block; + float: left; + margin: 0px; + width: 21px; + height: 23px; +} + +#scaleComboBoxButton.down { + background: url('images/combobox.png') no-repeat -41px -46px; +} + +#scaleComboBoxButton.disabled { + background: url('images/combobox.png') no-repeat -41px 0px; +} + +#scaleComboBoxList { + background-color: #fff; + border: 1px solid #666; + clear: both; + position: relative; + display: none; + top: -20px; + width: 48px; +} + +#scaleComboBoxList > ul { + list-style: none; + padding: 0px; + margin: 0px; +} + +#scaleComboBoxList > ul > li { + display: inline-block; + cursor: pointer; + width: 100%; +} + +#scaleComboBoxList > ul > li:hover { + background-color: #09f; + color: #fff; +} + #pageNumber, #scale { text-align: right; } diff --git a/multi-page-viewer.html b/multi-page-viewer.html index aec84a42f..692cfb1c4 100644 --- a/multi-page-viewer.html +++ b/multi-page-viewer.html @@ -21,9 +21,18 @@ Page Number - - % + Zoom +
+
    +
  • 50%
  • +
  • 75%
  • +
  • 100%
  • +
  • 125%
  • +
  • 150%
  • +
  • 200%
  • +
+
diff --git a/multi-page-viewer.js b/multi-page-viewer.js index cc4a286ff..6cb46a08a 100644 --- a/multi-page-viewer.js +++ b/multi-page-viewer.js @@ -1,14 +1,17 @@ /* -*- 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, - pageNumberInput: null, previousPageButton: null, nextPageButton: null, + pageNumberInput: null, + scaleInput: null, willJumpToPage: false, @@ -156,6 +159,8 @@ var PDFViewer = { PDFViewer.drawPage(1); } } + + PDFViewer.scaleInput.value = Math.floor(PDFViewer.scale * 100) + '%'; }, goToPage: function(num) { @@ -315,13 +320,40 @@ window.onload = function() { this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; }; - var scaleInput = document.getElementById('scale'); - scaleInput.onchange = function(evt) { - PDFViewer.changeScale(this.value); + PDFViewer.scaleInput = document.getElementById('scale'); + PDFViewer.scaleInput.buttonElement = document.getElementById('scaleComboBoxButton'); + PDFViewer.scaleInput.buttonElement.listElement = document.getElementById('scaleComboBoxList'); + PDFViewer.scaleInput.onchange = function(evt) { + PDFViewer.changeScale(parseInt(this.value)); }; + PDFViewer.scaleInput.buttonElement.onclick = function(evt) { + this.listElement.style.display = (this.listElement.style.display === 'block') ? 'none' : 'block'; + }; + PDFViewer.scaleInput.buttonElement.onmousedown = function(evt) { + if (this.className.indexOf('disabled') === -1) { + this.className = 'down'; + } + }; + PDFViewer.scaleInput.buttonElement.onmouseup = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + PDFViewer.scaleInput.buttonElement.onmouseout = function(evt) { + this.className = (this.className.indexOf('disabled') !== -1) ? 'disabled' : ''; + }; + + var listItems = PDFViewer.scaleInput.buttonElement.listElement.getElementsByTagName('LI'); + + for (var i = 0; i < listItems.length; i++) { + var listItem = listItems[i]; + listItem.onclick = function(evt) { + PDFViewer.changeScale(parseInt(this.innerHTML)); + PDFViewer.scaleInput.buttonElement.listElement.style.display = 'none'; + }; + } + PDFViewer.pageNumber = parseInt(PDFViewer.queryParams.page) || PDFViewer.pageNumber; - PDFViewer.scale = parseInt(scaleInput.value) / 100 || 1.0; + PDFViewer.scale = parseInt(PDFViewer.scaleInput.value) / 100 || 1.0; PDFViewer.open(PDFViewer.queryParams.file || PDFViewer.url); window.onscroll = function(evt) { diff --git a/pdf.js b/pdf.js index 8f61646d1..dee64090f 100644 --- a/pdf.js +++ b/pdf.js @@ -1,6 +1,8 @@ /* -*- 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 ERRORS = 0, WARNINGS = 1, TODOS = 5; var verbosity = WARNINGS; @@ -389,6 +391,12 @@ var FlateStream = (function() { return [codes, maxLen]; }, readBlock: function() { + function repeat(stream, array, len, offset, what) { + var repeat = stream.getBits(len) + offset; + while (repeat-- > 0) + array[i++] = what; + } + var stream = this.stream; // read block header @@ -449,11 +457,6 @@ var FlateStream = (function() { var codes = numLitCodes + numDistCodes; var codeLengths = new Array(codes); while (i < codes) { - function repeat(stream, array, len, offset, what) { - var repeat = stream.getBits(len) + offset; - while (repeat-- > 0) - array[i++] = what; - } var code = this.getCode(codeLenCodeTab); if (code == 16) { repeat(this, codeLengths, 2, 3, len); @@ -506,6 +509,94 @@ var FlateStream = (function() { return constructor; })(); +var PredictorStream = (function() { + function constructor(stream, params) { + this.stream = stream; + this.predictor = params.get("Predictor") || 1; + if (this.predictor <= 1) { + return stream; // no prediction + } + if (params.has("EarlyChange")) { + error("EarlyChange predictor parameter is not supported"); + } + this.colors = params.get("Colors") || 1; + this.bitsPerComponent = params.get("BitsPerComponent") || 8; + this.columns = params.get("Columns") || 1; + if (this.colors !== 1 || this.bitsPerComponent !== 8) { + error("Multi-color and multi-byte predictors are not supported"); + } + if (this.predictor < 10 || this.predictor > 15) { + error("Unsupported predictor"); + } + this.currentRow = new Uint8Array(this.columns); + this.pos = 0; + this.bufferLength = 0; + } + + constructor.prototype = { + readRow : function() { + var lastRow = this.currentRow; + var predictor = this.stream.getByte(); + var currentRow = this.stream.getBytes(this.columns), i; + switch (predictor) { + default: + error("Unsupported predictor"); + break; + case 0: + break; + case 2: + for (i = 0; i < currentRow.length; ++i) { + currentRow[i] = (lastRow[i] + currentRow[i]) & 0xFF; + } + break; + } + this.pos = 0; + this.bufferLength = currentRow.length; + this.currentRow = currentRow; + }, + 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, fileKey, encAlgorithm, keyLength) { // TODO @@ -725,6 +816,7 @@ var Lexer = (function() { var done = false; var str = ""; var stream = this.stream; + var ch; do { switch (ch = stream.getChar()) { case undefined: @@ -1085,7 +1177,9 @@ var Parser = (function() { this.encAlgorithm, this.keyLength); } - return this.filter(stream, dict); + stream = this.filter(stream, dict); + stream.parameters = dict; + return stream; }, filter: function(stream, dict) { var filter = dict.get2("Filter", "F"); @@ -1110,8 +1204,9 @@ var Parser = (function() { }, makeFilter: function(stream, name, params) { if (name == "FlateDecode" || name == "Fl") { - if (params) - error("params not supported yet for FlateDecode"); + if (params) { + return new PredictorStream(new FlateStream(stream), params); + } return new FlateStream(stream); } else { error("filter '" + name + "' not supported yet"); @@ -1204,10 +1299,10 @@ var XRef = (function() { this.stream = stream; this.entries = []; this.xrefstms = {}; - this.readXRef(startXRef); + var trailerDict = this.readXRef(startXRef); // get the root dictionary (catalog) object - if (!IsRef(this.root = this.trailerDict.get("Root"))) + if (!IsRef(this.root = trailerDict.get("Root"))) error("Invalid root reference"); // prepare the XRef cache @@ -1262,18 +1357,18 @@ var XRef = (function() { error("Invalid XRef table"); // get the 'Prev' pointer - var more = false; + var prev; obj = dict.get("Prev"); if (IsInt(obj)) { - this.prev = obj; - more = true; + prev = obj; } else if (IsRef(obj)) { // certain buggy PDF generators generate "/Prev NNN 0 R" instead // of "/Prev NNN" - this.prev = obj.num; - more = true; + prev = obj.num; + } + if (prev) { + this.readXRef(prev); } - this.trailerDict = dict; // check for 'XRefStm' key if (IsInt(obj = dict.get("XRefStm"))) { @@ -1283,11 +1378,64 @@ var XRef = (function() { this.xrefstms[pos] = 1; // avoid infinite recursion this.readXRef(pos); } - - return more; + return dict; }, - readXRefStream: function(parser) { - error("Invalid XRef stream"); + readXRefStream: function(stream) { + var streamParameters = stream.parameters; + var length = streamParameters.get("Length"); + var byteWidths = streamParameters.get("W"); + var range = streamParameters.get("Index"); + if (!range) { + range = [0, streamParameters.get("Size")]; + } + var i, j; + while (range.length > 0) { + var first = range[0], n = range[1]; + if (!IsInt(first) || !IsInt(n)) { + error("Invalid XRef range fields"); + } + var typeFieldWidth = byteWidths[0], offsetFieldWidth = byteWidths[1], generationFieldWidth = byteWidths[2]; + if (!IsInt(typeFieldWidth) || !IsInt(offsetFieldWidth) || !IsInt(generationFieldWidth)) { + error("Invalid XRef entry fields length"); + } + for (i = 0; i < n; ++i) { + var type = 0, offset = 0, generation = 0; + for (j = 0; j < typeFieldWidth; ++j) { + type = (type << 8) | stream.getByte(); + } + for (j = 0; j < offsetFieldWidth; ++j) { + offset = (offset << 8) | stream.getByte(); + } + for (j = 0; j < generationFieldWidth; ++j) { + generation = (generation << 8) | stream.getByte(); + } + var entry = { offset: offset, gen: generation }; + if (typeFieldWidth > 0) { + switch (type) { + case 0: + entry.free = true; + break; + case 1: + entry.uncompressed = true; + break; + case 2: + break; + default: + error("Invalid XRef entry type"); + break; + } + } + if (!this.entries[first + i]) { + this.entries[first + i] = entry; + } + } + range.splice(0, 2); + } + var prev = streamParameters.get("Prev"); + if (IsInt(prev)) { + this.readXRef(prev); + } + return streamParameters; }, readXRef: function(startXRef) { var stream = this.stream; @@ -1429,7 +1577,7 @@ var Catalog = (function() { return shadow(this, "toplevelPagesDict", obj); }, get numPages() { - obj = this.toplevelPagesDict.get("Count"); + var obj = this.toplevelPagesDict.get("Count"); assertWellFormed(IsInt(obj), "page count in top level pages object is not an integer"); // shadow the prototype getter @@ -1571,7 +1719,7 @@ var PDFDoc = (function() { }, getPage: function(n) { var linearization = this.linearization; - assert(!linearization, "linearized page access not implemented"); + // assert(!linearization, "linearized page access not implemented"); return this.catalog.getPage(n); } }; @@ -2383,7 +2531,7 @@ var CanvasGraphics = (function() { error("No support for array of functions"); else if (!IsPDFFunction(fnObj)) error("Invalid function"); - fn = new PDFFunction(this.xref, fnObj); + var fn = new PDFFunction(this.xref, fnObj); var gradient = this.ctx.createLinearGradient(x0, y0, x1, y1); var step = (t1 - t0) / 10; diff --git a/utils/cffStandardStrings.js b/utils/cffStandardStrings.js index 8977cd8f2..1b328a2da 100644 --- a/utils/cffStandardStrings.js +++ b/utils/cffStandardStrings.js @@ -1,5 +1,10 @@ +/* -*- 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 CFFStrings = [ - ".notdef", + ".notdef", "space", "exclam", "quotedbl", @@ -490,7 +495,7 @@ var CFFDictDataMap = { }, "10": { name: "StdHW" - }, + }, "11": { name: "StdVW" }, @@ -597,7 +602,7 @@ var CFFDictDataMap = { }, "18": { name: "Private", - operand: "number number" + operand: "number number" }, "19": { name: "Subrs" diff --git a/utils/fonts_utils.js b/utils/fonts_utils.js index 086648fe2..79ecf257f 100644 --- a/utils/fonts_utils.js +++ b/utils/fonts_utils.js @@ -1,3 +1,8 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- / +/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ + +"use strict"; + /** * The Type2 reader code below is only used for debugging purpose since Type2 * is only a CharString format and is never used directly as a Font file. diff --git a/viewer.js b/viewer.js index 59d8167a2..675f2fb87 100644 --- a/viewer.js +++ b/viewer.js @@ -1,12 +1,14 @@ /* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- / /* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ -var pdfDocument, canvas, pageDisplay, pageNum, pageInterval; +"use strict"; + +var pdfDocument, canvas, numPages, pageDisplay, pageNum, pageInterval; function load(userInput) { canvas = document.getElementById("canvas"); canvas.mozOpaque = true; pageNum = parseInt(queryParams().page) || 1; - fileName = userInput; + var fileName = userInput; if (!userInput) { fileName = queryParams().file || "compressed.tracemonkey-pldi-09.pdf"; } @@ -26,7 +28,7 @@ function queryParams() { function open(url) { document.title = url; - req = new XMLHttpRequest(); + var req = new XMLHttpRequest(); req.open("GET", url); req.mozResponseType = req.responseType = "arraybuffer"; req.expected = (document.URL.indexOf("file:") == 0) ? 0 : 200;