From 889de3fc4d51dd54719df76d0232047070235b42 Mon Sep 17 00:00:00 2001 From: Artur Adib Date: Mon, 24 Oct 2011 18:13:12 -0700 Subject: [PATCH] make pdfjs --- .gitignore | 2 + Makefile | 47 +++- src/core.js | 604 +++++++++++++++++++++++++++++++++++++++++++++++ src/pdf.js | 609 ++---------------------------------------------- watch.py | 77 ++++++ web/viewer.html | 20 +- 6 files changed, 735 insertions(+), 624 deletions(-) create mode 100644 src/core.js create mode 100644 watch.py diff --git a/.gitignore b/.gitignore index 9e2d0f211..bb045de3e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ pdf.pdf intelisa.pdf openweb_tm-PRINT.pdf local.mk +build/ + diff --git a/Makefile b/Makefile index 959681f3a..9e7c36a8b 100644 --- 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 index 000000000..05cd8cd32 --- /dev/null +++ b/src/core.js @@ -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; +})(); diff --git a/src/pdf.js b/src/pdf.js index 05cd8cd32..ba721d0b5 100644 --- a/src/pdf.js +++ b/src/pdf.js @@ -1,604 +1,21 @@ /* -*- 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 index 000000000..1a94f8953 --- /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 - + is/are the file/s to be monitored + 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) + + diff --git a/web/viewer.html b/web/viewer.html index a1d0ca894..f6e492d3e 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -6,25 +6,7 @@ - - - - - - - - - - - - - - - - - - - +