Skip to content

Commit 85cf906

Browse files
committed
[api-minor] Add support for PageLabels in the API
1 parent 8ad1895 commit 85cf906

File tree

7 files changed

+177
-0
lines changed

7 files changed

+177
-0
lines changed

src/core/obj.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,96 @@ var Catalog = (function CatalogClosure() {
263263
}
264264
return dest;
265265
},
266+
267+
get pageLabels() {
268+
var obj = null;
269+
try {
270+
obj = this.readPageLabels();
271+
} catch (ex) {
272+
if (ex instanceof MissingDataException) {
273+
throw ex;
274+
}
275+
warn('Unable to read page labels.');
276+
}
277+
return shadow(this, 'pageLabels', obj);
278+
},
279+
readPageLabels: function Catalog_readPageLabels() {
280+
var obj = this.catDict.getRaw('PageLabels');
281+
if (!obj) {
282+
return null;
283+
}
284+
var pageLabels = new Array(this.numPages);
285+
var style = null;
286+
var prefix = '';
287+
var start = 1;
288+
289+
var numberTree = new NumberTree(obj, this.xref);
290+
var nums = numberTree.getAll();
291+
var currentLabel = '', currentIndex = 1;
292+
293+
for (var i = 0, ii = this.numPages; i < ii; i++) {
294+
if (nums.hasOwnProperty(i)) {
295+
var labelDict = nums[i];
296+
assert(isDict(labelDict), 'The PageLabel is not a dictionary.');
297+
298+
var type = labelDict.get('Type');
299+
assert(!type || (isName(type) && type.name === 'PageLabel'),
300+
'Invalid type in PageLabel dictionary.');
301+
302+
var s = labelDict.get('S');
303+
assert(!s || isName(s), 'Invalid style in PageLabel dictionary.');
304+
style = (s ? s.name : null);
305+
306+
prefix = labelDict.get('P') || '';
307+
assert(isString(prefix), 'Invalid prefix in PageLabel dictionary.');
308+
309+
start = labelDict.get('St') || 1;
310+
assert(isInt(start), 'Invalid start in PageLabel dictionary.');
311+
currentIndex = start;
312+
}
313+
314+
switch (style) {
315+
case 'D':
316+
currentLabel = currentIndex;
317+
break;
318+
case 'R':
319+
case 'r':
320+
currentLabel = Util.toRoman(currentIndex, style === 'r');
321+
break;
322+
case 'A':
323+
case 'a':
324+
var LIMIT = 26; // Use only the characters A--Z, or a--z.
325+
var A_UPPER_CASE = 0x41, A_LOWER_CASE = 0x61;
326+
327+
var baseCharCode = (style === 'a' ? A_LOWER_CASE : A_UPPER_CASE);
328+
var letterIndex = currentIndex - 1;
329+
var character = String.fromCharCode(baseCharCode +
330+
(letterIndex % LIMIT));
331+
var charBuf = [];
332+
for (var j = 0, jj = (letterIndex / LIMIT) | 0; j <= jj; j++) {
333+
charBuf.push(character);
334+
}
335+
currentLabel = charBuf.join('');
336+
break;
337+
default:
338+
assert(!style,
339+
'Invalid style "' + style + '" in PageLabel dictionary.');
340+
}
341+
pageLabels[i] = prefix + currentLabel;
342+
343+
currentLabel = '';
344+
currentIndex++;
345+
}
346+
347+
// Ignore PageLabels if they correspond to standard page numbering.
348+
for (i = 0, ii = this.numPages; i < ii; i++) {
349+
if (pageLabels[i] !== (i + 1).toString()) {
350+
break;
351+
}
352+
}
353+
return (i === ii ? [] : pageLabels);
354+
},
355+
266356
get attachments() {
267357
var xref = this.xref;
268358
var attachments = null, nameTreeRef;

src/core/worker.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,12 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
451451
}
452452
);
453453

454+
handler.on('GetPageLabels',
455+
function wphSetupGetPageLabels(data) {
456+
return pdfManager.ensureCatalog('pageLabels');
457+
}
458+
);
459+
454460
handler.on('GetAttachments',
455461
function wphSetupGetAttachments(data) {
456462
return pdfManager.ensureCatalog('attachments');

src/display/api.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,16 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
690690
getDestination: function PDFDocumentProxy_getDestination(id) {
691691
return this.transport.getDestination(id);
692692
},
693+
/**
694+
* @return {Promise} A promise that is resolved with: an Array containing
695+
* the pageLabels that correspond to the pageIndexes; or null, when no
696+
* pageLabels are present in the PDF file.
697+
* NOTE: If the pageLabels are all identical to standard page numbering,
698+
* i.e. [1, 2, 3, ...], the promise is resolved with an empty Array.
699+
*/
700+
getPageLabels: function PDFDocumentProxy_getPageLabels() {
701+
return this.transport.getPageLabels();
702+
},
693703
/**
694704
* @return {Promise} A promise that is resolved with a lookup table for
695705
* mapping named attachments to their content.
@@ -1804,6 +1814,10 @@ var WorkerTransport = (function WorkerTransportClosure() {
18041814
return this.messageHandler.sendWithPromise('GetDestination', { id: id });
18051815
},
18061816

1817+
getPageLabels: function WorkerTransport_getPageLabels() {
1818+
return this.messageHandler.sendWithPromise('GetPageLabels', null);
1819+
},
1820+
18071821
getAttachments: function WorkerTransport_getAttachments() {
18081822
return this.messageHandler.sendWithPromise('GetAttachments', null);
18091823
},

src/shared/util.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,42 @@ var Util = PDFJS.Util = (function UtilClosure() {
808808
return num < 0 ? -1 : 1;
809809
};
810810

811+
var ROMAN_NUMBER_MAP = [
812+
'', 'C', 'CC', 'CCC', 'CD', 'D', 'DC', 'DCC', 'DCCC', 'CM',
813+
'', 'X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC',
814+
'', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'
815+
];
816+
/**
817+
* Converts positive integers to (upper case) Roman numerals.
818+
* @param {integer} number - The number that should be converted.
819+
* @param {boolean} lowerCase - Indicates if the result should be converted
820+
* to lower case letters. The default is false.
821+
* @return {string} The resulting Roman number.
822+
*/
823+
Util.toRoman = function Util_toRoman(number, lowerCase) {
824+
assert(isInt(number) && number > 0,
825+
'The number should be a positive integer.');
826+
var pos, romanBuf = [];
827+
// Thousands
828+
while (number >= 1000) {
829+
number -= 1000;
830+
romanBuf.push('M');
831+
}
832+
// Hundreds
833+
pos = (number / 100) | 0;
834+
number %= 100;
835+
romanBuf.push(ROMAN_NUMBER_MAP[pos]);
836+
// Tens
837+
pos = (number / 10) | 0;
838+
number %= 10;
839+
romanBuf.push(ROMAN_NUMBER_MAP[10 + pos]);
840+
// Ones
841+
romanBuf.push(ROMAN_NUMBER_MAP[20 + number]);
842+
843+
var romanStr = romanBuf.join('');
844+
return (lowerCase ? romanStr.toLowerCase() : romanStr);
845+
};
846+
811847
Util.appendToArray = function Util_appendToArray(arr1, arr2) {
812848
Array.prototype.push.apply(arr1, arr2);
813849
};

test/pdfs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
!arial_unicode_ab_cidfont.pdf
2525
!arial_unicode_en_cidfont.pdf
2626
!asciihexdecode.pdf
27+
!bug793632.pdf
2728
!bug1020858.pdf
2829
!bug1050040.pdf
2930
!bug1200096.pdf

test/pdfs/bug793632.pdf

23 KB
Binary file not shown.

test/unit/api_spec.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,36 @@ describe('api', function() {
329329
expect(data).toEqual(null);
330330
});
331331
});
332+
it('gets non-existent page labels', function () {
333+
var promise = doc.getPageLabels();
334+
waitsForPromiseResolved(promise, function (data) {
335+
expect(data).toEqual(null);
336+
});
337+
});
338+
it('gets page labels', function () {
339+
// PageLabels with Roman/Arabic numerals.
340+
var url0 = combineUrl(window.location.href, '../pdfs/bug793632.pdf');
341+
var promise0 = PDFJS.getDocument(url0).promise.then(function (pdfDoc) {
342+
return pdfDoc.getPageLabels();
343+
});
344+
// PageLabels with only a label prefix.
345+
var url1 = combineUrl(window.location.href, '../pdfs/issue1453.pdf');
346+
var promise1 = PDFJS.getDocument(url1).promise.then(function (pdfDoc) {
347+
return pdfDoc.getPageLabels();
348+
});
349+
// PageLabels identical to standard page numbering.
350+
var url2 = combineUrl(window.location.href, '../pdfs/rotation.pdf');
351+
var promise2 = PDFJS.getDocument(url2).promise.then(function (pdfDoc) {
352+
return pdfDoc.getPageLabels();
353+
});
354+
355+
waitsForPromiseResolved(Promise.all([promise0, promise1, promise2]),
356+
function (pageLabels) {
357+
expect(pageLabels[0]).toEqual(['i', 'ii', 'iii', '1']);
358+
expect(pageLabels[1]).toEqual(['Front Page1']);
359+
expect(pageLabels[2]).toEqual([]);
360+
});
361+
});
332362
it('gets attachments', function() {
333363
var promise = doc.getAttachments();
334364
waitsForPromiseResolved(promise, function (data) {

0 commit comments

Comments
 (0)