Move parsing of destination dictionaries to a helper function

This not only reduces code duplication, but it also allow us to easily support the same kind of URLs we currently do for Link annotations in the Outline as well.
This commit is contained in:
Jonas Jenwald 2016-09-30 16:08:03 +02:00
parent 6906623462
commit e64bc1fd13
4 changed files with 126 additions and 117 deletions

View File

@ -38,14 +38,11 @@ var AnnotationFlag = sharedUtil.AnnotationFlag;
var AnnotationType = sharedUtil.AnnotationType;
var OPS = sharedUtil.OPS;
var Util = sharedUtil.Util;
var isBool = sharedUtil.isBool;
var isString = sharedUtil.isString;
var isArray = sharedUtil.isArray;
var isInt = sharedUtil.isInt;
var isValidUrl = sharedUtil.isValidUrl;
var stringToBytes = sharedUtil.stringToBytes;
var stringToPDFString = sharedUtil.stringToPDFString;
var stringToUTF8String = sharedUtil.stringToUTF8String;
var warn = sharedUtil.warn;
var Dict = corePrimitives.Dict;
var isDict = corePrimitives.isDict;
@ -53,6 +50,7 @@ var isName = corePrimitives.isName;
var isRef = corePrimitives.isRef;
var Stream = coreStream.Stream;
var ColorSpace = coreColorSpace.ColorSpace;
var Catalog = coreObj.Catalog;
var ObjectLoader = coreObj.ObjectLoader;
var FileSpec = coreObj.FileSpec;
var OperatorList = coreEvaluator.OperatorList;
@ -842,103 +840,13 @@ var LinkAnnotation = (function LinkAnnotationClosure() {
function LinkAnnotation(params) {
Annotation.call(this, params);
var dict = params.dict;
var data = this.data;
data.annotationType = AnnotationType.LINK;
var action = dict.get('A'), url, dest;
if (action && isDict(action)) {
var linkType = action.get('S').name;
switch (linkType) {
case 'URI':
url = action.get('URI');
if (isName(url)) {
// Some bad PDFs do not put parentheses around relative URLs.
url = '/' + url.name;
} else if (url) {
url = addDefaultProtocolToUrl(url);
}
// TODO: pdf spec mentions urls can be relative to a Base
// entry in the dictionary.
break;
case 'GoTo':
dest = action.get('D');
break;
case 'GoToR':
var urlDict = action.get('F');
if (isDict(urlDict)) {
// We assume that we found a FileSpec dictionary
// and fetch the URL without checking any further.
url = urlDict.get('F') || null;
} else if (isString(urlDict)) {
url = urlDict;
}
// NOTE: the destination is relative to the *remote* document.
var remoteDest = action.get('D');
if (remoteDest) {
if (isName(remoteDest)) {
remoteDest = remoteDest.name;
}
if (isString(url)) {
var baseUrl = url.split('#')[0];
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`.
var newWindow = action.get('NewWindow');
if (isBool(newWindow)) {
data.newWindow = newWindow;
}
break;
case 'Named':
data.action = action.get('N').name;
break;
default:
warn('unrecognized link type: ' + linkType);
}
} else if (dict.has('Dest')) { // Simple destination link.
dest = dict.get('Dest');
}
if (url) {
if (isValidUrl(url, /* allowRelative = */ false)) {
data.url = tryConvertUrlEncoding(url);
}
}
if (dest) {
data.dest = isName(dest) ? dest.name : dest;
}
}
// Lets URLs beginning with 'www.' default to using the 'http://' protocol.
function addDefaultProtocolToUrl(url) {
if (isString(url) && url.indexOf('www.') === 0) {
return ('http://' + url);
}
return url;
}
function tryConvertUrlEncoding(url) {
// According to ISO 32000-1:2008, section 12.6.4.7, URIs should be encoded
// in 7-bit ASCII. Some bad PDFs use UTF-8 encoding, see Bugzilla 1122280.
try {
return stringToUTF8String(url);
} catch (e) {
return url;
}
Catalog.parseDestDictionary({
destDict: params.dict,
resultObj: data,
});
}
Util.inherit(LinkAnnotation, Annotation, {});

View File

@ -41,6 +41,7 @@ var createPromiseCapability = sharedUtil.createPromiseCapability;
var error = sharedUtil.error;
var info = sharedUtil.info;
var isArray = sharedUtil.isArray;
var isBool = sharedUtil.isBool;
var isInt = sharedUtil.isInt;
var isString = sharedUtil.isString;
var shadow = sharedUtil.shadow;
@ -152,23 +153,11 @@ var Catalog = (function CatalogClosure() {
}
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 data = { url: null, dest: null, };
Catalog.parseDestDictionary({
destDict: outlineDict,
resultObj: data,
});
var title = outlineDict.get('Title');
var flags = outlineDict.get('F') || 0;
@ -179,8 +168,9 @@ var Catalog = (function CatalogClosure() {
rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0);
}
var outlineItem = {
dest: dest,
url: url,
dest: data.dest,
url: data.url,
newWindow: data.newWindow,
title: stringToPDFString(title),
color: rgbColor,
count: outlineDict.get('Count'),
@ -595,6 +585,111 @@ var Catalog = (function CatalogClosure() {
}
};
/**
* Helper function used to parse the contents of destination dictionaries.
* @param {Dict} destDict - The dictionary containing the destination.
* @param {Object} resultObj - The object where the parsed destination
* properties will be placed.
*/
Catalog.parseDestDictionary = function Catalog_parseDestDictionary(params) {
// Lets URLs beginning with 'www.' default to using the 'http://' protocol.
function addDefaultProtocolToUrl(url) {
if (isString(url) && url.indexOf('www.') === 0) {
return ('http://' + url);
}
return url;
}
// According to ISO 32000-1:2008, section 12.6.4.7, URIs should be encoded
// in 7-bit ASCII. Some bad PDFs use UTF-8 encoding, see Bugzilla 1122280.
function tryConvertUrlEncoding(url) {
try {
return stringToUTF8String(url);
} catch (e) {
return url;
}
}
var destDict = params.destDict;
var resultObj = params.resultObj;
var action = destDict.get('A'), url, dest;
if (action && isDict(action)) {
var linkType = action.get('S').name;
switch (linkType) {
case 'URI':
url = action.get('URI');
if (isName(url)) {
// Some bad PDFs do not put parentheses around relative URLs.
url = '/' + url.name;
} else if (url) {
url = addDefaultProtocolToUrl(url);
}
// TODO: pdf spec mentions urls can be relative to a Base
// entry in the dictionary.
break;
case 'GoTo':
dest = action.get('D');
break;
case 'GoToR':
var urlDict = action.get('F');
if (isDict(urlDict)) {
// We assume that we found a FileSpec dictionary
// and fetch the URL without checking any further.
url = urlDict.get('F') || null;
} else if (isString(urlDict)) {
url = urlDict;
}
// NOTE: the destination is relative to the *remote* document.
var remoteDest = action.get('D');
if (remoteDest) {
if (isName(remoteDest)) {
remoteDest = remoteDest.name;
}
if (isString(url)) {
var baseUrl = url.split('#')[0];
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`.
var newWindow = action.get('NewWindow');
if (isBool(newWindow)) {
resultObj.newWindow = newWindow;
}
break;
case 'Named':
resultObj.action = action.get('N').name;
break;
default:
warn('Catalog_parseDestDictionary: Unrecognized link type "' +
linkType + '".');
}
} else if (destDict.has('Dest')) { // Simple destination link.
dest = destDict.get('Dest');
}
if (url) {
if (isValidUrl(url, /* allowRelative = */ false)) {
resultObj.url = tryConvertUrlEncoding(url);
}
}
if (dest) {
resultObj.dest = isName(dest) ? dest.name : dest;
}
};
return Catalog;
})();

View File

@ -644,6 +644,7 @@ describe('api', function() {
expect(typeof outlineItemTwo.title).toEqual('string');
expect(outlineItemTwo.dest).toEqual(null);
expect(outlineItemTwo.url).toEqual('http://google.com');
expect(outlineItemTwo.newWindow).toBeUndefined();
var outlineItemOne = outline[1];
expect(outlineItemOne.bold).toEqual(false);

View File

@ -26,6 +26,8 @@
}
}(this, function (exports, pdfjsLib) {
var PDFJS = pdfjsLib.PDFJS;
var DEFAULT_TITLE = '\u2013';
/**
@ -82,7 +84,10 @@ var PDFOutlineViewer = (function PDFOutlineViewerClosure() {
*/
_bindLink: function PDFOutlineViewer_bindLink(element, item) {
if (item.url) {
pdfjsLib.addLinkAttributes(element, { url: item.url });
pdfjsLib.addLinkAttributes(element, {
url: item.url,
target: (item.newWindow ? PDFJS.LinkTarget.BLANK : undefined),
});
return;
}
var linkService = this.linkService;