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); } }