From 3b940c4bcd4f334c87bc937a610820040cf6fe2d Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Fri, 19 Aug 2011 22:41:56 -0500 Subject: [PATCH 1/2] 'GoTo' action/links; document outline (no UI) --- pdf.js | 78 +++++++++++++++++++++++++++++++++++++++++++++------ web/viewer.js | 31 ++++++++++++++++---- 2 files changed, 94 insertions(+), 15 deletions(-) diff --git a/pdf.js b/pdf.js index b8193a138..eb9e18086 100644 --- a/pdf.js +++ b/pdf.js @@ -3163,7 +3163,7 @@ var XRef = (function() { })(); var Page = (function() { - function constructor(xref, pageNumber, pageDict) { + function constructor(xref, pageNumber, pageDict, ref) { this.pageNumber = pageNumber; this.pageDict = pageDict; this.stats = { @@ -3174,6 +3174,7 @@ var Page = (function() { render: 0.0 }; this.xref = xref; + this.ref = ref; } constructor.prototype = { @@ -3349,10 +3350,10 @@ var Page = (function() { var annotation = xref.fetch(annotations[i]); if (!IsDict(annotation, 'Annot')) continue; - var subtype = annotation.get("Subtype"); + var subtype = annotation.get('Subtype'); if (!IsName(subtype) || subtype.name != 'Link') continue; - var rect = annotation.get("Rect"); + var rect = annotation.get('Rect'); var topLeftCorner = this.rotatePoint(rect[0], rect[1]); var bottomRightCorner = this.rotatePoint(rect[2], rect[3]); @@ -3361,14 +3362,17 @@ var Page = (function() { 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 = annotation.get("A"); + var a = this.xref.fetchIfRef(annotation.get('A')); if (a) { - switch(a.get("S").name) { - case "URI": - link.url = a.get("URI"); + 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"); + TODO('other link types'); break; } } @@ -3398,6 +3402,62 @@ var Catalog = (function() { // shadow the prototype getter return shadow(this, 'toplevelPagesDict', obj); }, + get documentOutline() { + function convertIfUnicode(str) { + if (str[0] === '\xFE' && str[1] === '\xFF') { + // UTF16BE BOM + var i, n = str.length, str2 = ""; + for (i = 2; i < n; i+=2) + str2 += String.fromCharCode((str.charCodeAt(i) << 8) | str.charCodeAt(i + 1)); + str = str2; + } + return str; + } + + var obj = this.catDict.get('Outlines'); + var root = { items: [] }; + if (IsRef(obj)) { + obj = this.xref.fetch(obj).get('First'); + var processed = {}; + if (IsRef(obj)) { + var queue = [{obj: obj, parent: root}]; + // to avoid recursion keeping track of the items + // in the processed dictionary + processed['R' + obj.num + ',' + obj.gen] = true; + while (queue.length > 0) { + var i = queue.shift(); + var outlineDict = this.xref.fetch(i.obj); + if (!outlineDict.has('Title')) + error('Invalid outline item'); + var dest = outlineDict.get('Dest'); + if (!dest && outlineDict.get('A')) { + var a = this.xref.fetchIfRef(outlineDict.get('A')); + dest = a.get('D'); + } + var outlineItem = { + dest: dest, + title: convertIfUnicode(outlineDict.get('Title')), + color: outlineDict.get('C') || [0, 0, 0], + bold: !!(outlineDict.get('F') & 2), + italic: !!(outlineDict.get('F') & 1), + items: [] + }; + i.parent.items.push(outlineItem); + obj = outlineDict.get('First'); + if (IsRef(obj) && !processed['R' + obj.num + ',' + obj.gen]) { + queue.push({obj: obj, parent: outlineItem}); + processed['R' + obj.num + ',' + obj.gen] = true; + } + obj = outlineDict.get('Next'); + if (IsRef(obj) && !processed['R' + obj.num + ',' + obj.gen]) { + queue.push({obj: obj, parent: i.parent}); + processed['R' + obj.num + ',' + obj.gen] = true; + } + } + } + } + return shadow(this, 'documentOutline', root); + }, get numPages() { var obj = this.toplevelPagesDict.get('Count'); assertWellFormed( @@ -3418,7 +3478,7 @@ var Catalog = (function() { 'page dictionary kid is not a reference'); var obj = this.xref.fetch(kid); if (IsDict(obj, 'Page') || (IsDict(obj) && !obj.has('Kids'))) { - pageCache.push(new Page(this.xref, pageCache.length, obj)); + pageCache.push(new Page(this.xref, pageCache.length, obj, kid)); } else { // must be a child page dictionary assertWellFormed( IsDict(obj), diff --git a/web/viewer.js b/web/viewer.js index e501a10b2..24afce799 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -84,6 +84,20 @@ var PDFView = { xhr.send(null); }, + navigateTo: function (dest) { + var i, n = this.pages.length; + var destRef = dest[0]; + // TODO optimize destination page search + for (i = 0; i < n; i++) { + var pageRef = this.pages[i].content.ref; + if (destRef.num == pageRef.num && destRef.gen == pageRef.gen) { + this.page = i + 1; + // TODO scroll to specific region on the page + break; + } + } + }, + load: function(data, scale) { var sidebar = document.getElementById('sidebarView'); sidebar.parentNode.scrollTop = 0; @@ -105,7 +119,7 @@ var PDFView = { for (var i = 1; i <= pagesCount; i++) { var page = pdf.getPage(i); pages.push(new PageView(container, page, i, page.width, page.height, - page.stats)); + page.stats, this.navigateTo.bind(this))); thumbnails.push(new ThumbnailView(sidebar, pages[i - 1])); } @@ -140,7 +154,8 @@ var PDFView = { } }; -var PageView = function(container, content, id, width, height, stats) { +var PageView = function(container, content, id, width, height, + stats, navigateTo) { this.width = width; this.height = height; this.id = id; @@ -178,10 +193,9 @@ var PageView = function(container, content, id, width, height, stats) { } x /= scale; y /= scale; - for (var i = 0; i < links.length; i++) { + var i, n = links.length; + for (i = 0; i < n; i++) { var link = links[i]; - if (!link.url) - continue; if (link.x <= x && link.y <= y && x < link.x + link.width && y < link.y + link.height) { currentLink = link; @@ -193,7 +207,12 @@ var PageView = function(container, content, id, width, height, stats) { canvas.style.cursor = 'default'; }, false); canvas.addEventListener('mousedown', function(e) { - window.location.href = currentLink.url; + if (!currentLink) + return; + if (currentLink.url) + window.location.href = currentLink.url; + if (currentLink.dest) + navigateTo(currentLink.dest); }, false); } } From 3e0c0de985f6cad680b19b1676c71270483a8d16 Mon Sep 17 00:00:00 2001 From: notmasteryet Date: Sat, 20 Aug 2011 09:51:44 -0500 Subject: [PATCH 2/2] Fixing bookmarks for PDF32000 --- pdf.js | 70 +++++++++++++++++++++++++++++++++++++++++++++------ web/viewer.js | 21 +++++++++------- 2 files changed, 74 insertions(+), 17 deletions(-) diff --git a/pdf.js b/pdf.js index eb9e18086..ef2b0c612 100644 --- a/pdf.js +++ b/pdf.js @@ -2228,6 +2228,26 @@ var Ref = (function() { return constructor; })(); +// The reference is identified by number and generation, +// this structure stores only one instance of the reference. +var RefSet = (function() { + function constructor() { + this.dict = {}; + } + + constructor.prototype = { + has: function(ref) { + return !!this.dict['R' + ref.num + '.' + ref.gen]; + }, + + put: function(ref) { + this.dict['R' + ref.num + '.' + ref.gen] = ref; + } + }; + + return constructor; +})(); + function IsBool(v) { return typeof v == 'boolean'; } @@ -3408,22 +3428,22 @@ var Catalog = (function() { // UTF16BE BOM var i, n = str.length, str2 = ""; for (i = 2; i < n; i+=2) - str2 += String.fromCharCode((str.charCodeAt(i) << 8) | str.charCodeAt(i + 1)); + str2 += String.fromCharCode( + (str.charCodeAt(i) << 8) | str.charCodeAt(i + 1)); str = str2; } return str; } - var obj = this.catDict.get('Outlines'); var root = { items: [] }; if (IsRef(obj)) { obj = this.xref.fetch(obj).get('First'); - var processed = {}; + var processed = new RefSet(); if (IsRef(obj)) { var queue = [{obj: obj, parent: root}]; // to avoid recursion keeping track of the items // in the processed dictionary - processed['R' + obj.num + ',' + obj.gen] = true; + processed.put(obj); while (queue.length > 0) { var i = queue.shift(); var outlineDict = this.xref.fetch(i.obj); @@ -3438,20 +3458,21 @@ var Catalog = (function() { dest: dest, title: convertIfUnicode(outlineDict.get('Title')), color: outlineDict.get('C') || [0, 0, 0], + count: outlineDict.get('Count'), bold: !!(outlineDict.get('F') & 2), italic: !!(outlineDict.get('F') & 1), items: [] }; i.parent.items.push(outlineItem); obj = outlineDict.get('First'); - if (IsRef(obj) && !processed['R' + obj.num + ',' + obj.gen]) { + if (IsRef(obj) && !processed.has(obj)) { queue.push({obj: obj, parent: outlineItem}); - processed['R' + obj.num + ',' + obj.gen] = true; + processed.put(obj); } obj = outlineDict.get('Next'); - if (IsRef(obj) && !processed['R' + obj.num + ',' + obj.gen]) { + if (IsRef(obj) && !processed.has(obj)) { queue.push({obj: obj, parent: i.parent}); - processed['R' + obj.num + ',' + obj.gen] = true; + processed.put(obj); } } } @@ -3488,6 +3509,39 @@ var Catalog = (function() { } } }, + get destinations() { + var xref = this.xref; + var obj = this.catDict.get('Names'); + obj = obj ? xref.fetch(obj) : this.catDict; + obj = obj.get('Dests'); + var dests = {}; + if (obj) { + // reading name tree + var processed = new RefSet(); + processed.put(obj); + var queue = [obj]; + while (queue.length > 0) { + var i, n; + obj = xref.fetch(queue.shift()); + if (obj.has('Kids')) { + var kids = obj.get('Kids'); + for (i = 0, n = kids.length; i < n; i++) { + var kid = kids[i]; + if (processed.has(kid)) + error('invalid destinations'); + queue.push(kid); + processed.put(kid); + } + continue; + } + var names = obj.get('Names'); + for (i = 0, n = names.length; i < n; i += 2) { + dests[names[i]] = xref.fetch(names[i + 1]).get('D'); + } + } + } + return shadow(this, 'destinations', dests); + }, getPage: function(n) { var pageCache = this.pageCache; if (!pageCache) { diff --git a/web/viewer.js b/web/viewer.js index 24afce799..8fe011b29 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -85,16 +85,14 @@ var PDFView = { }, navigateTo: function (dest) { - var i, n = this.pages.length; + if (typeof dest === 'string') + dest = this.destinations[dest]; + // dest array looks like that: var destRef = dest[0]; - // TODO optimize destination page search - for (i = 0; i < n; i++) { - var pageRef = this.pages[i].content.ref; - if (destRef.num == pageRef.num && destRef.gen == pageRef.gen) { - this.page = i + 1; - // TODO scroll to specific region on the page - break; - } + var pageNumber = this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R']; + if (pageNumber) { + this.page = pageNumber; + // TODO scroll to specific region on the page, the precise scaling required } }, @@ -115,16 +113,21 @@ var PDFView = { document.getElementById('numPages').innerHTML = pagesCount; var pages = this.pages = []; + var pagesRefMap = {}; var thumbnails = this.thumbnails = []; for (var i = 1; i <= pagesCount; i++) { var page = pdf.getPage(i); pages.push(new PageView(container, page, i, page.width, page.height, page.stats, this.navigateTo.bind(this))); thumbnails.push(new ThumbnailView(sidebar, pages[i - 1])); + var pageRef = page.ref; + pagesRefMap[pageRef.num + ' ' + pageRef.gen + ' R'] = i; } this.scale = (scale || kDefaultScale); this.page = parseInt(document.location.hash.substring(1)) || 1; + this.pagesRefMap = pagesRefMap; + this.destinations = pdf.catalog.destinations; }, getVisiblePages: function() {