Browse Source

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.
Jonas Jenwald 9 years ago
parent
commit
e64bc1fd13
  1. 102
      src/core/annotation.js
  2. 133
      src/core/obj.js
  3. 1
      test/unit/api_spec.js
  4. 7
      web/pdf_outline_viewer.js

102
src/core/annotation.js

@ -38,14 +38,11 @@ var AnnotationFlag = sharedUtil.AnnotationFlag;
var AnnotationType = sharedUtil.AnnotationType; var AnnotationType = sharedUtil.AnnotationType;
var OPS = sharedUtil.OPS; var OPS = sharedUtil.OPS;
var Util = sharedUtil.Util; var Util = sharedUtil.Util;
var isBool = sharedUtil.isBool;
var isString = sharedUtil.isString; var isString = sharedUtil.isString;
var isArray = sharedUtil.isArray; var isArray = sharedUtil.isArray;
var isInt = sharedUtil.isInt; var isInt = sharedUtil.isInt;
var isValidUrl = sharedUtil.isValidUrl;
var stringToBytes = sharedUtil.stringToBytes; var stringToBytes = sharedUtil.stringToBytes;
var stringToPDFString = sharedUtil.stringToPDFString; var stringToPDFString = sharedUtil.stringToPDFString;
var stringToUTF8String = sharedUtil.stringToUTF8String;
var warn = sharedUtil.warn; var warn = sharedUtil.warn;
var Dict = corePrimitives.Dict; var Dict = corePrimitives.Dict;
var isDict = corePrimitives.isDict; var isDict = corePrimitives.isDict;
@ -53,6 +50,7 @@ var isName = corePrimitives.isName;
var isRef = corePrimitives.isRef; var isRef = corePrimitives.isRef;
var Stream = coreStream.Stream; var Stream = coreStream.Stream;
var ColorSpace = coreColorSpace.ColorSpace; var ColorSpace = coreColorSpace.ColorSpace;
var Catalog = coreObj.Catalog;
var ObjectLoader = coreObj.ObjectLoader; var ObjectLoader = coreObj.ObjectLoader;
var FileSpec = coreObj.FileSpec; var FileSpec = coreObj.FileSpec;
var OperatorList = coreEvaluator.OperatorList; var OperatorList = coreEvaluator.OperatorList;
@ -842,103 +840,13 @@ var LinkAnnotation = (function LinkAnnotationClosure() {
function LinkAnnotation(params) { function LinkAnnotation(params) {
Annotation.call(this, params); Annotation.call(this, params);
var dict = params.dict;
var data = this.data; var data = this.data;
data.annotationType = AnnotationType.LINK; data.annotationType = AnnotationType.LINK;
var action = dict.get('A'), url, dest; Catalog.parseDestDictionary({
if (action && isDict(action)) { destDict: params.dict,
var linkType = action.get('S').name; resultObj: data,
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;
}
} }
Util.inherit(LinkAnnotation, Annotation, {}); Util.inherit(LinkAnnotation, Annotation, {});

133
src/core/obj.js

@ -41,6 +41,7 @@ var createPromiseCapability = sharedUtil.createPromiseCapability;
var error = sharedUtil.error; var error = sharedUtil.error;
var info = sharedUtil.info; var info = sharedUtil.info;
var isArray = sharedUtil.isArray; var isArray = sharedUtil.isArray;
var isBool = sharedUtil.isBool;
var isInt = sharedUtil.isInt; var isInt = sharedUtil.isInt;
var isString = sharedUtil.isString; var isString = sharedUtil.isString;
var shadow = sharedUtil.shadow; var shadow = sharedUtil.shadow;
@ -152,23 +153,11 @@ var Catalog = (function CatalogClosure() {
} }
assert(outlineDict.has('Title'), 'Invalid outline item'); assert(outlineDict.has('Title'), 'Invalid outline item');
var actionDict = outlineDict.get('A'), dest = null, url = null; var data = { url: null, dest: null, };
if (actionDict) { Catalog.parseDestDictionary({
var destEntry = actionDict.get('D'); destDict: outlineDict,
if (destEntry) { resultObj: data,
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 title = outlineDict.get('Title');
var flags = outlineDict.get('F') || 0; var flags = outlineDict.get('F') || 0;
@ -179,8 +168,9 @@ var Catalog = (function CatalogClosure() {
rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0); rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0);
} }
var outlineItem = { var outlineItem = {
dest: dest, dest: data.dest,
url: url, url: data.url,
newWindow: data.newWindow,
title: stringToPDFString(title), title: stringToPDFString(title),
color: rgbColor, color: rgbColor,
count: outlineDict.get('Count'), 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; return Catalog;
})(); })();

1
test/unit/api_spec.js

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

7
web/pdf_outline_viewer.js

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

Loading…
Cancel
Save