Newer
Older
13001
13002
13003
13004
13005
13006
13007
13008
13009
13010
13011
13012
13013
13014
13015
13016
13017
13018
13019
13020
13021
13022
13023
13024
13025
13026
13027
13028
13029
13030
13031
13032
13033
13034
13035
13036
13037
13038
13039
13040
13041
13042
13043
13044
13045
13046
13047
13048
13049
13050
13051
13052
13053
13054
13055
13056
13057
13058
13059
13060
13061
13062
13063
13064
13065
13066
13067
13068
13069
13070
13071
13072
13073
13074
13075
13076
13077
13078
13079
13080
13081
13082
13083
13084
13085
13086
13087
13088
13089
13090
13091
13092
13093
13094
13095
13096
13097
13098
13099
13100
13101
13102
13103
13104
13105
13106
13107
13108
13109
13110
13111
13112
13113
13114
13115
13116
13117
13118
13119
13120
13121
13122
13123
13124
13125
13126
13127
13128
13129
13130
13131
13132
13133
13134
13135
13136
13137
13138
13139
13140
13141
13142
13143
13144
13145
13146
13147
13148
13149
13150
13151
13152
13153
13154
13155
13156
13157
13158
13159
13160
13161
13162
13163
13164
13165
13166
13167
13168
13169
13170
13171
13172
13173
13174
13175
13176
13177
13178
13179
13180
13181
13182
13183
13184
13185
13186
13187
13188
13189
13190
13191
13192
13193
13194
13195
13196
13197
13198
13199
13200
13201
13202
13203
13204
13205
13206
13207
13208
13209
13210
13211
13212
13213
13214
13215
13216
13217
13218
13219
13220
13221
13222
13223
13224
13225
13226
13227
13228
13229
13230
13231
13232
13233
13234
13235
13236
13237
13238
13239
13240
13241
13242
13243
13244
13245
13246
13247
13248
13249
13250
13251
13252
13253
13254
13255
13256
13257
13258
13259
13260
13261
13262
13263
13264
13265
13266
13267
13268
13269
13270
13271
13272
13273
13274
13275
13276
13277
13278
13279
13280
13281
13282
13283
13284
13285
13286
13287
13288
13289
13290
13291
13292
13293
13294
13295
13296
13297
13298
13299
13300
13301
13302
13303
13304
13305
13306
13307
13308
13309
13310
13311
13312
13313
13314
13315
13316
13317
13318
13319
13320
13321
13322
13323
13324
13325
13326
13327
13328
13329
13330
13331
13332
13333
13334
13335
13336
13337
13338
13339
13340
13341
13342
13343
13344
13345
13346
13347
13348
13349
13350
13351
13352
13353
13354
13355
13356
13357
13358
13359
13360
13361
13362
13363
13364
13365
13366
13367
13368
13369
13370
13371
13372
13373
13374
13375
13376
13377
13378
13379
13380
13381
13382
13383
13384
13385
13386
13387
13388
13389
13390
13391
13392
13393
13394
13395
13396
13397
13398
13399
13400
13401
13402
13403
13404
13405
13406
13407
13408
13409
13410
13411
13412
13413
13414
13415
13416
13417
13418
13419
13420
13421
13422
13423
13424
13425
13426
13427
13428
13429
13430
13431
13432
13433
13434
13435
13436
13437
13438
13439
13440
13441
13442
13443
13444
13445
13446
13447
13448
13449
13450
13451
13452
13453
13454
13455
13456
13457
13458
13459
13460
13461
13462
13463
13464
13465
13466
13467
13468
13469
13470
13471
13472
13473
13474
13475
13476
13477
13478
13479
13480
13481
13482
13483
13484
13485
13486
13487
13488
13489
13490
13491
13492
13493
13494
13495
13496
13497
13498
13499
13500
13501
13502
13503
13504
13505
13506
13507
13508
13509
13510
13511
13512
13513
13514
13515
13516
13517
13518
13519
13520
13521
13522
13523
13524
13525
13526
13527
13528
13529
13530
13531
13532
13533
13534
13535
13536
13537
13538
13539
13540
13541
13542
13543
13544
13545
13546
13547
13548
13549
13550
13551
13552
13553
13554
13555
13556
13557
13558
13559
13560
13561
13562
13563
13564
13565
13566
13567
13568
13569
13570
13571
13572
13573
13574
13575
13576
13577
13578
13579
13580
13581
13582
13583
13584
13585
13586
13587
13588
13589
13590
13591
13592
13593
13594
13595
13596
13597
13598
13599
13600
13601
13602
13603
13604
13605
13606
13607
13608
13609
13610
13611
13612
13613
13614
13615
13616
13617
13618
13619
13620
13621
13622
13623
13624
13625
13626
13627
13628
13629
13630
13631
13632
13633
13634
13635
13636
13637
13638
13639
13640
13641
13642
13643
13644
13645
13646
13647
13648
13649
13650
13651
13652
13653
13654
13655
13656
13657
13658
13659
13660
13661
13662
13663
13664
13665
13666
13667
13668
13669
13670
13671
13672
13673
13674
13675
13676
13677
13678
13679
13680
13681
13682
13683
13684
13685
13686
13687
13688
13689
13690
13691
13692
13693
13694
13695
13696
13697
13698
13699
13700
13701
13702
13703
13704
13705
13706
13707
13708
13709
13710
13711
13712
13713
13714
13715
13716
13717
13718
13719
13720
13721
13722
13723
13724
13725
13726
13727
13728
13729
13730
13731
13732
13733
13734
13735
13736
13737
13738
13739
13740
13741
13742
13743
13744
13745
13746
13747
13748
13749
13750
13751
13752
13753
13754
13755
13756
13757
13758
13759
13760
13761
13762
13763
13764
13765
13766
13767
13768
13769
13770
13771
13772
13773
13774
13775
13776
13777
13778
13779
13780
13781
13782
13783
13784
13785
13786
13787
13788
13789
13790
13791
13792
13793
13794
13795
13796
13797
13798
13799
13800
13801
13802
13803
13804
13805
13806
13807
13808
13809
13810
13811
13812
13813
13814
13815
13816
13817
13818
13819
13820
13821
13822
13823
13824
13825
13826
13827
13828
13829
13830
13831
13832
13833
13834
13835
13836
13837
13838
13839
13840
13841
13842
13843
13844
13845
13846
13847
13848
13849
13850
13851
13852
13853
13854
13855
13856
13857
13858
13859
13860
13861
13862
13863
13864
13865
13866
13867
13868
13869
13870
13871
13872
13873
13874
13875
13876
13877
13878
13879
13880
13881
13882
13883
13884
13885
13886
13887
13888
13889
13890
13891
13892
13893
13894
13895
13896
13897
13898
13899
13900
13901
13902
13903
13904
13905
13906
13907
13908
13909
13910
13911
13912
13913
13914
13915
13916
13917
13918
13919
13920
13921
13922
13923
13924
13925
13926
13927
13928
13929
13930
13931
13932
13933
13934
13935
13936
13937
13938
13939
13940
13941
13942
13943
13944
13945
13946
13947
13948
13949
13950
13951
13952
13953
13954
13955
13956
13957
13958
13959
13960
13961
13962
13963
13964
13965
13966
13967
13968
13969
13970
13971
13972
13973
13974
13975
13976
13977
13978
13979
13980
13981
13982
13983
13984
13985
13986
13987
13988
13989
13990
13991
13992
13993
13994
13995
13996
13997
13998
13999
14000
for (var name in names) {
if (!names.hasOwnProperty(name)) {
continue;
}
// We don't really use the JavaScript right now. This code is
// defensive so we don't cause errors on document load.
var jsDict = names[name];
if (isDict(jsDict)) {
appendIfJavaScriptDict(jsDict);
}
}
}
// Append OpenAction actions to javaScript array
var openactionDict = this.catDict.get("OpenAction");
if (isDict(openactionDict, "Action")) {
var actionType = openactionDict.get("S");
if (isName(actionType) && actionType.name === "Named") {
// The named Print action is not a part of the PDF 1.7 specification,
// but is supported by many PDF readers/writers (including Adobe's).
var action = openactionDict.get("N");
if (isName(action) && action.name === "Print") {
javaScript.push("print({});");
}
} else {
appendIfJavaScriptDict(openactionDict);
}
}
return shadow(this, "javaScript", javaScript);
},
cleanup: function Catalog_cleanup() {
var promises = [];
this.fontCache.forEach(function(promise) {
promises.push(promise);
});
return Promise.all(promises).then(
function(translatedFonts) {
for (var i = 0, ii = translatedFonts.length; i < ii; i++) {
var font = translatedFonts[i].dict;
delete font.translated;
}
this.fontCache.clear();
}.bind(this)
);
},
getPage: function Catalog_getPage(pageIndex) {
if (!(pageIndex in this.pagePromises)) {
this.pagePromises[pageIndex] = this.getPageDict(pageIndex).then(
function(a) {
var dict = a[0];
var ref = a[1];
return this.pageFactory.createPage(
pageIndex,
dict,
ref,
this.fontCache
);
}.bind(this)
);
}
return this.pagePromises[pageIndex];
},
getPageDict: function Catalog_getPageDict(pageIndex) {
var capability = createPromiseCapability();
var nodesToVisit = [this.catDict.getRaw("Pages")];
var currentPageIndex = 0;
var xref = this.xref;
var checkAllKids = false;
function next() {
while (nodesToVisit.length) {
var currentNode = nodesToVisit.pop();
if (isRef(currentNode)) {
xref.fetchAsync(currentNode).then(function(obj) {
if (isDict(obj, "Page") || (isDict(obj) && !obj.has("Kids"))) {
if (pageIndex === currentPageIndex) {
capability.resolve([obj, currentNode]);
} else {
currentPageIndex++;
next();
}
return;
}
nodesToVisit.push(obj);
next();
}, capability.reject);
return;
}
// Must be a child page dictionary.
assert(
isDict(currentNode),
"page dictionary kid reference points to wrong type of object"
);
var count = currentNode.get("Count");
// If the current node doesn't have any children, avoid getting stuck
// in an empty node further down in the tree (see issue5644.pdf).
if (count === 0) {
checkAllKids = true;
}
// Skip nodes where the page can't be.
if (currentPageIndex + count <= pageIndex) {
currentPageIndex += count;
continue;
}
var kids = currentNode.get("Kids");
assert(
isArray(kids),
"page dictionary kids object is not an array"
);
if (!checkAllKids && count === kids.length) {
// Nodes that don't have the page have been skipped and this is the
// bottom of the tree which means the page requested must be a
// descendant of this pages node. Ideally we would just resolve the
// promise with the page ref here, but there is the case where more
// pages nodes could link to single a page (see issue 3666 pdf). To
// handle this push it back on the queue so if it is a pages node it
// will be descended into.
nodesToVisit = [kids[pageIndex - currentPageIndex]];
currentPageIndex = pageIndex;
continue;
} else {
for (var last = kids.length - 1; last >= 0; last--) {
nodesToVisit.push(kids[last]);
}
}
}
capability.reject("Page index " + pageIndex + " not found.");
}
next();
return capability.promise;
},
getPageIndex: function Catalog_getPageIndex(ref) {
// 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.
var xref = this.xref;
function pagesBeforeRef(kidRef) {
var total = 0;
var parentRef;
return xref
.fetchAsync(kidRef)
.then(function(node) {
if (!node) {
return null;
}
parentRef = node.getRaw("Parent");
return node.getAsync("Parent");
})
.then(function(parent) {
if (!parent) {
return null;
}
return parent.getAsync("Kids");
})
.then(function(kids) {
if (!kids) {
return null;
}
var kidPromises = [];
var found = false;
for (var i = 0; i < kids.length; i++) {
var kid = kids[i];
assert(isRef(kid), "kids must be a ref");
if (kid.num === kidRef.num) {
found = true;
break;
}
kidPromises.push(
xref.fetchAsync(kid).then(function(kid) {
if (kid.has("Count")) {
var count = kid.get("Count");
total += count;
} else {
// page leaf node
total++;
}
})
);
}
if (!found) {
error("kid ref not found in parents kids");
}
return Promise.all(kidPromises).then(function() {
return [total, parentRef];
});
});
}
var total = 0;
function next(ref) {
return pagesBeforeRef(ref).then(function(args) {
if (!args) {
return total;
}
var count = args[0];
var parentRef = args[1];
total += count;
return next(parentRef);
});
}
return next(ref);
}
};
return Catalog;
})();
var XRef = (function XRefClosure() {
function XRef(stream, password) {
this.stream = stream;
this.entries = [];
this.xrefstms = {};
// prepare the XRef cache
this.cache = [];
this.password = password;
this.stats = {
streamTypes: [],
fontTypes: []
};
}
XRef.prototype = {
setStartXRef: function XRef_setStartXRef(startXRef) {
// Store the starting positions of xref tables as we process them
// so we can recover from missing data errors
this.startXRefQueue = [startXRef];
this.xrefBlocks = [startXRef];
},
parse: function XRef_parse(recoveryMode) {
var trailerDict;
if (!recoveryMode) {
trailerDict = this.readXRef();
} else {
warn("Indexing all PDF objects");
trailerDict = this.indexObjects();
}
trailerDict.assignXref(this);
this.trailer = trailerDict;
var encrypt = trailerDict.get("Encrypt");
if (encrypt) {
var ids = trailerDict.get("ID");
var fileId = ids && ids.length ? ids[0] : "";
this.encrypt = new CipherTransformFactory(
encrypt,
fileId,
this.password
);
}
// get the root dictionary (catalog) object
if (!(this.root = trailerDict.get("Root"))) {
error("Invalid root reference");
}
},
processXRefTable: function XRef_processXRefTable(parser) {
if (!("tableState" in this)) {
// Stores state of the table as we process it so we can resume
// from middle of table in case of missing data error
this.tableState = {
entryNum: 0,
streamPos: parser.lexer.stream.pos,
parserBuf1: parser.buf1,
parserBuf2: parser.buf2
};
}
var obj = this.readXRefTable(parser);
// Sanity check
if (!isCmd(obj, "trailer")) {
error("Invalid XRef table: could not find trailer dictionary");
}
// Read trailer dictionary, e.g.
// trailer
// << /Size 22
// /Root 20R
// /Info 10R
// /ID [ <81b14aafa313db63dbd6f981e49f94f4> ]
// >>
// The parser goes through the entire stream << ... >> and provides
// a getter interface for the key-value table
var dict = parser.getObj();
// The pdflib PDF generator can generate a nested trailer dictionary
if (!isDict(dict) && dict.dict) {
dict = dict.dict;
}
if (!isDict(dict)) {
error("Invalid XRef table: could not parse trailer dictionary");
}
delete this.tableState;
return dict;
},
readXRefTable: function XRef_readXRefTable(parser) {
// Example of cross-reference table:
// xref
// 0 1 <-- subsection header (first obj #, obj count)
// 0000000000 65535 f <-- actual object (offset, generation #, f/n)
// 23 2 <-- subsection header ... and so on ...
// 0000025518 00002 n
// 0000025635 00000 n
// trailer
// ...
var stream = parser.lexer.stream;
var tableState = this.tableState;
stream.pos = tableState.streamPos;
parser.buf1 = tableState.parserBuf1;
parser.buf2 = tableState.parserBuf2;
// Outer loop is over subsection headers
var obj;
while (true) {
if (
!("firstEntryNum" in tableState) ||
!("entryCount" in tableState)
) {
if (isCmd((obj = parser.getObj()), "trailer")) {
break;
}
tableState.firstEntryNum = obj;
tableState.entryCount = parser.getObj();
}
var first = tableState.firstEntryNum;
var count = tableState.entryCount;
if (!isInt(first) || !isInt(count)) {
error("Invalid XRef table: wrong types in subsection header");
}
// Inner loop is over objects themselves
for (var i = tableState.entryNum; i < count; i++) {
tableState.streamPos = stream.pos;
tableState.entryNum = i;
tableState.parserBuf1 = parser.buf1;
tableState.parserBuf2 = parser.buf2;
var entry = {};
entry.offset = parser.getObj();
entry.gen = parser.getObj();
var type = parser.getObj();
if (isCmd(type, "f")) {
entry.free = true;
} else if (isCmd(type, "n")) {
entry.uncompressed = true;
}
// Validate entry obj
if (
!isInt(entry.offset) ||
!isInt(entry.gen) ||
!(entry.free || entry.uncompressed)
) {
error(
"Invalid entry in XRef subsection: " + first + ", " + count
);
}
if (!this.entries[i + first]) {
this.entries[i + first] = entry;
}
}
tableState.entryNum = 0;
tableState.streamPos = stream.pos;
tableState.parserBuf1 = parser.buf1;
tableState.parserBuf2 = parser.buf2;
delete tableState.firstEntryNum;
delete tableState.entryCount;
}
// Per issue 3248: hp scanners generate bad XRef
if (first === 1 && this.entries[1] && this.entries[1].free) {
// shifting the entries
this.entries.shift();
}
// Sanity check: as per spec, first object must be free
if (this.entries[0] && !this.entries[0].free) {
error("Invalid XRef table: unexpected first object");
}
return obj;
},
processXRefStream: function XRef_processXRefStream(stream) {
if (!("streamState" in this)) {
// Stores state of the stream as we process it so we can resume
// from middle of stream in case of missing data error
var streamParameters = stream.dict;
var byteWidths = streamParameters.get("W");
var range = streamParameters.get("Index");
if (!range) {
range = [0, streamParameters.get("Size")];
}
this.streamState = {
entryRanges: range,
byteWidths: byteWidths,
entryNum: 0,
streamPos: stream.pos
};
}
this.readXRefStream(stream);
delete this.streamState;
return stream.dict;
},
readXRefStream: function XRef_readXRefStream(stream) {
var i, j;
var streamState = this.streamState;
stream.pos = streamState.streamPos;
var byteWidths = streamState.byteWidths;
var typeFieldWidth = byteWidths[0];
var offsetFieldWidth = byteWidths[1];
var generationFieldWidth = byteWidths[2];
var entryRanges = streamState.entryRanges;
while (entryRanges.length > 0) {
var first = entryRanges[0];
var n = entryRanges[1];
if (!isInt(first) || !isInt(n)) {
error("Invalid XRef range fields: " + first + ", " + n);
}
if (
!isInt(typeFieldWidth) ||
!isInt(offsetFieldWidth) ||
!isInt(generationFieldWidth)
) {
error("Invalid XRef entry fields length: " + first + ", " + n);
}
for (i = streamState.entryNum; i < n; ++i) {
streamState.entryNum = i;
streamState.streamPos = stream.pos;
var type = 0,
offset = 0,
generation = 0;
for (j = 0; j < typeFieldWidth; ++j) {
type = (type << 8) | stream.getByte();
}
// if type field is absent, its default value is 1
if (typeFieldWidth === 0) {
type = 1;
}
for (j = 0; j < offsetFieldWidth; ++j) {
offset = (offset << 8) | stream.getByte();
}
for (j = 0; j < generationFieldWidth; ++j) {
generation = (generation << 8) | stream.getByte();
}
var entry = {};
entry.offset = offset;
entry.gen = generation;
switch (type) {
case 0:
entry.free = true;
break;
case 1:
entry.uncompressed = true;
break;
case 2:
break;
default:
error("Invalid XRef entry type: " + type);
}
if (!this.entries[first + i]) {
this.entries[first + i] = entry;
}
}
streamState.entryNum = 0;
streamState.streamPos = stream.pos;
entryRanges.splice(0, 2);
}
},
indexObjects: function XRef_indexObjects() {
// Simple scan through the PDF content to find objects,
// trailers and XRef streams.
var TAB = 0x9,
LF = 0xa,
CR = 0xd,
SPACE = 0x20;
var PERCENT = 0x25,
LT = 0x3c;
function readToken(data, offset) {
var token = "",
ch = data[offset];
while (ch !== LF && ch !== CR && ch !== LT) {
if (++offset >= data.length) {
break;
}
token += String.fromCharCode(ch);
ch = data[offset];
}
return token;
}
function skipUntil(data, offset, what) {
var length = what.length,
dataLength = data.length;
var skipped = 0;
// finding byte sequence
while (offset < dataLength) {
var i = 0;
while (i < length && data[offset + i] === what[i]) {
++i;
}
if (i >= length) {
break; // sequence found
}
offset++;
skipped++;
}
return skipped;
}
var objRegExp = /^(\d+)\s+(\d+)\s+obj\b/;
var trailerBytes = new Uint8Array([116, 114, 97, 105, 108, 101, 114]);
var startxrefBytes = new Uint8Array([
115,
116,
97,
114,
116,
120,
114,
101,
102
]);
var endobjBytes = new Uint8Array([101, 110, 100, 111, 98, 106]);
var xrefBytes = new Uint8Array([47, 88, 82, 101, 102]);
// Clear out any existing entries, since they may be bogus.
this.entries.length = 0;
var stream = this.stream;
stream.pos = 0;
var buffer = stream.getBytes();
var position = stream.start,
length = buffer.length;
var trailers = [],
xrefStms = [];
while (position < length) {
var ch = buffer[position];
if (ch === TAB || ch === LF || ch === CR || ch === SPACE) {
++position;
continue;
}
if (ch === PERCENT) {
// %-comment
do {
++position;
if (position >= length) {
break;
}
ch = buffer[position];
} while (ch !== LF && ch !== CR);
continue;
}
var token = readToken(buffer, position);
var m;
if (
token.indexOf("xref") === 0 &&
(token.length === 4 || /\s/.test(token[4]))
) {
position += skipUntil(buffer, position, trailerBytes);
trailers.push(position);
position += skipUntil(buffer, position, startxrefBytes);
} else if ((m = objRegExp.exec(token))) {
if (typeof this.entries[m[1]] === "undefined") {
this.entries[m[1]] = {
offset: position - stream.start,
gen: m[2] | 0,
uncompressed: true
};
}
var contentLength = skipUntil(buffer, position, endobjBytes) + 7;
var content = buffer.subarray(position, position + contentLength);
// checking XRef stream suspect
// (it shall have '/XRef' and next char is not a letter)
var xrefTagOffset = skipUntil(content, 0, xrefBytes);
if (
xrefTagOffset < contentLength &&
content[xrefTagOffset + 5] < 64
) {
xrefStms.push(position - stream.start);
this.xrefstms[position - stream.start] = 1; // Avoid recursion
}
position += contentLength;
} else if (
token.indexOf("trailer") === 0 &&
(token.length === 7 || /\s/.test(token[7]))
) {
trailers.push(position);
position += skipUntil(buffer, position, startxrefBytes);
} else {
position += token.length + 1;
}
}
// reading XRef streams
var i, ii;
for (i = 0, ii = xrefStms.length; i < ii; ++i) {
this.startXRefQueue.push(xrefStms[i]);
this.xrefBlocks.push(xrefStms[i]);
this.readXRef(/* recoveryMode */ true);
}
// finding main trailer
var dict;
for (i = 0, ii = trailers.length; i < ii; ++i) {
stream.pos = trailers[i];
var parser = new Parser(new Lexer(stream), true, this);
var obj = parser.getObj();
if (!isCmd(obj, "trailer")) {
continue;
}
// read the trailer dictionary
if (!isDict((dict = parser.getObj()))) {
continue;
}
// taking the first one with 'ID'
if (dict.has("ID")) {
return dict;
}
}
// no tailer with 'ID', taking last one (if exists)
if (dict) {
return dict;
}
// nothing helps
// calling error() would reject worker with an UnknownErrorException.
throw new InvalidPDFException("Invalid PDF structure");
},
readXRef: function XRef_readXRef(recoveryMode) {
var stream = this.stream;
try {
while (this.startXRefQueue.length) {
var startXRef = this.startXRefQueue[0];
stream.pos = startXRef + stream.start;
var parser = new Parser(new Lexer(stream), true, this);
var obj = parser.getObj();
var dict;
// Get dictionary
if (isCmd(obj, "xref")) {
// Parse end-of-file XRef
dict = this.processXRefTable(parser);
if (!this.topDict) {
this.topDict = dict;
}
// Recursively get other XRefs 'XRefStm', if any
obj = dict.get("XRefStm");
if (isInt(obj)) {
var pos = obj;
// ignore previously loaded xref streams
// (possible infinite recursion)
if (!(pos in this.xrefstms)) {
this.xrefstms[pos] = 1;
this.startXRefQueue.push(pos);
this.xrefBlocks.push(pos);
}
}
} else if (isInt(obj)) {
// Parse in-stream XRef
if (
!isInt(parser.getObj()) ||
!isCmd(parser.getObj(), "obj") ||
!isStream((obj = parser.getObj()))
) {
error("Invalid XRef stream");
}
dict = this.processXRefStream(obj);
if (!this.topDict) {
this.topDict = dict;
}
if (!dict) {
error("Failed to read XRef stream");
}
} else {
error("Invalid XRef stream header");
}
// Recursively get previous dictionary, if any
obj = dict.get("Prev");
if (isInt(obj)) {
this.startXRefQueue.push(obj);
this.xrefBlocks.push(obj);
} else if (isRef(obj)) {
// The spec says Prev must not be a reference, i.e. "/Prev NNN"
// This is a fallback for non-compliant PDFs, i.e. "/Prev NNN 0 R"
this.startXRefQueue.push(obj.num);
this.xrefBlocks.push(obj.num);
}
this.xrefBlocks.push(stream.pos);
this.startXRefQueue.shift();
}
return this.topDict;
} catch (e) {
if (e instanceof MissingDataException) {
throw e;
}
info("(while reading XRef): " + e);
}
if (recoveryMode) {
return;
}
throw new XRefParseException();
},
getEntry: function XRef_getEntry(i) {
var xrefEntry = this.entries[i];
if (xrefEntry && !xrefEntry.free && xrefEntry.offset) {
return xrefEntry;
}
return null;
},
fetchIfRef: function XRef_fetchIfRef(obj) {
if (!isRef(obj)) {
return obj;
}
return this.fetch(obj);
},
fetch: function XRef_fetch(ref, suppressEncryption) {
assert(isRef(ref), "ref object is not a reference");
var num = ref.num;
if (num in this.cache) {
var cacheEntry = this.cache[num];
return cacheEntry;
}
var xrefEntry = this.getEntry(num);
// the referenced entry can be free
if (xrefEntry === null) {
return (this.cache[num] = null);
}
if (xrefEntry.uncompressed) {
xrefEntry = this.fetchUncompressed(
ref,
xrefEntry,
suppressEncryption
);
} else {
xrefEntry = this.fetchCompressed(xrefEntry, suppressEncryption);
}
if (isDict(xrefEntry)) {
xrefEntry.objId = ref.toString();
} else if (isStream(xrefEntry)) {
xrefEntry.dict.objId = ref.toString();
}
return xrefEntry;
},
fetchUncompressed: function XRef_fetchUncompressed(
ref,
xrefEntry,
suppressEncryption
) {
var gen = ref.gen;
var num = ref.num;
if (xrefEntry.gen !== gen) {
error("inconsistent generation in XRef");
}
var stream = this.stream.makeSubStream(
xrefEntry.offset + this.stream.start
);
var parser = new Parser(new Lexer(stream), true, this);
var obj1 = parser.getObj();
var obj2 = parser.getObj();
var obj3 = parser.getObj();
if (
!isInt(obj1) ||
parseInt(obj1, 10) !== num ||
!isInt(obj2) ||
parseInt(obj2, 10) !== gen ||
!isCmd(obj3)
) {
error("bad XRef entry");
}
if (!isCmd(obj3, "obj")) {
// some bad PDFs use "obj1234" and really mean 1234
if (obj3.cmd.indexOf("obj") === 0) {
num = parseInt(obj3.cmd.substring(3), 10);
if (!isNaN(num)) {
return num;
}
}
error("bad XRef entry");
}
if (this.encrypt && !suppressEncryption) {
xrefEntry = parser.getObj(
this.encrypt.createCipherTransform(num, gen)
);
} else {
xrefEntry = parser.getObj();
}
if (!isStream(xrefEntry)) {
this.cache[num] = xrefEntry;
}
return xrefEntry;
},
fetchCompressed: function XRef_fetchCompressed(
xrefEntry,
suppressEncryption
) {
var tableOffset = xrefEntry.offset;
var stream = this.fetch(new Ref(tableOffset, 0));
if (!isStream(stream)) {
error("bad ObjStm stream");
}
var first = stream.dict.get("First");
var n = stream.dict.get("N");
if (!isInt(first) || !isInt(n)) {
error("invalid first and n parameters for ObjStm stream");
}
var parser = new Parser(new Lexer(stream), false, this);
parser.allowStreams = true;
var i,
entries = [],
num,
nums = [];
// read the object numbers to populate cache
for (i = 0; i < n; ++i) {
num = parser.getObj();
if (!isInt(num)) {
error("invalid object number in the ObjStm stream: " + num);
}
nums.push(num);
var offset = parser.getObj();
if (!isInt(offset)) {
error("invalid object offset in the ObjStm stream: " + offset);
}
}
// read stream objects for cache
for (i = 0; i < n; ++i) {
entries.push(parser.getObj());
num = nums[i];
var entry = this.entries[num];
if (entry && entry.offset === tableOffset && entry.gen === i) {
this.cache[num] = entries[i];
}
}
xrefEntry = entries[xrefEntry.gen];
if (xrefEntry === undefined) {
error("bad XRef entry for compressed object");
}
return xrefEntry;
},
fetchIfRefAsync: function XRef_fetchIfRefAsync(obj) {
if (!isRef(obj)) {
return Promise.resolve(obj);
}
return this.fetchAsync(obj);
},
fetchAsync: function XRef_fetchAsync(ref, suppressEncryption) {
var streamManager = this.stream.manager;
var xref = this;
return new Promise(function tryFetch(resolve, reject) {
try {
resolve(xref.fetch(ref, suppressEncryption));
} catch (e) {
if (e instanceof MissingDataException) {
streamManager.requestRange(e.begin, e.end).then(function() {
tryFetch(resolve, reject);
}, reject);
return;
}
reject(e);
}
});
},
getCatalogObj: function XRef_getCatalogObj() {
return this.root;
}
};
return XRef;
})();
/**
* A NameTree is like a Dict but has some advantageous properties, see the
* spec (7.9.6) for more details.
* TODO: implement all the Dict functions and make this more efficent.
*/
var NameTree = (function NameTreeClosure() {
function NameTree(root, xref) {
this.root = root;
this.xref = xref;
}
NameTree.prototype = {
getAll: function NameTree_getAll() {
var dict = {};
if (!this.root) {
return dict;
}
var xref = this.xref;
// reading name tree
var processed = new RefSet();
processed.put(this.root);
var queue = [this.root];
while (queue.length > 0) {
var i, n;
var obj = xref.fetchIfRef(queue.shift());
if (!isDict(obj)) {
continue;
}
if (obj.has("Kids")) {
var kids = obj.get("Kids");
for (i = 0, n = kids.length; i < n; i++) {
var kid = kids[i];
if (processed.has(kid)) {
error("invalid destinations");
}
queue.push(kid);
processed.put(kid);
}
continue;
}
var names = obj.get("Names");
if (names) {
for (i = 0, n = names.length; i < n; i += 2) {
dict[xref.fetchIfRef(names[i])] = xref.fetchIfRef(names[i + 1]);
}
}
}
return dict;
},
get: function NameTree_get(destinationId) {
if (!this.root) {
return null;
}
var xref = this.xref;
var kidsOrNames = xref.fetchIfRef(this.root);
var loopCount = 0;
var MAX_NAMES_LEVELS = 10;
var l, r, m;
// Perform a binary search to quickly find the entry that
// contains the named destination we are looking for.
while (kidsOrNames.has("Kids")) {
loopCount++;
if (loopCount > MAX_NAMES_LEVELS) {
warn(
"Search depth limit for named destionations has been reached."
);
return null;
}
var kids = kidsOrNames.get("Kids");
if (!isArray(kids)) {
return null;
}
l = 0;
r = kids.length - 1;
while (l <= r) {
m = (l + r) >> 1;
var kid = xref.fetchIfRef(kids[m]);
var limits = kid.get("Limits");
if (destinationId < xref.fetchIfRef(limits[0])) {
r = m - 1;
} else if (destinationId > xref.fetchIfRef(limits[1])) {
l = m + 1;
} else {