]> git.parisson.com Git - pdf.js.git/commitdiff
make pdfjs
authorArtur Adib <arturadib@gmail.com>
Tue, 25 Oct 2011 01:13:12 +0000 (18:13 -0700)
committerArtur Adib <arturadib@gmail.com>
Tue, 25 Oct 2011 01:13:12 +0000 (18:13 -0700)
.gitignore
Makefile
src/core.js [new file with mode: 0644]
src/pdf.js
watch.py [new file with mode: 0644]
web/viewer.html

index 9e2d0f2110e7029e09f4e7c718e8085869828091..bb045de3ea167c9901da889ddb4f4319e3a9a52b 100644 (file)
@@ -3,3 +3,5 @@ pdf.pdf
 intelisa.pdf
 openweb_tm-PRINT.pdf
 local.mk
+build/
+
index 959681f3a1b368f889c08fbd9a7b96a464c730e1..9e7c36a8b9e715dac0cd5d5cf3953d52eee2ca9e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -12,13 +12,26 @@ EXTENSION_NAME := pdf.js.xpi
 # JS files needed for pdf.js.
 # This list doesn't account for the 'worker' directory.
 PDF_JS_FILES = \
-       pdf.js \
-       crypto.js \
-       fonts.js \
-       metrics.js \
-       charsets.js \
-       glyphlist.js \
-       cidmaps.js \
+  core.js \
+  util.js \
+  canvas.js \
+  obj.js \
+  function.js \
+  charsets.js \
+  cidmaps.js \
+  colorspace.js \
+  core.js \
+  crypto.js \
+  evaluator.js \
+  fonts.js \
+  glyphlist.js \
+  image.js \
+  metrics.js \
+  parser.js \
+  pattern.js \
+  stream.js \
+  worker/message_handler.js \
+  worker/processor_handler.js \
        $(NULL)
 
 PDF_WORKER_FILES = \
@@ -28,8 +41,24 @@ PDF_WORKER_FILES = \
        worker/processor_handler.js \
        $(NULL) 
 
-# not sure what to do for all yet
-all: help
+#
+# Bundle pdf.js
+#
+pdfjs:
+       @echo "Bundling source files..."
+       @mkdir -p build
+       @cd src; \
+       cat $(PDF_JS_FILES) > all_files.tmp; \
+       sed -E '/INSERT_POINT/ r all_files.tmp' pdf.js > ../build/pdf.js; \
+       rm -f all_files.tmp; \
+       cd ..
+
+#
+# Watch for file changes, regenerate pdf.js if change found
+#
+watch:
+       @echo "Watching for file changes in src/"
+       @python watch.py src/*.js - 'make pdfjs'
 
 # make server
 #
diff --git a/src/core.js b/src/core.js
new file mode 100644 (file)
index 0000000..05cd8cd
--- /dev/null
@@ -0,0 +1,604 @@
+/* -*- 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 ERRORS = 0, WARNINGS = 1, TODOS = 5;
+var verbosity = WARNINGS;
+
+// Set this to true if you want to use workers.
+var useWorker = false;
+
+//
+// getPdf()
+// Convenience function to perform binary Ajax GET
+// Usage: getPdf('http://...', callback)
+//        getPdf({
+//                 url:String ,
+//                 [,progress:Function, error:Function]
+//               },
+//               callback)
+//
+function getPdf(arg, callback) {
+  var params = arg;
+  if (typeof arg === 'string')
+    params = { url: arg };
+
+  var xhr = new XMLHttpRequest();
+  xhr.open('GET', params.url);
+  xhr.mozResponseType = xhr.responseType = 'arraybuffer';
+  xhr.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
+
+  if ('progress' in params)
+    xhr.onprogress = params.progress || undefined;
+
+  if ('error' in params)
+    xhr.onerror = params.error || undefined;
+
+  xhr.onreadystatechange = function getPdfOnreadystatechange() {
+    if (xhr.readyState === 4 && xhr.status === xhr.expected) {
+      var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse ||
+                  xhr.responseArrayBuffer || xhr.response);
+      callback(data);
+    }
+  };
+  xhr.send(null);
+}
+
+var Page = (function pagePage() {
+  function constructor(xref, pageNumber, pageDict, ref) {
+    this.pageNumber = pageNumber;
+    this.pageDict = pageDict;
+    this.stats = {
+      create: Date.now(),
+      compile: 0.0,
+      fonts: 0.0,
+      images: 0.0,
+      render: 0.0
+    };
+    this.xref = xref;
+    this.ref = ref;
+  }
+
+  constructor.prototype = {
+    getPageProp: function pageGetPageProp(key) {
+      return this.xref.fetchIfRef(this.pageDict.get(key));
+    },
+    inheritPageProp: function pageInheritPageProp(key) {
+      var dict = this.pageDict;
+      var obj = dict.get(key);
+      while (obj === undefined) {
+        dict = this.xref.fetchIfRef(dict.get('Parent'));
+        if (!dict)
+          break;
+        obj = dict.get(key);
+      }
+      return obj;
+    },
+    get content() {
+      return shadow(this, 'content', this.getPageProp('Contents'));
+    },
+    get resources() {
+      return shadow(this, 'resources', this.inheritPageProp('Resources'));
+    },
+    get mediaBox() {
+      var obj = this.inheritPageProp('MediaBox');
+      // Reset invalid media box to letter size.
+      if (!isArray(obj) || obj.length !== 4)
+        obj = [0, 0, 612, 792];
+      return shadow(this, 'mediaBox', obj);
+    },
+    get view() {
+      var obj = this.inheritPageProp('CropBox');
+      var view = {
+        x: 0,
+        y: 0,
+        width: this.width,
+        height: this.height
+      };
+      if (isArray(obj) && obj.length == 4) {
+        var tl = this.rotatePoint(obj[0], obj[1]);
+        var br = this.rotatePoint(obj[2], obj[3]);
+        view.x = Math.min(tl.x, br.x);
+        view.y = Math.min(tl.y, br.y);
+        view.width = Math.abs(tl.x - br.x);
+        view.height = Math.abs(tl.y - br.y);
+      }
+
+      return shadow(this, 'cropBox', view);
+    },
+    get annotations() {
+      return shadow(this, 'annotations', this.inheritPageProp('Annots'));
+    },
+    get width() {
+      var mediaBox = this.mediaBox;
+      var rotate = this.rotate;
+      var width;
+      if (rotate == 0 || rotate == 180) {
+        width = (mediaBox[2] - mediaBox[0]);
+      } else {
+        width = (mediaBox[3] - mediaBox[1]);
+      }
+      return shadow(this, 'width', width);
+    },
+    get height() {
+      var mediaBox = this.mediaBox;
+      var rotate = this.rotate;
+      var height;
+      if (rotate == 0 || rotate == 180) {
+        height = (mediaBox[3] - mediaBox[1]);
+      } else {
+        height = (mediaBox[2] - mediaBox[0]);
+      }
+      return shadow(this, 'height', height);
+    },
+    get rotate() {
+      var rotate = this.inheritPageProp('Rotate') || 0;
+      // Normalize rotation so it's a multiple of 90 and between 0 and 270
+      if (rotate % 90 != 0) {
+        rotate = 0;
+      } else if (rotate >= 360) {
+        rotate = rotate % 360;
+      } else if (rotate < 0) {
+        // The spec doesn't cover negatives, assume its counterclockwise
+        // rotation. The following is the other implementation of modulo.
+        rotate = ((rotate % 360) + 360) % 360;
+      }
+      return shadow(this, 'rotate', rotate);
+    },
+
+    startRenderingFromIRQueue: function startRenderingFromIRQueue(
+                                                IRQueue, fonts) {
+      var self = this;
+      this.IRQueue = IRQueue;
+      var gfx = new CanvasGraphics(this.ctx, this.objs);
+      var startTime = Date.now();
+
+      var displayContinuation = function pageDisplayContinuation() {
+        // Always defer call to display() to work around bug in
+        // Firefox error reporting from XHR callbacks.
+        setTimeout(function pageSetTimeout() {
+          try {
+            self.display(gfx, self.callback);
+          } catch (e) {
+            if (self.callback) self.callback(e.toString());
+            throw e;
+          }
+        });
+      };
+
+      this.ensureFonts(fonts, function() {
+        displayContinuation();
+      });
+    },
+
+    getIRQueue: function(handler, dependency) {
+      if (this.IRQueue) {
+        // content was compiled
+        return this.IRQueue;
+      }
+
+      var xref = this.xref;
+      var content = xref.fetchIfRef(this.content);
+      var resources = xref.fetchIfRef(this.resources);
+      if (isArray(content)) {
+        // fetching items
+        var i, n = content.length;
+        for (i = 0; i < n; ++i)
+          content[i] = xref.fetchIfRef(content[i]);
+        content = new StreamsSequenceStream(content);
+      }
+
+      var pe = this.pe = new PartialEvaluator(
+                                xref, handler, 'p' + this.pageNumber + '_');
+      var IRQueue = {};
+      return this.IRQueue = pe.getIRQueue(
+                                content, resources, IRQueue, dependency);
+    },
+
+    ensureFonts: function(fonts, callback) {
+      // Convert the font names to the corresponding font obj.
+      for (var i = 0; i < fonts.length; i++) {
+        fonts[i] = this.objs.objs[fonts[i]].data;
+      }
+
+      // Load all the fonts
+      var fontObjs = FontLoader.bind(
+        fonts,
+        function(fontObjs) {
+          this.stats.fonts = Date.now();
+
+          callback.call(this);
+        }.bind(this),
+        this.objs
+      );
+    },
+
+    display: function(gfx, callback) {
+      var xref = this.xref;
+      var resources = xref.fetchIfRef(this.resources);
+      var mediaBox = xref.fetchIfRef(this.mediaBox);
+      assertWellFormed(isDict(resources), 'invalid page resources');
+
+      gfx.xref = xref;
+      gfx.res = resources;
+      gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1],
+            width: this.width,
+            height: this.height,
+            rotate: this.rotate });
+
+      var startIdx = 0;
+      var length = this.IRQueue.fnArray.length;
+      var IRQueue = this.IRQueue;
+
+      var self = this;
+      var startTime = Date.now();
+      function next() {
+        startIdx = gfx.executeIRQueue(IRQueue, startIdx, next);
+        if (startIdx == length) {
+          self.stats.render = Date.now();
+          if (callback) callback();
+        }
+      }
+      next();
+    },
+    rotatePoint: function pageRotatePoint(x, y, reverse) {
+      var rotate = reverse ? (360 - this.rotate) : this.rotate;
+      switch (rotate) {
+        case 180:
+          return {x: this.width - x, y: y};
+        case 90:
+          return {x: this.width - y, y: this.height - x};
+        case 270:
+          return {x: y, y: x};
+        case 360:
+        case 0:
+        default:
+          return {x: x, y: this.height - y};
+      }
+    },
+    getLinks: function pageGetLinks() {
+      var xref = this.xref;
+      var annotations = xref.fetchIfRef(this.annotations) || [];
+      var i, n = annotations.length;
+      var links = [];
+      for (i = 0; i < n; ++i) {
+        var annotation = xref.fetch(annotations[i]);
+        if (!isDict(annotation))
+          continue;
+        var subtype = annotation.get('Subtype');
+        if (!isName(subtype) || subtype.name != 'Link')
+          continue;
+        var rect = annotation.get('Rect');
+        var topLeftCorner = this.rotatePoint(rect[0], rect[1]);
+        var bottomRightCorner = this.rotatePoint(rect[2], rect[3]);
+
+        var link = {};
+        link.x = Math.min(topLeftCorner.x, bottomRightCorner.x);
+        link.y = Math.min(topLeftCorner.y, bottomRightCorner.y);
+        link.width = Math.abs(topLeftCorner.x - bottomRightCorner.x);
+        link.height = Math.abs(topLeftCorner.y - bottomRightCorner.y);
+        var a = this.xref.fetchIfRef(annotation.get('A'));
+        if (a) {
+          switch (a.get('S').name) {
+            case 'URI':
+              link.url = a.get('URI');
+              break;
+            case 'GoTo':
+              link.dest = a.get('D');
+              break;
+            default:
+              TODO('other link types');
+          }
+        } else if (annotation.has('Dest')) {
+          // simple destination link
+          var dest = annotation.get('Dest');
+          link.dest = isName(dest) ? dest.name : dest;
+        }
+        links.push(link);
+      }
+      return links;
+    },
+    startRendering: function(ctx, callback)  {
+      this.ctx = ctx;
+      this.callback = callback;
+
+      this.startRenderingTime = Date.now();
+      this.pdf.startRendering(this);
+    }
+  };
+
+  return constructor;
+})();
+
+/**
+ * The `PDFDocModel` holds all the data of the PDF file. Compared to the
+ * `PDFDoc`, this one doesn't have any job management code.
+ * Right now there exists one PDFDocModel on the main thread + one object
+ * for each worker. If there is no worker support enabled, there are two
+ * `PDFDocModel` objects on the main thread created.
+ * TODO: Refactor the internal object structure, such that there is no
+ * need for the `PDFDocModel` anymore and there is only one object on the
+ * main thread and not one entire copy on each worker instance.
+ */
+var PDFDocModel = (function pdfDoc() {
+  function constructor(arg, callback) {
+    if (isStream(arg))
+      init.call(this, arg);
+    else if (isArrayBuffer(arg))
+      init.call(this, new Stream(arg));
+    else
+      error('PDFDocModel: Unknown argument type');
+  }
+
+  function init(stream) {
+    assertWellFormed(stream.length > 0, 'stream must have data');
+    this.stream = stream;
+    this.setup();
+  }
+
+  function find(stream, needle, limit, backwards) {
+    var pos = stream.pos;
+    var end = stream.end;
+    var str = '';
+    if (pos + limit > end)
+      limit = end - pos;
+    for (var n = 0; n < limit; ++n)
+      str += stream.getChar();
+    stream.pos = pos;
+    var index = backwards ? str.lastIndexOf(needle) : str.indexOf(needle);
+    if (index == -1)
+      return false; /* not found */
+    stream.pos += index;
+    return true; /* found */
+  }
+
+  constructor.prototype = {
+    get linearization() {
+      var length = this.stream.length;
+      var linearization = false;
+      if (length) {
+        linearization = new Linearization(this.stream);
+        if (linearization.length != length)
+          linearization = false;
+      }
+      // shadow the prototype getter with a data property
+      return shadow(this, 'linearization', linearization);
+    },
+    get startXRef() {
+      var stream = this.stream;
+      var startXRef = 0;
+      var linearization = this.linearization;
+      if (linearization) {
+        // Find end of first obj.
+        stream.reset();
+        if (find(stream, 'endobj', 1024))
+          startXRef = stream.pos + 6;
+      } else {
+        // Find startxref at the end of the file.
+        var start = stream.end - 1024;
+        if (start < 0)
+          start = 0;
+        stream.pos = start;
+        if (find(stream, 'startxref', 1024, true)) {
+          stream.skip(9);
+          var ch;
+          do {
+            ch = stream.getChar();
+          } while (Lexer.isSpace(ch));
+          var str = '';
+          while ((ch - '0') <= 9) {
+            str += ch;
+            ch = stream.getChar();
+          }
+          startXRef = parseInt(str, 10);
+          if (isNaN(startXRef))
+            startXRef = 0;
+        }
+      }
+      // shadow the prototype getter with a data property
+      return shadow(this, 'startXRef', startXRef);
+    },
+    get mainXRefEntriesOffset() {
+      var mainXRefEntriesOffset = 0;
+      var linearization = this.linearization;
+      if (linearization)
+        mainXRefEntriesOffset = linearization.mainXRefEntriesOffset;
+      // shadow the prototype getter with a data property
+      return shadow(this, 'mainXRefEntriesOffset', mainXRefEntriesOffset);
+    },
+    // Find the header, remove leading garbage and setup the stream
+    // starting from the header.
+    checkHeader: function pdfDocCheckHeader() {
+      var stream = this.stream;
+      stream.reset();
+      if (find(stream, '%PDF-', 1024)) {
+        // Found the header, trim off any garbage before it.
+        stream.moveStart();
+        return;
+      }
+      // May not be a PDF file, continue anyway.
+    },
+    setup: function pdfDocSetup(ownerPassword, userPassword) {
+      this.checkHeader();
+      this.xref = new XRef(this.stream,
+                           this.startXRef,
+                           this.mainXRefEntriesOffset);
+      this.catalog = new Catalog(this.xref);
+    },
+    get numPages() {
+      var linearization = this.linearization;
+      var num = linearization ? linearization.numPages : this.catalog.numPages;
+      // shadow the prototype getter
+      return shadow(this, 'numPages', num);
+    },
+    getPage: function pdfDocGetPage(n) {
+      return this.catalog.getPage(n);
+    }
+  };
+
+  return constructor;
+})();
+
+var PDFDoc = (function() {
+  function constructor(arg, callback) {
+    var stream = null;
+    var data = null;
+
+    if (isStream(arg)) {
+      stream = arg;
+      data = arg.bytes;
+    } else if (isArrayBuffer(arg)) {
+      stream = new Stream(arg);
+      data = arg;
+    } else {
+      error('PDFDoc: Unknown argument type');
+    }
+
+    this.data = data;
+    this.stream = stream;
+    this.pdf = new PDFDocModel(stream);
+
+    this.catalog = this.pdf.catalog;
+    this.objs = new PDFObjects();
+
+    this.pageCache = [];
+
+    if (useWorker) {
+      var worker = new Worker('../worker/pdf_worker_loader.js');
+    } else {
+      // If we don't use a worker, just post/sendMessage to the main thread.
+      var worker = {
+        postMessage: function(obj) {
+          worker.onmessage({data: obj});
+        },
+        terminate: function() {}
+      };
+    }
+    this.worker = worker;
+
+    this.fontsLoading = {};
+
+    var processorHandler = this.processorHandler =
+                                        new MessageHandler('main', worker);
+
+    processorHandler.on('page', function(data) {
+      var pageNum = data.pageNum;
+      var page = this.pageCache[pageNum];
+      var depFonts = data.depFonts;
+
+      page.startRenderingFromIRQueue(data.IRQueue, depFonts);
+    }, this);
+
+    processorHandler.on('obj', function(data) {
+      var id = data[0];
+      var type = data[1];
+
+      switch (type) {
+        case 'JpegStream':
+          var IR = data[2];
+          new JpegImage(id, IR, this.objs);
+        break;
+        case 'Font':
+          var name = data[2];
+          var file = data[3];
+          var properties = data[4];
+
+          if (file) {
+            var fontFileDict = new Dict();
+            fontFileDict.map = file.dict.map;
+
+            var fontFile = new Stream(file.bytes, file.start,
+                                      file.end - file.start, fontFileDict);
+
+            // Check if this is a FlateStream. Otherwise just use the created
+            // Stream one. This makes complex_ttf_font.pdf work.
+            var cmf = file.bytes[0];
+            if ((cmf & 0x0f) == 0x08) {
+              file = new FlateStream(fontFile);
+            } else {
+              file = fontFile;
+            }
+          }
+
+          // For now, resolve the font object here direclty. The real font
+          // object is then created in FontLoader.bind().
+          this.objs.resolve(id, {
+            name: name,
+            file: file,
+            properties: properties
+          });
+        break;
+        default:
+          throw 'Got unkown object type ' + type;
+      }
+    }, this);
+
+    processorHandler.on('font_ready', function(data) {
+      var id = data[0];
+      var font = new FontShape(data[1]);
+
+      // If there is no string, then there is nothing to attach to the DOM.
+      if (!font.str) {
+        this.objs.resolve(id, font);
+      } else {
+        this.objs.setData(id, font);
+      }
+    }.bind(this));
+
+    if (!useWorker) {
+      // If the main thread is our worker, setup the handling for the messages
+      // the main thread sends to it self.
+      WorkerProcessorHandler.setup(processorHandler);
+    }
+
+    this.workerReadyPromise = new Promise('workerReady');
+    setTimeout(function() {
+      processorHandler.send('doc', this.data);
+      this.workerReadyPromise.resolve(true);
+    }.bind(this));
+  }
+
+  constructor.prototype = {
+    get numPages() {
+      return this.pdf.numPages;
+    },
+
+    startRendering: function(page) {
+      // The worker might not be ready to receive the page request yet.
+      this.workerReadyPromise.then(function() {
+        this.processorHandler.send('page_request', page.pageNumber + 1);
+      }.bind(this));
+    },
+
+    getPage: function(n) {
+      if (this.pageCache[n])
+        return this.pageCache[n];
+
+      var page = this.pdf.getPage(n);
+      // Add a reference to the objects such that Page can forward the reference
+      // to the CanvasGraphics and so on.
+      page.objs = this.objs;
+      page.pdf = this;
+      return this.pageCache[n] = page;
+    },
+
+    destroy: function() {
+      if (this.worker)
+        this.worker.terminate();
+
+      if (this.fontWorker)
+        this.fontWorker.terminate();
+
+      for (var n in this.pageCache)
+        delete this.pageCache[n];
+
+      delete this.data;
+      delete this.stream;
+      delete this.pdf;
+      delete this.catalog;
+    }
+  };
+
+  return constructor;
+})();
index 05cd8cd32441106185f23460acf24e29ae13b490..ba721d0b5218190bec1e3e8e3f182cf5fb59077d 100644 (file)
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
-'use strict';
+// // TODO: Global namespace
+// var PDF = {};
 
-var ERRORS = 0, WARNINGS = 1, TODOS = 5;
-var verbosity = WARNINGS;
+// Stay away from global
+(function(){
 
-// Set this to true if you want to use workers.
-var useWorker = false;
+  'use strict';
 
-//
-// getPdf()
-// Convenience function to perform binary Ajax GET
-// Usage: getPdf('http://...', callback)
-//        getPdf({
-//                 url:String ,
-//                 [,progress:Function, error:Function]
-//               },
-//               callback)
-//
-function getPdf(arg, callback) {
-  var params = arg;
-  if (typeof arg === 'string')
-    params = { url: arg };
+  // All files will be inserted below this point
+  // INSERT_POINT
 
-  var xhr = new XMLHttpRequest();
-  xhr.open('GET', params.url);
-  xhr.mozResponseType = xhr.responseType = 'arraybuffer';
-  xhr.expected = (document.URL.indexOf('file:') === 0) ? 0 : 200;
+  //
+  // Expose API in global object
+  //
+  window.PDFDoc = PDFDoc;
+  window.getPdf = getPdf;
 
-  if ('progress' in params)
-    xhr.onprogress = params.progress || undefined;
-
-  if ('error' in params)
-    xhr.onerror = params.error || undefined;
-
-  xhr.onreadystatechange = function getPdfOnreadystatechange() {
-    if (xhr.readyState === 4 && xhr.status === xhr.expected) {
-      var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse ||
-                  xhr.responseArrayBuffer || xhr.response);
-      callback(data);
-    }
-  };
-  xhr.send(null);
-}
-
-var Page = (function pagePage() {
-  function constructor(xref, pageNumber, pageDict, ref) {
-    this.pageNumber = pageNumber;
-    this.pageDict = pageDict;
-    this.stats = {
-      create: Date.now(),
-      compile: 0.0,
-      fonts: 0.0,
-      images: 0.0,
-      render: 0.0
-    };
-    this.xref = xref;
-    this.ref = ref;
-  }
-
-  constructor.prototype = {
-    getPageProp: function pageGetPageProp(key) {
-      return this.xref.fetchIfRef(this.pageDict.get(key));
-    },
-    inheritPageProp: function pageInheritPageProp(key) {
-      var dict = this.pageDict;
-      var obj = dict.get(key);
-      while (obj === undefined) {
-        dict = this.xref.fetchIfRef(dict.get('Parent'));
-        if (!dict)
-          break;
-        obj = dict.get(key);
-      }
-      return obj;
-    },
-    get content() {
-      return shadow(this, 'content', this.getPageProp('Contents'));
-    },
-    get resources() {
-      return shadow(this, 'resources', this.inheritPageProp('Resources'));
-    },
-    get mediaBox() {
-      var obj = this.inheritPageProp('MediaBox');
-      // Reset invalid media box to letter size.
-      if (!isArray(obj) || obj.length !== 4)
-        obj = [0, 0, 612, 792];
-      return shadow(this, 'mediaBox', obj);
-    },
-    get view() {
-      var obj = this.inheritPageProp('CropBox');
-      var view = {
-        x: 0,
-        y: 0,
-        width: this.width,
-        height: this.height
-      };
-      if (isArray(obj) && obj.length == 4) {
-        var tl = this.rotatePoint(obj[0], obj[1]);
-        var br = this.rotatePoint(obj[2], obj[3]);
-        view.x = Math.min(tl.x, br.x);
-        view.y = Math.min(tl.y, br.y);
-        view.width = Math.abs(tl.x - br.x);
-        view.height = Math.abs(tl.y - br.y);
-      }
-
-      return shadow(this, 'cropBox', view);
-    },
-    get annotations() {
-      return shadow(this, 'annotations', this.inheritPageProp('Annots'));
-    },
-    get width() {
-      var mediaBox = this.mediaBox;
-      var rotate = this.rotate;
-      var width;
-      if (rotate == 0 || rotate == 180) {
-        width = (mediaBox[2] - mediaBox[0]);
-      } else {
-        width = (mediaBox[3] - mediaBox[1]);
-      }
-      return shadow(this, 'width', width);
-    },
-    get height() {
-      var mediaBox = this.mediaBox;
-      var rotate = this.rotate;
-      var height;
-      if (rotate == 0 || rotate == 180) {
-        height = (mediaBox[3] - mediaBox[1]);
-      } else {
-        height = (mediaBox[2] - mediaBox[0]);
-      }
-      return shadow(this, 'height', height);
-    },
-    get rotate() {
-      var rotate = this.inheritPageProp('Rotate') || 0;
-      // Normalize rotation so it's a multiple of 90 and between 0 and 270
-      if (rotate % 90 != 0) {
-        rotate = 0;
-      } else if (rotate >= 360) {
-        rotate = rotate % 360;
-      } else if (rotate < 0) {
-        // The spec doesn't cover negatives, assume its counterclockwise
-        // rotation. The following is the other implementation of modulo.
-        rotate = ((rotate % 360) + 360) % 360;
-      }
-      return shadow(this, 'rotate', rotate);
-    },
-
-    startRenderingFromIRQueue: function startRenderingFromIRQueue(
-                                                IRQueue, fonts) {
-      var self = this;
-      this.IRQueue = IRQueue;
-      var gfx = new CanvasGraphics(this.ctx, this.objs);
-      var startTime = Date.now();
-
-      var displayContinuation = function pageDisplayContinuation() {
-        // Always defer call to display() to work around bug in
-        // Firefox error reporting from XHR callbacks.
-        setTimeout(function pageSetTimeout() {
-          try {
-            self.display(gfx, self.callback);
-          } catch (e) {
-            if (self.callback) self.callback(e.toString());
-            throw e;
-          }
-        });
-      };
-
-      this.ensureFonts(fonts, function() {
-        displayContinuation();
-      });
-    },
-
-    getIRQueue: function(handler, dependency) {
-      if (this.IRQueue) {
-        // content was compiled
-        return this.IRQueue;
-      }
-
-      var xref = this.xref;
-      var content = xref.fetchIfRef(this.content);
-      var resources = xref.fetchIfRef(this.resources);
-      if (isArray(content)) {
-        // fetching items
-        var i, n = content.length;
-        for (i = 0; i < n; ++i)
-          content[i] = xref.fetchIfRef(content[i]);
-        content = new StreamsSequenceStream(content);
-      }
-
-      var pe = this.pe = new PartialEvaluator(
-                                xref, handler, 'p' + this.pageNumber + '_');
-      var IRQueue = {};
-      return this.IRQueue = pe.getIRQueue(
-                                content, resources, IRQueue, dependency);
-    },
-
-    ensureFonts: function(fonts, callback) {
-      // Convert the font names to the corresponding font obj.
-      for (var i = 0; i < fonts.length; i++) {
-        fonts[i] = this.objs.objs[fonts[i]].data;
-      }
-
-      // Load all the fonts
-      var fontObjs = FontLoader.bind(
-        fonts,
-        function(fontObjs) {
-          this.stats.fonts = Date.now();
-
-          callback.call(this);
-        }.bind(this),
-        this.objs
-      );
-    },
-
-    display: function(gfx, callback) {
-      var xref = this.xref;
-      var resources = xref.fetchIfRef(this.resources);
-      var mediaBox = xref.fetchIfRef(this.mediaBox);
-      assertWellFormed(isDict(resources), 'invalid page resources');
-
-      gfx.xref = xref;
-      gfx.res = resources;
-      gfx.beginDrawing({ x: mediaBox[0], y: mediaBox[1],
-            width: this.width,
-            height: this.height,
-            rotate: this.rotate });
-
-      var startIdx = 0;
-      var length = this.IRQueue.fnArray.length;
-      var IRQueue = this.IRQueue;
-
-      var self = this;
-      var startTime = Date.now();
-      function next() {
-        startIdx = gfx.executeIRQueue(IRQueue, startIdx, next);
-        if (startIdx == length) {
-          self.stats.render = Date.now();
-          if (callback) callback();
-        }
-      }
-      next();
-    },
-    rotatePoint: function pageRotatePoint(x, y, reverse) {
-      var rotate = reverse ? (360 - this.rotate) : this.rotate;
-      switch (rotate) {
-        case 180:
-          return {x: this.width - x, y: y};
-        case 90:
-          return {x: this.width - y, y: this.height - x};
-        case 270:
-          return {x: y, y: x};
-        case 360:
-        case 0:
-        default:
-          return {x: x, y: this.height - y};
-      }
-    },
-    getLinks: function pageGetLinks() {
-      var xref = this.xref;
-      var annotations = xref.fetchIfRef(this.annotations) || [];
-      var i, n = annotations.length;
-      var links = [];
-      for (i = 0; i < n; ++i) {
-        var annotation = xref.fetch(annotations[i]);
-        if (!isDict(annotation))
-          continue;
-        var subtype = annotation.get('Subtype');
-        if (!isName(subtype) || subtype.name != 'Link')
-          continue;
-        var rect = annotation.get('Rect');
-        var topLeftCorner = this.rotatePoint(rect[0], rect[1]);
-        var bottomRightCorner = this.rotatePoint(rect[2], rect[3]);
-
-        var link = {};
-        link.x = Math.min(topLeftCorner.x, bottomRightCorner.x);
-        link.y = Math.min(topLeftCorner.y, bottomRightCorner.y);
-        link.width = Math.abs(topLeftCorner.x - bottomRightCorner.x);
-        link.height = Math.abs(topLeftCorner.y - bottomRightCorner.y);
-        var a = this.xref.fetchIfRef(annotation.get('A'));
-        if (a) {
-          switch (a.get('S').name) {
-            case 'URI':
-              link.url = a.get('URI');
-              break;
-            case 'GoTo':
-              link.dest = a.get('D');
-              break;
-            default:
-              TODO('other link types');
-          }
-        } else if (annotation.has('Dest')) {
-          // simple destination link
-          var dest = annotation.get('Dest');
-          link.dest = isName(dest) ? dest.name : dest;
-        }
-        links.push(link);
-      }
-      return links;
-    },
-    startRendering: function(ctx, callback)  {
-      this.ctx = ctx;
-      this.callback = callback;
-
-      this.startRenderingTime = Date.now();
-      this.pdf.startRendering(this);
-    }
-  };
-
-  return constructor;
-})();
-
-/**
- * The `PDFDocModel` holds all the data of the PDF file. Compared to the
- * `PDFDoc`, this one doesn't have any job management code.
- * Right now there exists one PDFDocModel on the main thread + one object
- * for each worker. If there is no worker support enabled, there are two
- * `PDFDocModel` objects on the main thread created.
- * TODO: Refactor the internal object structure, such that there is no
- * need for the `PDFDocModel` anymore and there is only one object on the
- * main thread and not one entire copy on each worker instance.
- */
-var PDFDocModel = (function pdfDoc() {
-  function constructor(arg, callback) {
-    if (isStream(arg))
-      init.call(this, arg);
-    else if (isArrayBuffer(arg))
-      init.call(this, new Stream(arg));
-    else
-      error('PDFDocModel: Unknown argument type');
-  }
-
-  function init(stream) {
-    assertWellFormed(stream.length > 0, 'stream must have data');
-    this.stream = stream;
-    this.setup();
-  }
-
-  function find(stream, needle, limit, backwards) {
-    var pos = stream.pos;
-    var end = stream.end;
-    var str = '';
-    if (pos + limit > end)
-      limit = end - pos;
-    for (var n = 0; n < limit; ++n)
-      str += stream.getChar();
-    stream.pos = pos;
-    var index = backwards ? str.lastIndexOf(needle) : str.indexOf(needle);
-    if (index == -1)
-      return false; /* not found */
-    stream.pos += index;
-    return true; /* found */
-  }
-
-  constructor.prototype = {
-    get linearization() {
-      var length = this.stream.length;
-      var linearization = false;
-      if (length) {
-        linearization = new Linearization(this.stream);
-        if (linearization.length != length)
-          linearization = false;
-      }
-      // shadow the prototype getter with a data property
-      return shadow(this, 'linearization', linearization);
-    },
-    get startXRef() {
-      var stream = this.stream;
-      var startXRef = 0;
-      var linearization = this.linearization;
-      if (linearization) {
-        // Find end of first obj.
-        stream.reset();
-        if (find(stream, 'endobj', 1024))
-          startXRef = stream.pos + 6;
-      } else {
-        // Find startxref at the end of the file.
-        var start = stream.end - 1024;
-        if (start < 0)
-          start = 0;
-        stream.pos = start;
-        if (find(stream, 'startxref', 1024, true)) {
-          stream.skip(9);
-          var ch;
-          do {
-            ch = stream.getChar();
-          } while (Lexer.isSpace(ch));
-          var str = '';
-          while ((ch - '0') <= 9) {
-            str += ch;
-            ch = stream.getChar();
-          }
-          startXRef = parseInt(str, 10);
-          if (isNaN(startXRef))
-            startXRef = 0;
-        }
-      }
-      // shadow the prototype getter with a data property
-      return shadow(this, 'startXRef', startXRef);
-    },
-    get mainXRefEntriesOffset() {
-      var mainXRefEntriesOffset = 0;
-      var linearization = this.linearization;
-      if (linearization)
-        mainXRefEntriesOffset = linearization.mainXRefEntriesOffset;
-      // shadow the prototype getter with a data property
-      return shadow(this, 'mainXRefEntriesOffset', mainXRefEntriesOffset);
-    },
-    // Find the header, remove leading garbage and setup the stream
-    // starting from the header.
-    checkHeader: function pdfDocCheckHeader() {
-      var stream = this.stream;
-      stream.reset();
-      if (find(stream, '%PDF-', 1024)) {
-        // Found the header, trim off any garbage before it.
-        stream.moveStart();
-        return;
-      }
-      // May not be a PDF file, continue anyway.
-    },
-    setup: function pdfDocSetup(ownerPassword, userPassword) {
-      this.checkHeader();
-      this.xref = new XRef(this.stream,
-                           this.startXRef,
-                           this.mainXRefEntriesOffset);
-      this.catalog = new Catalog(this.xref);
-    },
-    get numPages() {
-      var linearization = this.linearization;
-      var num = linearization ? linearization.numPages : this.catalog.numPages;
-      // shadow the prototype getter
-      return shadow(this, 'numPages', num);
-    },
-    getPage: function pdfDocGetPage(n) {
-      return this.catalog.getPage(n);
-    }
-  };
-
-  return constructor;
-})();
-
-var PDFDoc = (function() {
-  function constructor(arg, callback) {
-    var stream = null;
-    var data = null;
-
-    if (isStream(arg)) {
-      stream = arg;
-      data = arg.bytes;
-    } else if (isArrayBuffer(arg)) {
-      stream = new Stream(arg);
-      data = arg;
-    } else {
-      error('PDFDoc: Unknown argument type');
-    }
-
-    this.data = data;
-    this.stream = stream;
-    this.pdf = new PDFDocModel(stream);
-
-    this.catalog = this.pdf.catalog;
-    this.objs = new PDFObjects();
-
-    this.pageCache = [];
-
-    if (useWorker) {
-      var worker = new Worker('../worker/pdf_worker_loader.js');
-    } else {
-      // If we don't use a worker, just post/sendMessage to the main thread.
-      var worker = {
-        postMessage: function(obj) {
-          worker.onmessage({data: obj});
-        },
-        terminate: function() {}
-      };
-    }
-    this.worker = worker;
-
-    this.fontsLoading = {};
-
-    var processorHandler = this.processorHandler =
-                                        new MessageHandler('main', worker);
-
-    processorHandler.on('page', function(data) {
-      var pageNum = data.pageNum;
-      var page = this.pageCache[pageNum];
-      var depFonts = data.depFonts;
-
-      page.startRenderingFromIRQueue(data.IRQueue, depFonts);
-    }, this);
-
-    processorHandler.on('obj', function(data) {
-      var id = data[0];
-      var type = data[1];
-
-      switch (type) {
-        case 'JpegStream':
-          var IR = data[2];
-          new JpegImage(id, IR, this.objs);
-        break;
-        case 'Font':
-          var name = data[2];
-          var file = data[3];
-          var properties = data[4];
-
-          if (file) {
-            var fontFileDict = new Dict();
-            fontFileDict.map = file.dict.map;
-
-            var fontFile = new Stream(file.bytes, file.start,
-                                      file.end - file.start, fontFileDict);
-
-            // Check if this is a FlateStream. Otherwise just use the created
-            // Stream one. This makes complex_ttf_font.pdf work.
-            var cmf = file.bytes[0];
-            if ((cmf & 0x0f) == 0x08) {
-              file = new FlateStream(fontFile);
-            } else {
-              file = fontFile;
-            }
-          }
-
-          // For now, resolve the font object here direclty. The real font
-          // object is then created in FontLoader.bind().
-          this.objs.resolve(id, {
-            name: name,
-            file: file,
-            properties: properties
-          });
-        break;
-        default:
-          throw 'Got unkown object type ' + type;
-      }
-    }, this);
-
-    processorHandler.on('font_ready', function(data) {
-      var id = data[0];
-      var font = new FontShape(data[1]);
-
-      // If there is no string, then there is nothing to attach to the DOM.
-      if (!font.str) {
-        this.objs.resolve(id, font);
-      } else {
-        this.objs.setData(id, font);
-      }
-    }.bind(this));
-
-    if (!useWorker) {
-      // If the main thread is our worker, setup the handling for the messages
-      // the main thread sends to it self.
-      WorkerProcessorHandler.setup(processorHandler);
-    }
-
-    this.workerReadyPromise = new Promise('workerReady');
-    setTimeout(function() {
-      processorHandler.send('doc', this.data);
-      this.workerReadyPromise.resolve(true);
-    }.bind(this));
-  }
-
-  constructor.prototype = {
-    get numPages() {
-      return this.pdf.numPages;
-    },
-
-    startRendering: function(page) {
-      // The worker might not be ready to receive the page request yet.
-      this.workerReadyPromise.then(function() {
-        this.processorHandler.send('page_request', page.pageNumber + 1);
-      }.bind(this));
-    },
-
-    getPage: function(n) {
-      if (this.pageCache[n])
-        return this.pageCache[n];
-
-      var page = this.pdf.getPage(n);
-      // Add a reference to the objects such that Page can forward the reference
-      // to the CanvasGraphics and so on.
-      page.objs = this.objs;
-      page.pdf = this;
-      return this.pageCache[n] = page;
-    },
-
-    destroy: function() {
-      if (this.worker)
-        this.worker.terminate();
-
-      if (this.fontWorker)
-        this.fontWorker.terminate();
-
-      for (var n in this.pageCache)
-        delete this.pageCache[n];
-
-      delete this.data;
-      delete this.stream;
-      delete this.pdf;
-      delete this.catalog;
-    }
-  };
-
-  return constructor;
-})();
+})(); // self-executing function
diff --git a/watch.py b/watch.py
new file mode 100644 (file)
index 0000000..1a94f89
--- /dev/null
+++ b/watch.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+# Python port of Ian Piumarta's watch.c
+# BSD Licensed - http://eschew.org/txt/bsd.txt
+
+import re
+import os
+import sys
+import time
+import string
+import subprocess
+
+maxfiles = 64
+
+def usage():
+  return """usage: %(watch)s <paths...> - <commands...>
+  <paths...> is/are the file/s to be monitored
+  <commands...> is/are the commands to execute (quote if args required)
+  Note: occurrences of '${file}' in command strings will be replaced
+    with updated filename before execution.
+      e.g.:   %(watch)s *.txt - 'echo ${file}'
+""" % { 'watch': sys.argv[0] }
+
+def try_get_mtime(path):
+  try:
+    buf = os.stat(path)
+  except OSError:
+    time.sleep(1)
+    try:
+      buf = os.stat(path)
+    except OSError:
+      print "%(watch)s: %(file)s: file not found"
+      sys.exit(1)
+  return buf.st_mtime
+
+def execute_commands(commands, filename):
+  for command in commands:
+    cmd = string.Template(command).safe_substitute(file=filename)
+    cmd_pieces = re.split('\s+', cmd)
+    subprocess.Popen(cmd_pieces)
+
+def main():
+  files = []
+  commands = []
+  seeing_paths = True
+  for part in sys.argv[1:]:
+    if part == '-':
+      seeing_paths = False
+    elif seeing_paths:
+      files.append(part)
+    else:
+      commands.append(part)
+
+  if len(commands) == 0:
+    print usage()
+    sys.exit(1)
+
+  if len(files) > maxfiles:
+    print "%(watch)s: too many files to watch" % sys.argv[0]
+
+  mtimes = dict([(f, try_get_mtime(f)) for f in files])
+  done = False
+  while not done:
+    for f in files:
+      old_mtime = mtimes[f]
+      new_mtime = try_get_mtime(f)
+      if new_mtime != old_mtime:
+        mtimes[f] = new_mtime
+        execute_commands(commands, f)
+    time.sleep(1)
+
+if __name__ == '__main__':
+  try:
+    main()
+  except KeyboardInterrupt:
+    sys.exit(0)
+
+
index a1d0ca894793127869b6314f0b52dbe77b74c1cb..f6e492d3e56210ba2e1f201dca0345f7dca5ca9c 100644 (file)
@@ -6,25 +6,7 @@
 
         <script type="text/javascript" src="compatibility.js"></script>
         <script type="text/javascript" src="viewer.js"></script>
-        <script type="text/javascript" src="../src/pdf.js"></script>
-        <script type="text/javascript" src="../src/util.js"></script>
-        <script type="text/javascript" src="../src/canvas.js"></script>
-        <script type="text/javascript" src="../src/function.js"></script>
-        <script type="text/javascript" src="../src/colorspace.js"></script>
-        <script type="text/javascript" src="../src/image.js"></script>
-        <script type="text/javascript" src="../src/pattern.js"></script>
-        <script type="text/javascript" src="../src/obj.js"></script>
-        <script type="text/javascript" src="../src/parser.js"></script>
-        <script type="text/javascript" src="../src/evaluator.js"></script>
-        <script type="text/javascript" src="../src/stream.js"></script>
-        <script type="text/javascript" src="../src/fonts.js"></script>
-        <script type="text/javascript" src="../src/crypto.js"></script>
-        <script type="text/javascript" src="../src/glyphlist.js"></script>
-        <script type="text/javascript" src="../src/metrics.js"></script>
-        <script type="text/javascript" src="../src/charsets.js"></script>
-        <script type="text/javascript" src="../src/cidmaps.js"></script>
-        <script type="text/javascript" src="../src/worker/message_handler.js"></script>
-        <script type="text/javascript" src="../src/worker/processor_handler.js"></script>
+        <script type="text/javascript" src="../pdf.js"></script>
   </head>
 
   <body>