diff --git a/src/core/obj.js b/src/core/obj.js index 37102c9b5..5f41e37ec 100644 --- a/src/core/obj.js +++ b/src/core/obj.js @@ -19,18 +19,18 @@ if (typeof define === 'function' && define.amd) { define('pdfjs/core/obj', ['exports', 'pdfjs/shared/util', 'pdfjs/core/primitives', 'pdfjs/core/crypto', 'pdfjs/core/parser', - 'pdfjs/core/chunked_stream'], factory); + 'pdfjs/core/chunked_stream', 'pdfjs/core/colorspace'], factory); } else if (typeof exports !== 'undefined') { factory(exports, require('../shared/util.js'), require('./primitives.js'), require('./crypto.js'), require('./parser.js'), - require('./chunked_stream.js')); + require('./chunked_stream.js'), require('./colorspace.js')); } else { factory((root.pdfjsCoreObj = {}), root.pdfjsSharedUtil, root.pdfjsCorePrimitives, root.pdfjsCoreCrypto, root.pdfjsCoreParser, - root.pdfjsCoreChunkedStream); + root.pdfjsCoreChunkedStream, root.pdfjsCoreColorSpace); } }(this, function (exports, sharedUtil, corePrimitives, coreCrypto, coreParser, - coreChunkedStream) { + coreChunkedStream, coreColorSpace) { var InvalidPDFException = sharedUtil.InvalidPDFException; var MissingDataException = sharedUtil.MissingDataException; @@ -61,6 +61,7 @@ var CipherTransformFactory = coreCrypto.CipherTransformFactory; var Lexer = coreParser.Lexer; var Parser = coreParser.Parser; var ChunkedStream = coreChunkedStream.ChunkedStream; +var ColorSpace = coreColorSpace.ColorSpace; var Catalog = (function CatalogClosure() { function Catalog(pdfManager, xref, pageFactory) { @@ -128,66 +129,75 @@ var Catalog = (function CatalogClosure() { return shadow(this, 'documentOutline', obj); }, readDocumentOutline: function Catalog_readDocumentOutline() { - var xref = this.xref; var obj = this.catDict.get('Outlines'); + if (!isDict(obj)) { + return null; + } + obj = obj.getRaw('First'); + if (!isRef(obj)) { + return null; + } var root = { items: [] }; - if (isDict(obj)) { - obj = obj.getRaw('First'); - 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.put(obj); - while (queue.length > 0) { - var i = queue.shift(); - var outlineDict = xref.fetchIfRef(i.obj); - if (outlineDict === null) { - continue; - } - if (!outlineDict.has('Title')) { - error('Invalid outline item'); - } - var actionDict = outlineDict.get('A'), dest = null, url = null; - if (actionDict) { - var destEntry = actionDict.get('D'); - if (destEntry) { - dest = destEntry; - } else { - var uriEntry = actionDict.get('URI'); - if (isString(uriEntry) && isValidUrl(uriEntry, false)) { - url = uriEntry; - } - } - } else if (outlineDict.has('Dest')) { - dest = outlineDict.getRaw('Dest'); - if (isName(dest)) { - dest = dest.name; - } - } - var title = outlineDict.get('Title'); - var outlineItem = { - dest: dest, - url: url, - title: stringToPDFString(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.getRaw('First'); - if (isRef(obj) && !processed.has(obj)) { - queue.push({obj: obj, parent: outlineItem}); - processed.put(obj); - } - obj = outlineDict.getRaw('Next'); - if (isRef(obj) && !processed.has(obj)) { - queue.push({obj: obj, parent: i.parent}); - processed.put(obj); + var queue = [{obj: obj, parent: root}]; + // To avoid recursion, keep track of the already processed items. + var processed = new RefSet(); + processed.put(obj); + var xref = this.xref, blackColor = new Uint8Array(3); + + while (queue.length > 0) { + var i = queue.shift(); + var outlineDict = xref.fetchIfRef(i.obj); + if (outlineDict === null) { + continue; + } + assert(outlineDict.has('Title'), 'Invalid outline item'); + + var actionDict = outlineDict.get('A'), dest = null, url = null; + if (actionDict) { + var destEntry = actionDict.get('D'); + if (destEntry) { + dest = destEntry; + } else { + var uriEntry = actionDict.get('URI'); + if (isString(uriEntry) && isValidUrl(uriEntry, false)) { + url = uriEntry; } } + } else if (outlineDict.has('Dest')) { + dest = outlineDict.getRaw('Dest'); + if (isName(dest)) { + dest = dest.name; + } + } + var title = outlineDict.get('Title'); + var flags = outlineDict.get('F') || 0; + + var color = outlineDict.get('C'), rgbColor = blackColor; + // We only need to parse the color when it's valid, and non-default. + if (isArray(color) && color.length === 3 && + (color[0] !== 0 || color[1] !== 0 || color[2] !== 0)) { + rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0); + } + var outlineItem = { + dest: dest, + url: url, + title: stringToPDFString(title), + color: rgbColor, + count: outlineDict.get('Count'), + bold: !!(flags & 2), + italic: !!(flags & 1), + items: [] + }; + i.parent.items.push(outlineItem); + obj = outlineDict.getRaw('First'); + if (isRef(obj) && !processed.has(obj)) { + queue.push({obj: obj, parent: outlineItem}); + processed.put(obj); + } + obj = outlineDict.getRaw('Next'); + if (isRef(obj) && !processed.has(obj)) { + queue.push({obj: obj, parent: i.parent}); + processed.put(obj); } } return (root.items.length > 0 ? root.items : null); diff --git a/src/display/api.js b/src/display/api.js index 246bdf038..a9b243256 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -721,7 +721,7 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { * title: string, * bold: boolean, * italic: boolean, - * color: rgb array, + * color: rgb Uint8Array, * dest: dest obj, * url: string, * items: array of more items like this diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 14a76bde2..94f8e270c 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -430,6 +430,19 @@ describe('api', function() { loadingTask.destroy(); }); }); + it('gets non-existent outline', function() { + var url = combineUrl(window.location.href, '../pdfs/tracemonkey.pdf'); + var loadingTask = PDFJS.getDocument(url); + + var promise = loadingTask.promise.then(function (pdfDocument) { + return pdfDocument.getOutline(); + }); + waitsForPromiseResolved(promise, function (outline) { + expect(outline).toEqual(null); + + loadingTask.destroy(); + }); + }); it('gets outline', function() { var promise = doc.getOutline(); waitsForPromiseResolved(promise, function(outline) { @@ -442,6 +455,10 @@ describe('api', function() { expect(outlineItem.dest instanceof Array).toEqual(true); expect(outlineItem.url).toEqual(null); + expect(outlineItem.bold).toEqual(true); + expect(outlineItem.italic).toEqual(false); + expect(outlineItem.color).toEqual(new Uint8Array([0, 64, 128])); + expect(outlineItem.items.length).toEqual(1); expect(outlineItem.items[0].title).toEqual('Paragraph 1.1'); }); @@ -455,9 +472,15 @@ describe('api', function() { expect(outline instanceof Array).toEqual(true); expect(outline.length).toEqual(5); - var outlineItem = outline[2]; - expect(outlineItem.dest).toEqual(null); - expect(outlineItem.url).toEqual('http://google.com'); + var outlineItemTwo = outline[2]; + expect(typeof outlineItemTwo.title).toEqual('string'); + expect(outlineItemTwo.dest).toEqual(null); + expect(outlineItemTwo.url).toEqual('http://google.com'); + + var outlineItemOne = outline[1]; + expect(outlineItemOne.bold).toEqual(false); + expect(outlineItemOne.italic).toEqual(true); + expect(outlineItemOne.color).toEqual(new Uint8Array([0, 0, 0])); loadingTask.destroy(); // Cleanup the worker. });