Merge pull request #7341 from Snuffleupagus/getDestinationHash-Array
[api-minor] Improve handling of links that are using explicit destination arrays
This commit is contained in:
commit
f97d52182a
@ -757,9 +757,17 @@ var LinkAnnotation = (function LinkAnnotationClosure() {
|
||||
if (isName(remoteDest)) {
|
||||
remoteDest = remoteDest.name;
|
||||
}
|
||||
if (isString(remoteDest) && isString(url)) {
|
||||
if (isString(url)) {
|
||||
var baseUrl = url.split('#')[0];
|
||||
url = baseUrl + '#' + remoteDest;
|
||||
if (isString(remoteDest)) {
|
||||
// In practice, a named destination may contain only a number.
|
||||
// If that happens, use the '#nameddest=' form to avoid the link
|
||||
// redirecting to a page, instead of the correct destination.
|
||||
url = baseUrl + '#' +
|
||||
(/^\d+$/.test(remoteDest) ? 'nameddest=' : '') + remoteDest;
|
||||
} else if (isArray(remoteDest)) {
|
||||
url = baseUrl + '#' + JSON.stringify(remoteDest);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The 'NewWindow' property, equal to `LinkTarget.BLANK`.
|
||||
|
@ -56,6 +56,7 @@ var isName = corePrimitives.isName;
|
||||
var isCmd = corePrimitives.isCmd;
|
||||
var isDict = corePrimitives.isDict;
|
||||
var isRef = corePrimitives.isRef;
|
||||
var isRefsEqual = corePrimitives.isRefsEqual;
|
||||
var isStream = corePrimitives.isStream;
|
||||
var CipherTransformFactory = coreCrypto.CipherTransformFactory;
|
||||
var Lexer = coreParser.Lexer;
|
||||
@ -522,7 +523,7 @@ var Catalog = (function CatalogClosure() {
|
||||
return capability.promise;
|
||||
},
|
||||
|
||||
getPageIndex: function Catalog_getPageIndex(ref) {
|
||||
getPageIndex: function Catalog_getPageIndex(pageRef) {
|
||||
// The page tree nodes have the count of all the leaves below them. To get
|
||||
// how many pages are before we just have to walk up the tree and keep
|
||||
// adding the count of siblings to the left of the node.
|
||||
@ -531,15 +532,21 @@ var Catalog = (function CatalogClosure() {
|
||||
var total = 0;
|
||||
var parentRef;
|
||||
return xref.fetchAsync(kidRef).then(function (node) {
|
||||
if (isRefsEqual(kidRef, pageRef) && !isDict(node, 'Page') &&
|
||||
!(isDict(node) && !node.has('Type') && node.has('Contents'))) {
|
||||
throw new Error('The reference does not point to a /Page Dict.');
|
||||
}
|
||||
if (!node) {
|
||||
return null;
|
||||
}
|
||||
assert(isDict(node), 'node must be a Dict.');
|
||||
parentRef = node.getRaw('Parent');
|
||||
return node.getAsync('Parent');
|
||||
}).then(function (parent) {
|
||||
if (!parent) {
|
||||
return null;
|
||||
}
|
||||
assert(isDict(parent), 'parent must be a Dict.');
|
||||
return parent.getAsync('Kids');
|
||||
}).then(function (kids) {
|
||||
if (!kids) {
|
||||
@ -549,7 +556,7 @@ var Catalog = (function CatalogClosure() {
|
||||
var found = false;
|
||||
for (var i = 0; i < kids.length; i++) {
|
||||
var kid = kids[i];
|
||||
assert(isRef(kid), 'kids must be a ref');
|
||||
assert(isRef(kid), 'kid must be a Ref.');
|
||||
if (kid.num === kidRef.num) {
|
||||
found = true;
|
||||
break;
|
||||
@ -585,7 +592,7 @@ var Catalog = (function CatalogClosure() {
|
||||
});
|
||||
}
|
||||
|
||||
return next(ref);
|
||||
return next(pageRef);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -290,6 +290,10 @@ function isRef(v) {
|
||||
return v instanceof Ref;
|
||||
}
|
||||
|
||||
function isRefsEqual(v1, v2) {
|
||||
return v1.num === v2.num && v1.gen === v2.gen;
|
||||
}
|
||||
|
||||
function isStream(v) {
|
||||
return typeof v === 'object' && v !== null && v.getBytes !== undefined;
|
||||
}
|
||||
@ -304,5 +308,6 @@ exports.isCmd = isCmd;
|
||||
exports.isDict = isDict;
|
||||
exports.isName = isName;
|
||||
exports.isRef = isRef;
|
||||
exports.isRefsEqual = isRefsEqual;
|
||||
exports.isStream = isStream;
|
||||
}));
|
||||
|
@ -1683,7 +1683,12 @@ var WorkerTransport = (function WorkerTransportClosure() {
|
||||
},
|
||||
|
||||
getPageIndex: function WorkerTransport_getPageIndexByRef(ref) {
|
||||
return this.messageHandler.sendWithPromise('GetPageIndex', { ref: ref });
|
||||
return this.messageHandler.sendWithPromise('GetPageIndex', { ref: ref }).
|
||||
then(function (pageIndex) {
|
||||
return pageIndex;
|
||||
}, function (reason) {
|
||||
return Promise.reject(new Error(reason));
|
||||
});
|
||||
},
|
||||
|
||||
getAnnotations: function WorkerTransport_getAnnotations(pageIndex, intent) {
|
||||
|
@ -268,8 +268,9 @@ describe('Annotation layer', function() {
|
||||
var actionDict = new Dict();
|
||||
actionDict.set('Type', Name.get('Action'));
|
||||
actionDict.set('S', Name.get('GoToR'));
|
||||
actionDict.set('F', '../../0021/002156/215675E.pdf');
|
||||
actionDict.set('D', '15');
|
||||
actionDict.set('F', '../../0013/001346/134685E.pdf');
|
||||
actionDict.set('D', '4.3');
|
||||
actionDict.set('NewWindow', true);
|
||||
|
||||
var annotationDict = new Dict();
|
||||
annotationDict.set('Type', Name.get('Annot'));
|
||||
@ -283,7 +284,58 @@ describe('Annotation layer', function() {
|
||||
var data = annotation.data;
|
||||
expect(data.annotationType).toEqual(AnnotationType.LINK);
|
||||
|
||||
expect(data.url).toBeUndefined();
|
||||
expect(data.url).toBeUndefined(); // ../../0013/001346/134685E.pdf#4.3
|
||||
expect(data.dest).toBeUndefined();
|
||||
expect(data.newWindow).toEqual(true);
|
||||
});
|
||||
|
||||
it('should correctly parse a GoToR action, with named destination',
|
||||
function() {
|
||||
var actionDict = new Dict();
|
||||
actionDict.set('Type', Name.get('Action'));
|
||||
actionDict.set('S', Name.get('GoToR'));
|
||||
actionDict.set('F', 'http://www.example.com/test.pdf');
|
||||
actionDict.set('D', '15');
|
||||
|
||||
var annotationDict = new Dict();
|
||||
annotationDict.set('Type', Name.get('Annot'));
|
||||
annotationDict.set('Subtype', Name.get('Link'));
|
||||
annotationDict.set('A', actionDict);
|
||||
|
||||
var xrefMock = new XrefMock([annotationDict]);
|
||||
var annotationRef = new Ref(495, 0);
|
||||
|
||||
var annotation = annotationFactory.create(xrefMock, annotationRef);
|
||||
var data = annotation.data;
|
||||
expect(data.annotationType).toEqual(AnnotationType.LINK);
|
||||
|
||||
expect(data.url).toEqual('http://www.example.com/test.pdf#nameddest=15');
|
||||
expect(data.dest).toBeUndefined();
|
||||
expect(data.newWindow).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should correctly parse a GoToR action, with explicit destination array',
|
||||
function() {
|
||||
var actionDict = new Dict();
|
||||
actionDict.set('Type', Name.get('Action'));
|
||||
actionDict.set('S', Name.get('GoToR'));
|
||||
actionDict.set('F', 'http://www.example.com/test.pdf');
|
||||
actionDict.set('D', [14, Name.get('XYZ'), null, 298.043, null]);
|
||||
|
||||
var annotationDict = new Dict();
|
||||
annotationDict.set('Type', Name.get('Annot'));
|
||||
annotationDict.set('Subtype', Name.get('Link'));
|
||||
annotationDict.set('A', actionDict);
|
||||
|
||||
var xrefMock = new XrefMock([annotationDict]);
|
||||
var annotationRef = new Ref(489, 0);
|
||||
|
||||
var annotation = annotationFactory.create(xrefMock, annotationRef);
|
||||
var data = annotation.data;
|
||||
expect(data.annotationType).toEqual(AnnotationType.LINK);
|
||||
|
||||
expect(data.url).toEqual('http://www.example.com/test.pdf#' +
|
||||
'[14,{"name":"XYZ"},null,298.043,null]');
|
||||
expect(data.dest).toBeUndefined();
|
||||
expect(data.newWindow).toBeFalsy();
|
||||
});
|
||||
|
@ -360,6 +360,16 @@ describe('api', function() {
|
||||
done.fail(reason);
|
||||
});
|
||||
});
|
||||
it('gets invalid page index', function (done) {
|
||||
var ref = { num: 3, gen: 0 }; // Reference to a font dictionary.
|
||||
var promise = doc.getPageIndex(ref);
|
||||
promise.then(function () {
|
||||
done.fail('shall fail for invalid page reference.');
|
||||
}, function (data) {
|
||||
expect(data instanceof Error).toEqual(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('gets destinations, from /Dests dictionary', function(done) {
|
||||
var promise = doc.getDestinations();
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* globals expect, it, describe, beforeEach, Name, Dict, Ref, RefSet, Cmd,
|
||||
jasmine, isDict */
|
||||
jasmine, isDict, isRefsEqual */
|
||||
|
||||
'use strict';
|
||||
|
||||
@ -158,4 +158,18 @@ describe('primitives', function() {
|
||||
expect(isDict(dict, 'Page')).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isRefsEqual', function () {
|
||||
it('should handle different Refs pointing to the same object', function () {
|
||||
var ref1 = new Ref(1, 0);
|
||||
var ref2 = new Ref(1, 0);
|
||||
expect(isRefsEqual(ref1, ref2)).toEqual(true);
|
||||
});
|
||||
|
||||
it('should handle Refs pointing to different objects', function () {
|
||||
var ref1 = new Ref(1, 0);
|
||||
var ref2 = new Ref(2, 0);
|
||||
expect(isRefsEqual(ref1, ref2)).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -857,6 +857,7 @@ var PDFViewerApplication = {
|
||||
pdfViewer.setDocument(pdfDocument);
|
||||
var firstPagePromise = pdfViewer.firstPagePromise;
|
||||
var pagesPromise = pdfViewer.pagesPromise;
|
||||
var onePageRendered = pdfViewer.onePageRendered;
|
||||
|
||||
this.pageRotation = 0;
|
||||
|
||||
@ -962,9 +963,8 @@ var PDFViewerApplication = {
|
||||
}
|
||||
});
|
||||
|
||||
// outline depends on pagesRefMap
|
||||
var promises = [pagesPromise, this.animationStartedPromise];
|
||||
Promise.all(promises).then(function() {
|
||||
Promise.all([onePageRendered, this.animationStartedPromise]).then(
|
||||
function() {
|
||||
pdfDocument.getOutline().then(function(outline) {
|
||||
self.pdfOutlineViewer.render({ outline: outline });
|
||||
});
|
||||
|
@ -29,6 +29,11 @@
|
||||
|
||||
var parseQueryString = uiUtils.parseQueryString;
|
||||
|
||||
var PageNumberRegExp = /^\d+$/;
|
||||
function isPageNumber(str) {
|
||||
return PageNumberRegExp.test(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} PDFLinkServiceOptions
|
||||
* @property {EventBus} eventBus - The application event bus.
|
||||
@ -40,7 +45,7 @@ var parseQueryString = uiUtils.parseQueryString;
|
||||
* @class
|
||||
* @implements {IPDFLinkService}
|
||||
*/
|
||||
var PDFLinkService = (function () {
|
||||
var PDFLinkService = (function PDFLinkServiceClosure() {
|
||||
/**
|
||||
* @constructs PDFLinkService
|
||||
* @param {PDFLinkServiceOptions} options
|
||||
@ -100,7 +105,7 @@ var PDFLinkService = (function () {
|
||||
var self = this;
|
||||
|
||||
var goToDestination = function(destRef) {
|
||||
// dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
|
||||
// dest array looks like that: <page-ref> </XYZ|/FitXXX> <args..>
|
||||
var pageNumber = destRef instanceof Object ?
|
||||
self._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
|
||||
(destRef + 1);
|
||||
@ -150,30 +155,15 @@ var PDFLinkService = (function () {
|
||||
*/
|
||||
getDestinationHash: function PDFLinkService_getDestinationHash(dest) {
|
||||
if (typeof dest === 'string') {
|
||||
return this.getAnchorUrl('#' + escape(dest));
|
||||
// In practice, a named destination may contain only a number.
|
||||
// If that happens, use the '#nameddest=' form to avoid the link
|
||||
// redirecting to a page, instead of the correct destination.
|
||||
return this.getAnchorUrl(
|
||||
'#' + (isPageNumber(dest) ? 'nameddest=' : '') + escape(dest));
|
||||
}
|
||||
if (dest instanceof Array) {
|
||||
var destRef = dest[0]; // see navigateTo method for dest format
|
||||
var pageNumber = destRef instanceof Object ?
|
||||
this._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
|
||||
(destRef + 1);
|
||||
if (pageNumber) {
|
||||
var pdfOpenParams = this.getAnchorUrl('#page=' + pageNumber);
|
||||
var destKind = dest[1];
|
||||
if (typeof destKind === 'object' && 'name' in destKind &&
|
||||
destKind.name === 'XYZ') {
|
||||
var scale = (dest[4] || this.pdfViewer.currentScaleValue);
|
||||
var scaleNumber = parseFloat(scale);
|
||||
if (scaleNumber) {
|
||||
scale = scaleNumber * 100;
|
||||
}
|
||||
pdfOpenParams += '&zoom=' + scale;
|
||||
if (dest[2] || dest[3]) {
|
||||
pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
|
||||
}
|
||||
}
|
||||
return pdfOpenParams;
|
||||
}
|
||||
var str = JSON.stringify(dest);
|
||||
return this.getAnchorUrl('#' + escape(str));
|
||||
}
|
||||
return this.getAnchorUrl('');
|
||||
},
|
||||
@ -192,6 +182,7 @@ var PDFLinkService = (function () {
|
||||
* @param {string} hash
|
||||
*/
|
||||
setHash: function PDFLinkService_setHash(hash) {
|
||||
var pageNumber, dest;
|
||||
if (hash.indexOf('=') >= 0) {
|
||||
var params = parseQueryString(hash);
|
||||
if ('search' in params) {
|
||||
@ -209,7 +200,6 @@ var PDFLinkService = (function () {
|
||||
this.navigateTo(params.nameddest);
|
||||
return;
|
||||
}
|
||||
var pageNumber, dest;
|
||||
if ('page' in params) {
|
||||
pageNumber = (params.page | 0) || 1;
|
||||
}
|
||||
@ -259,13 +249,23 @@ var PDFLinkService = (function () {
|
||||
mode: params.pagemode
|
||||
});
|
||||
}
|
||||
} else if (/^\d+$/.test(hash)) { // page number
|
||||
this.page = hash;
|
||||
} else { // named destination
|
||||
if (this.pdfHistory) {
|
||||
this.pdfHistory.updateNextHashParam(unescape(hash));
|
||||
} else if (isPageNumber(hash)) { // Page number.
|
||||
this.page = hash | 0;
|
||||
} else { // Named (or explicit) destination.
|
||||
dest = unescape(hash);
|
||||
try {
|
||||
dest = JSON.parse(dest);
|
||||
} catch (ex) {}
|
||||
|
||||
if (typeof dest === 'string' || isValidExplicitDestination(dest)) {
|
||||
if (this.pdfHistory) {
|
||||
this.pdfHistory.updateNextHashParam(dest);
|
||||
}
|
||||
this.navigateTo(dest);
|
||||
return;
|
||||
}
|
||||
this.navigateTo(unescape(hash));
|
||||
console.error('PDFLinkService_setHash: \'' + unescape(hash) +
|
||||
'\' is not a valid destination.');
|
||||
}
|
||||
},
|
||||
|
||||
@ -323,6 +323,60 @@ var PDFLinkService = (function () {
|
||||
}
|
||||
};
|
||||
|
||||
function isValidExplicitDestination(dest) {
|
||||
if (!(dest instanceof Array)) {
|
||||
return false;
|
||||
}
|
||||
var destLength = dest.length, allowNull = true;
|
||||
if (destLength < 2) {
|
||||
return false;
|
||||
}
|
||||
var page = dest[0];
|
||||
if (!(typeof page === 'object' &&
|
||||
typeof page.num === 'number' && (page.num | 0) === page.num &&
|
||||
typeof page.gen === 'number' && (page.gen | 0) === page.gen) &&
|
||||
!(typeof page === 'number' && (page | 0) === page && page >= 0)) {
|
||||
return false;
|
||||
}
|
||||
var zoom = dest[1];
|
||||
if (!(typeof zoom === 'object' && typeof zoom.name === 'string')) {
|
||||
return false;
|
||||
}
|
||||
switch (zoom.name) {
|
||||
case 'XYZ':
|
||||
if (destLength !== 5) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'Fit':
|
||||
case 'FitB':
|
||||
return destLength === 2;
|
||||
case 'FitH':
|
||||
case 'FitBH':
|
||||
case 'FitV':
|
||||
case 'FitBV':
|
||||
if (destLength !== 3) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'FitR':
|
||||
if (destLength !== 6) {
|
||||
return false;
|
||||
}
|
||||
allowNull = false;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
for (var i = 2; i < destLength; i++) {
|
||||
var param = dest[i];
|
||||
if (!(typeof param === 'number' || (allowNull && param === null))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return PDFLinkService;
|
||||
})();
|
||||
|
||||
|
@ -587,6 +587,8 @@ var PDFViewer = (function pdfViewer() {
|
||||
scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
|
||||
break;
|
||||
default:
|
||||
console.error('PDFViewer_scrollPageIntoView: \'' + dest[1].name +
|
||||
'\' is not a valid destination type.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user