catalog.js 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657
  1. /* Copyright 2012 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. import {
  16. collectActions,
  17. MissingDataException,
  18. PDF_VERSION_REGEXP,
  19. recoverJsURL,
  20. toRomanNumerals,
  21. XRefEntryException,
  22. } from "./core_utils.js";
  23. import {
  24. createValidAbsoluteUrl,
  25. DocumentActionEventType,
  26. FormatError,
  27. info,
  28. objectSize,
  29. PermissionFlag,
  30. shadow,
  31. stringToPDFString,
  32. stringToUTF8String,
  33. warn,
  34. } from "../shared/util.js";
  35. import {
  36. Dict,
  37. isDict,
  38. isName,
  39. isRefsEqual,
  40. Name,
  41. Ref,
  42. RefSet,
  43. RefSetCache,
  44. } from "./primitives.js";
  45. import { NameTree, NumberTree } from "./name_number_tree.js";
  46. import { BaseStream } from "./base_stream.js";
  47. import { clearGlobalCaches } from "./cleanup_helper.js";
  48. import { ColorSpace } from "./colorspace.js";
  49. import { FileSpec } from "./file_spec.js";
  50. import { GlobalImageCache } from "./image_utils.js";
  51. import { MetadataParser } from "./metadata_parser.js";
  52. import { StructTreeRoot } from "./struct_tree.js";
  53. function fetchDestination(dest) {
  54. if (dest instanceof Dict) {
  55. dest = dest.get("D");
  56. }
  57. return Array.isArray(dest) ? dest : null;
  58. }
  59. class Catalog {
  60. constructor(pdfManager, xref) {
  61. this.pdfManager = pdfManager;
  62. this.xref = xref;
  63. this._catDict = xref.getCatalogObj();
  64. if (!(this._catDict instanceof Dict)) {
  65. throw new FormatError("Catalog object is not a dictionary.");
  66. }
  67. // Given that `XRef.parse` will both fetch *and* validate the /Pages-entry,
  68. // the following call must always succeed here:
  69. this.toplevelPagesDict; // eslint-disable-line no-unused-expressions
  70. this._actualNumPages = null;
  71. this.fontCache = new RefSetCache();
  72. this.builtInCMapCache = new Map();
  73. this.standardFontDataCache = new Map();
  74. this.globalImageCache = new GlobalImageCache();
  75. this.pageKidsCountCache = new RefSetCache();
  76. this.pageIndexCache = new RefSetCache();
  77. this.nonBlendModesSet = new RefSet();
  78. }
  79. get version() {
  80. const version = this._catDict.get("Version");
  81. if (version instanceof Name) {
  82. if (PDF_VERSION_REGEXP.test(version.name)) {
  83. return shadow(this, "version", version.name);
  84. }
  85. warn(`Invalid PDF catalog version: ${version.name}`);
  86. }
  87. return shadow(this, "version", null);
  88. }
  89. get lang() {
  90. const lang = this._catDict.get("Lang");
  91. return shadow(
  92. this,
  93. "lang",
  94. typeof lang === "string" ? stringToPDFString(lang) : null
  95. );
  96. }
  97. /**
  98. * @type {boolean} `true` for pure XFA documents,
  99. * `false` for XFA Foreground documents.
  100. */
  101. get needsRendering() {
  102. const needsRendering = this._catDict.get("NeedsRendering");
  103. return shadow(
  104. this,
  105. "needsRendering",
  106. typeof needsRendering === "boolean" ? needsRendering : false
  107. );
  108. }
  109. get collection() {
  110. let collection = null;
  111. try {
  112. const obj = this._catDict.get("Collection");
  113. if (obj instanceof Dict && obj.size > 0) {
  114. collection = obj;
  115. }
  116. } catch (ex) {
  117. if (ex instanceof MissingDataException) {
  118. throw ex;
  119. }
  120. info("Cannot fetch Collection entry; assuming no collection is present.");
  121. }
  122. return shadow(this, "collection", collection);
  123. }
  124. get acroForm() {
  125. let acroForm = null;
  126. try {
  127. const obj = this._catDict.get("AcroForm");
  128. if (obj instanceof Dict && obj.size > 0) {
  129. acroForm = obj;
  130. }
  131. } catch (ex) {
  132. if (ex instanceof MissingDataException) {
  133. throw ex;
  134. }
  135. info("Cannot fetch AcroForm entry; assuming no forms are present.");
  136. }
  137. return shadow(this, "acroForm", acroForm);
  138. }
  139. get acroFormRef() {
  140. const value = this._catDict.getRaw("AcroForm");
  141. return shadow(this, "acroFormRef", value instanceof Ref ? value : null);
  142. }
  143. get metadata() {
  144. const streamRef = this._catDict.getRaw("Metadata");
  145. if (!(streamRef instanceof Ref)) {
  146. return shadow(this, "metadata", null);
  147. }
  148. let metadata = null;
  149. try {
  150. const suppressEncryption = !(
  151. this.xref.encrypt && this.xref.encrypt.encryptMetadata
  152. );
  153. const stream = this.xref.fetch(streamRef, suppressEncryption);
  154. if (stream instanceof BaseStream && stream.dict instanceof Dict) {
  155. const type = stream.dict.get("Type");
  156. const subtype = stream.dict.get("Subtype");
  157. if (isName(type, "Metadata") && isName(subtype, "XML")) {
  158. // XXX: This should examine the charset the XML document defines,
  159. // however since there are currently no real means to decode arbitrary
  160. // charsets, let's just hope that the author of the PDF was reasonable
  161. // enough to stick with the XML default charset, which is UTF-8.
  162. const data = stringToUTF8String(stream.getString());
  163. if (data) {
  164. metadata = new MetadataParser(data).serializable;
  165. }
  166. }
  167. }
  168. } catch (ex) {
  169. if (ex instanceof MissingDataException) {
  170. throw ex;
  171. }
  172. info(`Skipping invalid Metadata: "${ex}".`);
  173. }
  174. return shadow(this, "metadata", metadata);
  175. }
  176. get markInfo() {
  177. let markInfo = null;
  178. try {
  179. markInfo = this._readMarkInfo();
  180. } catch (ex) {
  181. if (ex instanceof MissingDataException) {
  182. throw ex;
  183. }
  184. warn("Unable to read mark info.");
  185. }
  186. return shadow(this, "markInfo", markInfo);
  187. }
  188. /**
  189. * @private
  190. */
  191. _readMarkInfo() {
  192. const obj = this._catDict.get("MarkInfo");
  193. if (!(obj instanceof Dict)) {
  194. return null;
  195. }
  196. const markInfo = {
  197. Marked: false,
  198. UserProperties: false,
  199. Suspects: false,
  200. };
  201. for (const key in markInfo) {
  202. const value = obj.get(key);
  203. if (typeof value === "boolean") {
  204. markInfo[key] = value;
  205. }
  206. }
  207. return markInfo;
  208. }
  209. get structTreeRoot() {
  210. let structTree = null;
  211. try {
  212. structTree = this._readStructTreeRoot();
  213. } catch (ex) {
  214. if (ex instanceof MissingDataException) {
  215. throw ex;
  216. }
  217. warn("Unable read to structTreeRoot info.");
  218. }
  219. return shadow(this, "structTreeRoot", structTree);
  220. }
  221. /**
  222. * @private
  223. */
  224. _readStructTreeRoot() {
  225. const obj = this._catDict.get("StructTreeRoot");
  226. if (!(obj instanceof Dict)) {
  227. return null;
  228. }
  229. const root = new StructTreeRoot(obj);
  230. root.init();
  231. return root;
  232. }
  233. get toplevelPagesDict() {
  234. const pagesObj = this._catDict.get("Pages");
  235. if (!(pagesObj instanceof Dict)) {
  236. throw new FormatError("Invalid top-level pages dictionary.");
  237. }
  238. return shadow(this, "toplevelPagesDict", pagesObj);
  239. }
  240. get documentOutline() {
  241. let obj = null;
  242. try {
  243. obj = this._readDocumentOutline();
  244. } catch (ex) {
  245. if (ex instanceof MissingDataException) {
  246. throw ex;
  247. }
  248. warn("Unable to read document outline.");
  249. }
  250. return shadow(this, "documentOutline", obj);
  251. }
  252. /**
  253. * @private
  254. */
  255. _readDocumentOutline() {
  256. let obj = this._catDict.get("Outlines");
  257. if (!(obj instanceof Dict)) {
  258. return null;
  259. }
  260. obj = obj.getRaw("First");
  261. if (!(obj instanceof Ref)) {
  262. return null;
  263. }
  264. const root = { items: [] };
  265. const queue = [{ obj, parent: root }];
  266. // To avoid recursion, keep track of the already processed items.
  267. const processed = new RefSet();
  268. processed.put(obj);
  269. const xref = this.xref,
  270. blackColor = new Uint8ClampedArray(3);
  271. while (queue.length > 0) {
  272. const i = queue.shift();
  273. const outlineDict = xref.fetchIfRef(i.obj);
  274. if (outlineDict === null) {
  275. continue;
  276. }
  277. if (!outlineDict.has("Title")) {
  278. throw new FormatError("Invalid outline item encountered.");
  279. }
  280. const data = { url: null, dest: null, action: null };
  281. Catalog.parseDestDictionary({
  282. destDict: outlineDict,
  283. resultObj: data,
  284. docBaseUrl: this.pdfManager.docBaseUrl,
  285. docAttachments: this.attachments,
  286. });
  287. const title = outlineDict.get("Title");
  288. const flags = outlineDict.get("F") || 0;
  289. const color = outlineDict.getArray("C");
  290. const count = outlineDict.get("Count");
  291. let rgbColor = blackColor;
  292. // We only need to parse the color when it's valid, and non-default.
  293. if (
  294. Array.isArray(color) &&
  295. color.length === 3 &&
  296. (color[0] !== 0 || color[1] !== 0 || color[2] !== 0)
  297. ) {
  298. rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0);
  299. }
  300. const outlineItem = {
  301. action: data.action,
  302. attachment: data.attachment,
  303. dest: data.dest,
  304. url: data.url,
  305. unsafeUrl: data.unsafeUrl,
  306. newWindow: data.newWindow,
  307. setOCGState: data.setOCGState,
  308. title: stringToPDFString(title),
  309. color: rgbColor,
  310. count: Number.isInteger(count) ? count : undefined,
  311. bold: !!(flags & 2),
  312. italic: !!(flags & 1),
  313. items: [],
  314. };
  315. i.parent.items.push(outlineItem);
  316. obj = outlineDict.getRaw("First");
  317. if (obj instanceof Ref && !processed.has(obj)) {
  318. queue.push({ obj, parent: outlineItem });
  319. processed.put(obj);
  320. }
  321. obj = outlineDict.getRaw("Next");
  322. if (obj instanceof Ref && !processed.has(obj)) {
  323. queue.push({ obj, parent: i.parent });
  324. processed.put(obj);
  325. }
  326. }
  327. return root.items.length > 0 ? root.items : null;
  328. }
  329. get permissions() {
  330. let permissions = null;
  331. try {
  332. permissions = this._readPermissions();
  333. } catch (ex) {
  334. if (ex instanceof MissingDataException) {
  335. throw ex;
  336. }
  337. warn("Unable to read permissions.");
  338. }
  339. return shadow(this, "permissions", permissions);
  340. }
  341. /**
  342. * @private
  343. */
  344. _readPermissions() {
  345. const encrypt = this.xref.trailer.get("Encrypt");
  346. if (!(encrypt instanceof Dict)) {
  347. return null;
  348. }
  349. let flags = encrypt.get("P");
  350. if (typeof flags !== "number") {
  351. return null;
  352. }
  353. // PDF integer objects are represented internally in signed 2's complement
  354. // form. Therefore, convert the signed decimal integer to a signed 2's
  355. // complement binary integer so we can use regular bitwise operations on it.
  356. flags += 2 ** 32;
  357. const permissions = [];
  358. for (const key in PermissionFlag) {
  359. const value = PermissionFlag[key];
  360. if (flags & value) {
  361. permissions.push(value);
  362. }
  363. }
  364. return permissions;
  365. }
  366. get optionalContentConfig() {
  367. let config = null;
  368. try {
  369. const properties = this._catDict.get("OCProperties");
  370. if (!properties) {
  371. return shadow(this, "optionalContentConfig", null);
  372. }
  373. const defaultConfig = properties.get("D");
  374. if (!defaultConfig) {
  375. return shadow(this, "optionalContentConfig", null);
  376. }
  377. const groupsData = properties.get("OCGs");
  378. if (!Array.isArray(groupsData)) {
  379. return shadow(this, "optionalContentConfig", null);
  380. }
  381. const groups = [];
  382. const groupRefs = [];
  383. // Ensure all the optional content groups are valid.
  384. for (const groupRef of groupsData) {
  385. if (!(groupRef instanceof Ref)) {
  386. continue;
  387. }
  388. groupRefs.push(groupRef);
  389. const group = this.xref.fetchIfRef(groupRef);
  390. groups.push({
  391. id: groupRef.toString(),
  392. name:
  393. typeof group.get("Name") === "string"
  394. ? stringToPDFString(group.get("Name"))
  395. : null,
  396. intent:
  397. typeof group.get("Intent") === "string"
  398. ? stringToPDFString(group.get("Intent"))
  399. : null,
  400. });
  401. }
  402. config = this._readOptionalContentConfig(defaultConfig, groupRefs);
  403. config.groups = groups;
  404. } catch (ex) {
  405. if (ex instanceof MissingDataException) {
  406. throw ex;
  407. }
  408. warn(`Unable to read optional content config: ${ex}`);
  409. }
  410. return shadow(this, "optionalContentConfig", config);
  411. }
  412. _readOptionalContentConfig(config, contentGroupRefs) {
  413. function parseOnOff(refs) {
  414. const onParsed = [];
  415. if (Array.isArray(refs)) {
  416. for (const value of refs) {
  417. if (!(value instanceof Ref)) {
  418. continue;
  419. }
  420. if (contentGroupRefs.includes(value)) {
  421. onParsed.push(value.toString());
  422. }
  423. }
  424. }
  425. return onParsed;
  426. }
  427. function parseOrder(refs, nestedLevels = 0) {
  428. if (!Array.isArray(refs)) {
  429. return null;
  430. }
  431. const order = [];
  432. for (const value of refs) {
  433. if (value instanceof Ref && contentGroupRefs.includes(value)) {
  434. parsedOrderRefs.put(value); // Handle "hidden" groups, see below.
  435. order.push(value.toString());
  436. continue;
  437. }
  438. // Handle nested /Order arrays (see e.g. issue 9462 and bug 1240641).
  439. const nestedOrder = parseNestedOrder(value, nestedLevels);
  440. if (nestedOrder) {
  441. order.push(nestedOrder);
  442. }
  443. }
  444. if (nestedLevels > 0) {
  445. return order;
  446. }
  447. const hiddenGroups = [];
  448. for (const groupRef of contentGroupRefs) {
  449. if (parsedOrderRefs.has(groupRef)) {
  450. continue;
  451. }
  452. hiddenGroups.push(groupRef.toString());
  453. }
  454. if (hiddenGroups.length) {
  455. order.push({ name: null, order: hiddenGroups });
  456. }
  457. return order;
  458. }
  459. function parseNestedOrder(ref, nestedLevels) {
  460. if (++nestedLevels > MAX_NESTED_LEVELS) {
  461. warn("parseNestedOrder - reached MAX_NESTED_LEVELS.");
  462. return null;
  463. }
  464. const value = xref.fetchIfRef(ref);
  465. if (!Array.isArray(value)) {
  466. return null;
  467. }
  468. const nestedName = xref.fetchIfRef(value[0]);
  469. if (typeof nestedName !== "string") {
  470. return null;
  471. }
  472. const nestedOrder = parseOrder(value.slice(1), nestedLevels);
  473. if (!nestedOrder || !nestedOrder.length) {
  474. return null;
  475. }
  476. return { name: stringToPDFString(nestedName), order: nestedOrder };
  477. }
  478. const xref = this.xref,
  479. parsedOrderRefs = new RefSet(),
  480. MAX_NESTED_LEVELS = 10;
  481. return {
  482. name:
  483. typeof config.get("Name") === "string"
  484. ? stringToPDFString(config.get("Name"))
  485. : null,
  486. creator:
  487. typeof config.get("Creator") === "string"
  488. ? stringToPDFString(config.get("Creator"))
  489. : null,
  490. baseState:
  491. config.get("BaseState") instanceof Name
  492. ? config.get("BaseState").name
  493. : null,
  494. on: parseOnOff(config.get("ON")),
  495. off: parseOnOff(config.get("OFF")),
  496. order: parseOrder(config.get("Order")),
  497. groups: null,
  498. };
  499. }
  500. setActualNumPages(num = null) {
  501. this._actualNumPages = num;
  502. }
  503. get hasActualNumPages() {
  504. return this._actualNumPages !== null;
  505. }
  506. get _pagesCount() {
  507. const obj = this.toplevelPagesDict.get("Count");
  508. if (!Number.isInteger(obj)) {
  509. throw new FormatError(
  510. "Page count in top-level pages dictionary is not an integer."
  511. );
  512. }
  513. return shadow(this, "_pagesCount", obj);
  514. }
  515. get numPages() {
  516. return this.hasActualNumPages ? this._actualNumPages : this._pagesCount;
  517. }
  518. get destinations() {
  519. const obj = this._readDests(),
  520. dests = Object.create(null);
  521. if (obj instanceof NameTree) {
  522. for (const [key, value] of obj.getAll()) {
  523. const dest = fetchDestination(value);
  524. if (dest) {
  525. dests[stringToPDFString(key)] = dest;
  526. }
  527. }
  528. } else if (obj instanceof Dict) {
  529. obj.forEach(function (key, value) {
  530. const dest = fetchDestination(value);
  531. if (dest) {
  532. dests[key] = dest;
  533. }
  534. });
  535. }
  536. return shadow(this, "destinations", dests);
  537. }
  538. getDestination(id) {
  539. const obj = this._readDests();
  540. if (obj instanceof NameTree) {
  541. const dest = fetchDestination(obj.get(id));
  542. if (dest) {
  543. return dest;
  544. }
  545. // Fallback to checking the *entire* NameTree, in an attempt to handle
  546. // corrupt PDF documents with out-of-order NameTrees (fixes issue 10272).
  547. const allDest = this.destinations[id];
  548. if (allDest) {
  549. warn(`Found "${id}" at an incorrect position in the NameTree.`);
  550. return allDest;
  551. }
  552. } else if (obj instanceof Dict) {
  553. const dest = fetchDestination(obj.get(id));
  554. if (dest) {
  555. return dest;
  556. }
  557. }
  558. return null;
  559. }
  560. /**
  561. * @private
  562. */
  563. _readDests() {
  564. const obj = this._catDict.get("Names");
  565. if (obj && obj.has("Dests")) {
  566. return new NameTree(obj.getRaw("Dests"), this.xref);
  567. } else if (this._catDict.has("Dests")) {
  568. // Simple destination dictionary.
  569. return this._catDict.get("Dests");
  570. }
  571. return undefined;
  572. }
  573. get pageLabels() {
  574. let obj = null;
  575. try {
  576. obj = this._readPageLabels();
  577. } catch (ex) {
  578. if (ex instanceof MissingDataException) {
  579. throw ex;
  580. }
  581. warn("Unable to read page labels.");
  582. }
  583. return shadow(this, "pageLabels", obj);
  584. }
  585. /**
  586. * @private
  587. */
  588. _readPageLabels() {
  589. const obj = this._catDict.getRaw("PageLabels");
  590. if (!obj) {
  591. return null;
  592. }
  593. const pageLabels = new Array(this.numPages);
  594. let style = null,
  595. prefix = "";
  596. const numberTree = new NumberTree(obj, this.xref);
  597. const nums = numberTree.getAll();
  598. let currentLabel = "",
  599. currentIndex = 1;
  600. for (let i = 0, ii = this.numPages; i < ii; i++) {
  601. const labelDict = nums.get(i);
  602. if (labelDict !== undefined) {
  603. if (!(labelDict instanceof Dict)) {
  604. throw new FormatError("PageLabel is not a dictionary.");
  605. }
  606. if (
  607. labelDict.has("Type") &&
  608. !isName(labelDict.get("Type"), "PageLabel")
  609. ) {
  610. throw new FormatError("Invalid type in PageLabel dictionary.");
  611. }
  612. if (labelDict.has("S")) {
  613. const s = labelDict.get("S");
  614. if (!(s instanceof Name)) {
  615. throw new FormatError("Invalid style in PageLabel dictionary.");
  616. }
  617. style = s.name;
  618. } else {
  619. style = null;
  620. }
  621. if (labelDict.has("P")) {
  622. const p = labelDict.get("P");
  623. if (typeof p !== "string") {
  624. throw new FormatError("Invalid prefix in PageLabel dictionary.");
  625. }
  626. prefix = stringToPDFString(p);
  627. } else {
  628. prefix = "";
  629. }
  630. if (labelDict.has("St")) {
  631. const st = labelDict.get("St");
  632. if (!(Number.isInteger(st) && st >= 1)) {
  633. throw new FormatError("Invalid start in PageLabel dictionary.");
  634. }
  635. currentIndex = st;
  636. } else {
  637. currentIndex = 1;
  638. }
  639. }
  640. switch (style) {
  641. case "D":
  642. currentLabel = currentIndex;
  643. break;
  644. case "R":
  645. case "r":
  646. currentLabel = toRomanNumerals(currentIndex, style === "r");
  647. break;
  648. case "A":
  649. case "a":
  650. const LIMIT = 26; // Use only the characters A-Z, or a-z.
  651. const A_UPPER_CASE = 0x41,
  652. A_LOWER_CASE = 0x61;
  653. const baseCharCode = style === "a" ? A_LOWER_CASE : A_UPPER_CASE;
  654. const letterIndex = currentIndex - 1;
  655. const character = String.fromCharCode(
  656. baseCharCode + (letterIndex % LIMIT)
  657. );
  658. currentLabel = character.repeat(Math.floor(letterIndex / LIMIT) + 1);
  659. break;
  660. default:
  661. if (style) {
  662. throw new FormatError(
  663. `Invalid style "${style}" in PageLabel dictionary.`
  664. );
  665. }
  666. currentLabel = "";
  667. }
  668. pageLabels[i] = prefix + currentLabel;
  669. currentIndex++;
  670. }
  671. return pageLabels;
  672. }
  673. get pageLayout() {
  674. const obj = this._catDict.get("PageLayout");
  675. // Purposely use a non-standard default value, rather than 'SinglePage', to
  676. // allow differentiating between `undefined` and /SinglePage since that does
  677. // affect the Scroll mode (continuous/non-continuous) used in Adobe Reader.
  678. let pageLayout = "";
  679. if (obj instanceof Name) {
  680. switch (obj.name) {
  681. case "SinglePage":
  682. case "OneColumn":
  683. case "TwoColumnLeft":
  684. case "TwoColumnRight":
  685. case "TwoPageLeft":
  686. case "TwoPageRight":
  687. pageLayout = obj.name;
  688. }
  689. }
  690. return shadow(this, "pageLayout", pageLayout);
  691. }
  692. get pageMode() {
  693. const obj = this._catDict.get("PageMode");
  694. let pageMode = "UseNone"; // Default value.
  695. if (obj instanceof Name) {
  696. switch (obj.name) {
  697. case "UseNone":
  698. case "UseOutlines":
  699. case "UseThumbs":
  700. case "FullScreen":
  701. case "UseOC":
  702. case "UseAttachments":
  703. pageMode = obj.name;
  704. }
  705. }
  706. return shadow(this, "pageMode", pageMode);
  707. }
  708. get viewerPreferences() {
  709. const obj = this._catDict.get("ViewerPreferences");
  710. if (!(obj instanceof Dict)) {
  711. return shadow(this, "viewerPreferences", null);
  712. }
  713. let prefs = null;
  714. for (const key of obj.getKeys()) {
  715. const value = obj.get(key);
  716. let prefValue;
  717. switch (key) {
  718. case "HideToolbar":
  719. case "HideMenubar":
  720. case "HideWindowUI":
  721. case "FitWindow":
  722. case "CenterWindow":
  723. case "DisplayDocTitle":
  724. case "PickTrayByPDFSize":
  725. if (typeof value === "boolean") {
  726. prefValue = value;
  727. }
  728. break;
  729. case "NonFullScreenPageMode":
  730. if (value instanceof Name) {
  731. switch (value.name) {
  732. case "UseNone":
  733. case "UseOutlines":
  734. case "UseThumbs":
  735. case "UseOC":
  736. prefValue = value.name;
  737. break;
  738. default:
  739. prefValue = "UseNone";
  740. }
  741. }
  742. break;
  743. case "Direction":
  744. if (value instanceof Name) {
  745. switch (value.name) {
  746. case "L2R":
  747. case "R2L":
  748. prefValue = value.name;
  749. break;
  750. default:
  751. prefValue = "L2R";
  752. }
  753. }
  754. break;
  755. case "ViewArea":
  756. case "ViewClip":
  757. case "PrintArea":
  758. case "PrintClip":
  759. if (value instanceof Name) {
  760. switch (value.name) {
  761. case "MediaBox":
  762. case "CropBox":
  763. case "BleedBox":
  764. case "TrimBox":
  765. case "ArtBox":
  766. prefValue = value.name;
  767. break;
  768. default:
  769. prefValue = "CropBox";
  770. }
  771. }
  772. break;
  773. case "PrintScaling":
  774. if (value instanceof Name) {
  775. switch (value.name) {
  776. case "None":
  777. case "AppDefault":
  778. prefValue = value.name;
  779. break;
  780. default:
  781. prefValue = "AppDefault";
  782. }
  783. }
  784. break;
  785. case "Duplex":
  786. if (value instanceof Name) {
  787. switch (value.name) {
  788. case "Simplex":
  789. case "DuplexFlipShortEdge":
  790. case "DuplexFlipLongEdge":
  791. prefValue = value.name;
  792. break;
  793. default:
  794. prefValue = "None";
  795. }
  796. }
  797. break;
  798. case "PrintPageRange":
  799. // The number of elements must be even.
  800. if (Array.isArray(value) && value.length % 2 === 0) {
  801. const isValid = value.every((page, i, arr) => {
  802. return (
  803. Number.isInteger(page) &&
  804. page > 0 &&
  805. (i === 0 || page >= arr[i - 1]) &&
  806. page <= this.numPages
  807. );
  808. });
  809. if (isValid) {
  810. prefValue = value;
  811. }
  812. }
  813. break;
  814. case "NumCopies":
  815. if (Number.isInteger(value) && value > 0) {
  816. prefValue = value;
  817. }
  818. break;
  819. default:
  820. warn(`Ignoring non-standard key in ViewerPreferences: ${key}.`);
  821. continue;
  822. }
  823. if (prefValue === undefined) {
  824. warn(`Bad value, for key "${key}", in ViewerPreferences: ${value}.`);
  825. continue;
  826. }
  827. if (!prefs) {
  828. prefs = Object.create(null);
  829. }
  830. prefs[key] = prefValue;
  831. }
  832. return shadow(this, "viewerPreferences", prefs);
  833. }
  834. get openAction() {
  835. const obj = this._catDict.get("OpenAction");
  836. const openAction = Object.create(null);
  837. if (obj instanceof Dict) {
  838. // Convert the OpenAction dictionary into a format that works with
  839. // `parseDestDictionary`, to avoid having to re-implement those checks.
  840. const destDict = new Dict(this.xref);
  841. destDict.set("A", obj);
  842. const resultObj = { url: null, dest: null, action: null };
  843. Catalog.parseDestDictionary({ destDict, resultObj });
  844. if (Array.isArray(resultObj.dest)) {
  845. openAction.dest = resultObj.dest;
  846. } else if (resultObj.action) {
  847. openAction.action = resultObj.action;
  848. }
  849. } else if (Array.isArray(obj)) {
  850. openAction.dest = obj;
  851. }
  852. return shadow(
  853. this,
  854. "openAction",
  855. objectSize(openAction) > 0 ? openAction : null
  856. );
  857. }
  858. get attachments() {
  859. const obj = this._catDict.get("Names");
  860. let attachments = null;
  861. if (obj instanceof Dict && obj.has("EmbeddedFiles")) {
  862. const nameTree = new NameTree(obj.getRaw("EmbeddedFiles"), this.xref);
  863. for (const [key, value] of nameTree.getAll()) {
  864. const fs = new FileSpec(value, this.xref);
  865. if (!attachments) {
  866. attachments = Object.create(null);
  867. }
  868. attachments[stringToPDFString(key)] = fs.serializable;
  869. }
  870. }
  871. return shadow(this, "attachments", attachments);
  872. }
  873. get xfaImages() {
  874. const obj = this._catDict.get("Names");
  875. let xfaImages = null;
  876. if (obj instanceof Dict && obj.has("XFAImages")) {
  877. const nameTree = new NameTree(obj.getRaw("XFAImages"), this.xref);
  878. for (const [key, value] of nameTree.getAll()) {
  879. if (!xfaImages) {
  880. xfaImages = new Dict(this.xref);
  881. }
  882. xfaImages.set(stringToPDFString(key), value);
  883. }
  884. }
  885. return shadow(this, "xfaImages", xfaImages);
  886. }
  887. _collectJavaScript() {
  888. const obj = this._catDict.get("Names");
  889. let javaScript = null;
  890. function appendIfJavaScriptDict(name, jsDict) {
  891. if (!(jsDict instanceof Dict)) {
  892. return;
  893. }
  894. if (!isName(jsDict.get("S"), "JavaScript")) {
  895. return;
  896. }
  897. let js = jsDict.get("JS");
  898. if (js instanceof BaseStream) {
  899. js = js.getString();
  900. } else if (typeof js !== "string") {
  901. return;
  902. }
  903. if (javaScript === null) {
  904. javaScript = new Map();
  905. }
  906. js = stringToPDFString(js).replace(/\u0000/g, "");
  907. javaScript.set(name, js);
  908. }
  909. if (obj instanceof Dict && obj.has("JavaScript")) {
  910. const nameTree = new NameTree(obj.getRaw("JavaScript"), this.xref);
  911. for (const [key, value] of nameTree.getAll()) {
  912. appendIfJavaScriptDict(stringToPDFString(key), value);
  913. }
  914. }
  915. // Append OpenAction "JavaScript" actions, if any, to the JavaScript map.
  916. const openAction = this._catDict.get("OpenAction");
  917. if (openAction) {
  918. appendIfJavaScriptDict("OpenAction", openAction);
  919. }
  920. return javaScript;
  921. }
  922. get javaScript() {
  923. const javaScript = this._collectJavaScript();
  924. return shadow(
  925. this,
  926. "javaScript",
  927. javaScript ? [...javaScript.values()] : null
  928. );
  929. }
  930. get jsActions() {
  931. const javaScript = this._collectJavaScript();
  932. let actions = collectActions(
  933. this.xref,
  934. this._catDict,
  935. DocumentActionEventType
  936. );
  937. if (javaScript) {
  938. if (!actions) {
  939. actions = Object.create(null);
  940. }
  941. for (const [key, val] of javaScript) {
  942. if (key in actions) {
  943. actions[key].push(val);
  944. } else {
  945. actions[key] = [val];
  946. }
  947. }
  948. }
  949. return shadow(this, "jsActions", actions);
  950. }
  951. async fontFallback(id, handler) {
  952. const translatedFonts = await Promise.all(this.fontCache);
  953. for (const translatedFont of translatedFonts) {
  954. if (translatedFont.loadedName === id) {
  955. translatedFont.fallback(handler);
  956. return;
  957. }
  958. }
  959. }
  960. async cleanup(manuallyTriggered = false) {
  961. clearGlobalCaches();
  962. this.globalImageCache.clear(/* onlyData = */ manuallyTriggered);
  963. this.pageKidsCountCache.clear();
  964. this.pageIndexCache.clear();
  965. this.nonBlendModesSet.clear();
  966. const translatedFonts = await Promise.all(this.fontCache);
  967. for (const { dict } of translatedFonts) {
  968. delete dict.cacheKey;
  969. }
  970. this.fontCache.clear();
  971. this.builtInCMapCache.clear();
  972. this.standardFontDataCache.clear();
  973. }
  974. async getPageDict(pageIndex) {
  975. const nodesToVisit = [this.toplevelPagesDict];
  976. const visitedNodes = new RefSet();
  977. const pagesRef = this._catDict.getRaw("Pages");
  978. if (pagesRef instanceof Ref) {
  979. visitedNodes.put(pagesRef);
  980. }
  981. const xref = this.xref,
  982. pageKidsCountCache = this.pageKidsCountCache,
  983. pageIndexCache = this.pageIndexCache;
  984. let currentPageIndex = 0;
  985. while (nodesToVisit.length) {
  986. const currentNode = nodesToVisit.pop();
  987. if (currentNode instanceof Ref) {
  988. const count = pageKidsCountCache.get(currentNode);
  989. // Skip nodes where the page can't be.
  990. if (count >= 0 && currentPageIndex + count <= pageIndex) {
  991. currentPageIndex += count;
  992. continue;
  993. }
  994. // Prevent circular references in the /Pages tree.
  995. if (visitedNodes.has(currentNode)) {
  996. throw new FormatError("Pages tree contains circular reference.");
  997. }
  998. visitedNodes.put(currentNode);
  999. const obj = await xref.fetchAsync(currentNode);
  1000. if (obj instanceof Dict) {
  1001. let type = obj.getRaw("Type");
  1002. if (type instanceof Ref) {
  1003. type = await xref.fetchAsync(type);
  1004. }
  1005. if (isName(type, "Page") || !obj.has("Kids")) {
  1006. // Cache the Page reference, since it can *greatly* improve
  1007. // performance by reducing redundant lookups in long documents
  1008. // where all nodes are found at *one* level of the tree.
  1009. if (!pageKidsCountCache.has(currentNode)) {
  1010. pageKidsCountCache.put(currentNode, 1);
  1011. }
  1012. // Help improve performance of the `getPageIndex` method.
  1013. if (!pageIndexCache.has(currentNode)) {
  1014. pageIndexCache.put(currentNode, currentPageIndex);
  1015. }
  1016. if (currentPageIndex === pageIndex) {
  1017. return [obj, currentNode];
  1018. }
  1019. currentPageIndex++;
  1020. continue;
  1021. }
  1022. }
  1023. nodesToVisit.push(obj);
  1024. continue;
  1025. }
  1026. // Must be a child page dictionary.
  1027. if (!(currentNode instanceof Dict)) {
  1028. throw new FormatError(
  1029. "Page dictionary kid reference points to wrong type of object."
  1030. );
  1031. }
  1032. const { objId } = currentNode;
  1033. let count = currentNode.getRaw("Count");
  1034. if (count instanceof Ref) {
  1035. count = await xref.fetchAsync(count);
  1036. }
  1037. if (Number.isInteger(count) && count >= 0) {
  1038. // Cache the Kids count, since it can reduce redundant lookups in
  1039. // documents where all nodes are found at *one* level of the tree.
  1040. if (objId && !pageKidsCountCache.has(objId)) {
  1041. pageKidsCountCache.put(objId, count);
  1042. }
  1043. // Skip nodes where the page can't be.
  1044. if (currentPageIndex + count <= pageIndex) {
  1045. currentPageIndex += count;
  1046. continue;
  1047. }
  1048. }
  1049. let kids = currentNode.getRaw("Kids");
  1050. if (kids instanceof Ref) {
  1051. kids = await xref.fetchAsync(kids);
  1052. }
  1053. if (!Array.isArray(kids)) {
  1054. // Prevent errors in corrupt PDF documents that violate the
  1055. // specification by *inlining* Page dicts directly in the Kids
  1056. // array, rather than using indirect objects (fixes issue9540.pdf).
  1057. let type = currentNode.getRaw("Type");
  1058. if (type instanceof Ref) {
  1059. type = await xref.fetchAsync(type);
  1060. }
  1061. if (isName(type, "Page") || !currentNode.has("Kids")) {
  1062. if (currentPageIndex === pageIndex) {
  1063. return [currentNode, null];
  1064. }
  1065. currentPageIndex++;
  1066. continue;
  1067. }
  1068. throw new FormatError("Page dictionary kids object is not an array.");
  1069. }
  1070. // Always check all `Kids` nodes, to avoid getting stuck in an empty
  1071. // node further down in the tree (see issue5644.pdf, issue8088.pdf),
  1072. // and to ensure that we actually find the correct `Page` dict.
  1073. for (let last = kids.length - 1; last >= 0; last--) {
  1074. nodesToVisit.push(kids[last]);
  1075. }
  1076. }
  1077. throw new Error(`Page index ${pageIndex} not found.`);
  1078. }
  1079. /**
  1080. * Eagerly fetches the entire /Pages-tree; should ONLY be used as a fallback.
  1081. * @returns {Promise<Map>}
  1082. */
  1083. async getAllPageDicts(recoveryMode = false) {
  1084. const { ignoreErrors } = this.pdfManager.evaluatorOptions;
  1085. const queue = [{ currentNode: this.toplevelPagesDict, posInKids: 0 }];
  1086. const visitedNodes = new RefSet();
  1087. const pagesRef = this._catDict.getRaw("Pages");
  1088. if (pagesRef instanceof Ref) {
  1089. visitedNodes.put(pagesRef);
  1090. }
  1091. const map = new Map(),
  1092. xref = this.xref,
  1093. pageIndexCache = this.pageIndexCache;
  1094. let pageIndex = 0;
  1095. function addPageDict(pageDict, pageRef) {
  1096. // Help improve performance of the `getPageIndex` method.
  1097. if (pageRef && !pageIndexCache.has(pageRef)) {
  1098. pageIndexCache.put(pageRef, pageIndex);
  1099. }
  1100. map.set(pageIndex++, [pageDict, pageRef]);
  1101. }
  1102. function addPageError(error) {
  1103. if (error instanceof XRefEntryException && !recoveryMode) {
  1104. throw error;
  1105. }
  1106. if (recoveryMode && ignoreErrors && pageIndex === 0) {
  1107. // Ensure that the viewer will always load (fixes issue15590.pdf).
  1108. warn(`getAllPageDicts - Skipping invalid first page: "${error}".`);
  1109. error = Dict.empty;
  1110. }
  1111. map.set(pageIndex++, [error, null]);
  1112. }
  1113. while (queue.length > 0) {
  1114. const queueItem = queue.at(-1);
  1115. const { currentNode, posInKids } = queueItem;
  1116. let kids = currentNode.getRaw("Kids");
  1117. if (kids instanceof Ref) {
  1118. try {
  1119. kids = await xref.fetchAsync(kids);
  1120. } catch (ex) {
  1121. addPageError(ex);
  1122. break;
  1123. }
  1124. }
  1125. if (!Array.isArray(kids)) {
  1126. addPageError(
  1127. new FormatError("Page dictionary kids object is not an array.")
  1128. );
  1129. break;
  1130. }
  1131. if (posInKids >= kids.length) {
  1132. queue.pop();
  1133. continue;
  1134. }
  1135. const kidObj = kids[posInKids];
  1136. let obj;
  1137. if (kidObj instanceof Ref) {
  1138. // Prevent circular references in the /Pages tree.
  1139. if (visitedNodes.has(kidObj)) {
  1140. addPageError(
  1141. new FormatError("Pages tree contains circular reference.")
  1142. );
  1143. break;
  1144. }
  1145. visitedNodes.put(kidObj);
  1146. try {
  1147. obj = await xref.fetchAsync(kidObj);
  1148. } catch (ex) {
  1149. addPageError(ex);
  1150. break;
  1151. }
  1152. } else {
  1153. // Prevent errors in corrupt PDF documents that violate the
  1154. // specification by *inlining* Page dicts directly in the Kids
  1155. // array, rather than using indirect objects (see issue9540.pdf).
  1156. obj = kidObj;
  1157. }
  1158. if (!(obj instanceof Dict)) {
  1159. addPageError(
  1160. new FormatError(
  1161. "Page dictionary kid reference points to wrong type of object."
  1162. )
  1163. );
  1164. break;
  1165. }
  1166. let type = obj.getRaw("Type");
  1167. if (type instanceof Ref) {
  1168. try {
  1169. type = await xref.fetchAsync(type);
  1170. } catch (ex) {
  1171. addPageError(ex);
  1172. break;
  1173. }
  1174. }
  1175. if (isName(type, "Page") || !obj.has("Kids")) {
  1176. addPageDict(obj, kidObj instanceof Ref ? kidObj : null);
  1177. } else {
  1178. queue.push({ currentNode: obj, posInKids: 0 });
  1179. }
  1180. queueItem.posInKids++;
  1181. }
  1182. return map;
  1183. }
  1184. getPageIndex(pageRef) {
  1185. const cachedPageIndex = this.pageIndexCache.get(pageRef);
  1186. if (cachedPageIndex !== undefined) {
  1187. return Promise.resolve(cachedPageIndex);
  1188. }
  1189. // The page tree nodes have the count of all the leaves below them. To get
  1190. // how many pages are before we just have to walk up the tree and keep
  1191. // adding the count of siblings to the left of the node.
  1192. const xref = this.xref;
  1193. function pagesBeforeRef(kidRef) {
  1194. let total = 0,
  1195. parentRef;
  1196. return xref
  1197. .fetchAsync(kidRef)
  1198. .then(function (node) {
  1199. if (
  1200. isRefsEqual(kidRef, pageRef) &&
  1201. !isDict(node, "Page") &&
  1202. !(node instanceof Dict && !node.has("Type") && node.has("Contents"))
  1203. ) {
  1204. throw new FormatError(
  1205. "The reference does not point to a /Page dictionary."
  1206. );
  1207. }
  1208. if (!node) {
  1209. return null;
  1210. }
  1211. if (!(node instanceof Dict)) {
  1212. throw new FormatError("Node must be a dictionary.");
  1213. }
  1214. parentRef = node.getRaw("Parent");
  1215. return node.getAsync("Parent");
  1216. })
  1217. .then(function (parent) {
  1218. if (!parent) {
  1219. return null;
  1220. }
  1221. if (!(parent instanceof Dict)) {
  1222. throw new FormatError("Parent must be a dictionary.");
  1223. }
  1224. return parent.getAsync("Kids");
  1225. })
  1226. .then(function (kids) {
  1227. if (!kids) {
  1228. return null;
  1229. }
  1230. const kidPromises = [];
  1231. let found = false;
  1232. for (const kid of kids) {
  1233. if (!(kid instanceof Ref)) {
  1234. throw new FormatError("Kid must be a reference.");
  1235. }
  1236. if (isRefsEqual(kid, kidRef)) {
  1237. found = true;
  1238. break;
  1239. }
  1240. kidPromises.push(
  1241. xref.fetchAsync(kid).then(function (obj) {
  1242. if (!(obj instanceof Dict)) {
  1243. throw new FormatError("Kid node must be a dictionary.");
  1244. }
  1245. if (obj.has("Count")) {
  1246. total += obj.get("Count");
  1247. } else {
  1248. // Page leaf node.
  1249. total++;
  1250. }
  1251. })
  1252. );
  1253. }
  1254. if (!found) {
  1255. throw new FormatError("Kid reference not found in parent's kids.");
  1256. }
  1257. return Promise.all(kidPromises).then(function () {
  1258. return [total, parentRef];
  1259. });
  1260. });
  1261. }
  1262. let total = 0;
  1263. const next = ref =>
  1264. pagesBeforeRef(ref).then(args => {
  1265. if (!args) {
  1266. this.pageIndexCache.put(pageRef, total);
  1267. return total;
  1268. }
  1269. const [count, parentRef] = args;
  1270. total += count;
  1271. return next(parentRef);
  1272. });
  1273. return next(pageRef);
  1274. }
  1275. get baseUrl() {
  1276. const uri = this._catDict.get("URI");
  1277. if (uri instanceof Dict) {
  1278. const base = uri.get("Base");
  1279. if (typeof base === "string") {
  1280. const absoluteUrl = createValidAbsoluteUrl(base, null, {
  1281. tryConvertEncoding: true,
  1282. });
  1283. if (absoluteUrl) {
  1284. return shadow(this, "baseUrl", absoluteUrl.href);
  1285. }
  1286. }
  1287. }
  1288. return shadow(this, "baseUrl", null);
  1289. }
  1290. /**
  1291. * @typedef {Object} ParseDestDictionaryParameters
  1292. * @property {Dict} destDict - The dictionary containing the destination.
  1293. * @property {Object} resultObj - The object where the parsed destination
  1294. * properties will be placed.
  1295. * @property {string} [docBaseUrl] - The document base URL that is used when
  1296. * attempting to recover valid absolute URLs from relative ones.
  1297. * @property {Object} [docAttachments] - The document attachments (may not
  1298. * exist in most PDF documents).
  1299. */
  1300. /**
  1301. * Helper function used to parse the contents of destination dictionaries.
  1302. * @param {ParseDestDictionaryParameters} params
  1303. */
  1304. static parseDestDictionary(params) {
  1305. const destDict = params.destDict;
  1306. if (!(destDict instanceof Dict)) {
  1307. warn("parseDestDictionary: `destDict` must be a dictionary.");
  1308. return;
  1309. }
  1310. const resultObj = params.resultObj;
  1311. if (typeof resultObj !== "object") {
  1312. warn("parseDestDictionary: `resultObj` must be an object.");
  1313. return;
  1314. }
  1315. const docBaseUrl = params.docBaseUrl || null;
  1316. const docAttachments = params.docAttachments || null;
  1317. let action = destDict.get("A"),
  1318. url,
  1319. dest;
  1320. if (!(action instanceof Dict)) {
  1321. if (destDict.has("Dest")) {
  1322. // A /Dest entry should *only* contain a Name or an Array, but some bad
  1323. // PDF generators ignore that and treat it as an /A entry.
  1324. action = destDict.get("Dest");
  1325. } else {
  1326. action = destDict.get("AA");
  1327. if (action instanceof Dict) {
  1328. if (action.has("D")) {
  1329. // MouseDown
  1330. action = action.get("D");
  1331. } else if (action.has("U")) {
  1332. // MouseUp
  1333. action = action.get("U");
  1334. }
  1335. }
  1336. }
  1337. }
  1338. if (action instanceof Dict) {
  1339. const actionType = action.get("S");
  1340. if (!(actionType instanceof Name)) {
  1341. warn("parseDestDictionary: Invalid type in Action dictionary.");
  1342. return;
  1343. }
  1344. const actionName = actionType.name;
  1345. switch (actionName) {
  1346. case "ResetForm":
  1347. const flags = action.get("Flags");
  1348. const include = ((typeof flags === "number" ? flags : 0) & 1) === 0;
  1349. const fields = [];
  1350. const refs = [];
  1351. for (const obj of action.get("Fields") || []) {
  1352. if (obj instanceof Ref) {
  1353. refs.push(obj.toString());
  1354. } else if (typeof obj === "string") {
  1355. fields.push(stringToPDFString(obj));
  1356. }
  1357. }
  1358. resultObj.resetForm = { fields, refs, include };
  1359. break;
  1360. case "URI":
  1361. url = action.get("URI");
  1362. if (url instanceof Name) {
  1363. // Some bad PDFs do not put parentheses around relative URLs.
  1364. url = "/" + url.name;
  1365. }
  1366. break;
  1367. case "GoTo":
  1368. dest = action.get("D");
  1369. break;
  1370. case "Launch":
  1371. // We neither want, nor can, support arbitrary 'Launch' actions.
  1372. // However, in practice they are mostly used for linking to other PDF
  1373. // files, which we thus attempt to support (utilizing `docBaseUrl`).
  1374. /* falls through */
  1375. case "GoToR":
  1376. const urlDict = action.get("F");
  1377. if (urlDict instanceof Dict) {
  1378. // We assume that we found a FileSpec dictionary
  1379. // and fetch the URL without checking any further.
  1380. url = urlDict.get("F") || null;
  1381. } else if (typeof urlDict === "string") {
  1382. url = urlDict;
  1383. }
  1384. // NOTE: the destination is relative to the *remote* document.
  1385. let remoteDest = action.get("D");
  1386. if (remoteDest) {
  1387. if (remoteDest instanceof Name) {
  1388. remoteDest = remoteDest.name;
  1389. }
  1390. if (typeof url === "string") {
  1391. const baseUrl = url.split("#")[0];
  1392. if (typeof remoteDest === "string") {
  1393. url = baseUrl + "#" + remoteDest;
  1394. } else if (Array.isArray(remoteDest)) {
  1395. url = baseUrl + "#" + JSON.stringify(remoteDest);
  1396. }
  1397. }
  1398. }
  1399. // The 'NewWindow' property, equal to `LinkTarget.BLANK`.
  1400. const newWindow = action.get("NewWindow");
  1401. if (typeof newWindow === "boolean") {
  1402. resultObj.newWindow = newWindow;
  1403. }
  1404. break;
  1405. case "GoToE":
  1406. const target = action.get("T");
  1407. let attachment;
  1408. if (docAttachments && target instanceof Dict) {
  1409. const relationship = target.get("R");
  1410. const name = target.get("N");
  1411. if (isName(relationship, "C") && typeof name === "string") {
  1412. attachment = docAttachments[stringToPDFString(name)];
  1413. }
  1414. }
  1415. if (attachment) {
  1416. resultObj.attachment = attachment;
  1417. } else {
  1418. warn(`parseDestDictionary - unimplemented "GoToE" action.`);
  1419. }
  1420. break;
  1421. case "Named":
  1422. const namedAction = action.get("N");
  1423. if (namedAction instanceof Name) {
  1424. resultObj.action = namedAction.name;
  1425. }
  1426. break;
  1427. case "SetOCGState":
  1428. const state = action.get("State");
  1429. const preserveRB = action.get("PreserveRB");
  1430. if (!Array.isArray(state) || state.length === 0) {
  1431. break;
  1432. }
  1433. const stateArr = [];
  1434. for (const elem of state) {
  1435. if (elem instanceof Name) {
  1436. switch (elem.name) {
  1437. case "ON":
  1438. case "OFF":
  1439. case "Toggle":
  1440. stateArr.push(elem.name);
  1441. break;
  1442. }
  1443. } else if (elem instanceof Ref) {
  1444. stateArr.push(elem.toString());
  1445. }
  1446. }
  1447. if (stateArr.length !== state.length) {
  1448. break; // Some of the original entries are not valid.
  1449. }
  1450. resultObj.setOCGState = {
  1451. state: stateArr,
  1452. preserveRB: typeof preserveRB === "boolean" ? preserveRB : true,
  1453. };
  1454. break;
  1455. case "JavaScript":
  1456. const jsAction = action.get("JS");
  1457. let js;
  1458. if (jsAction instanceof BaseStream) {
  1459. js = jsAction.getString();
  1460. } else if (typeof jsAction === "string") {
  1461. js = jsAction;
  1462. }
  1463. const jsURL = js && recoverJsURL(stringToPDFString(js));
  1464. if (jsURL) {
  1465. url = jsURL.url;
  1466. resultObj.newWindow = jsURL.newWindow;
  1467. break;
  1468. }
  1469. /* falls through */
  1470. default:
  1471. if (actionName === "JavaScript" || actionName === "SubmitForm") {
  1472. // Don't bother the user with a warning for actions that require
  1473. // scripting support, since those will be handled separately.
  1474. break;
  1475. }
  1476. warn(`parseDestDictionary - unsupported action: "${actionName}".`);
  1477. break;
  1478. }
  1479. } else if (destDict.has("Dest")) {
  1480. // Simple destination.
  1481. dest = destDict.get("Dest");
  1482. }
  1483. if (typeof url === "string") {
  1484. const absoluteUrl = createValidAbsoluteUrl(url, docBaseUrl, {
  1485. addDefaultProtocol: true,
  1486. tryConvertEncoding: true,
  1487. });
  1488. if (absoluteUrl) {
  1489. resultObj.url = absoluteUrl.href;
  1490. }
  1491. resultObj.unsafeUrl = url;
  1492. }
  1493. if (dest) {
  1494. if (dest instanceof Name) {
  1495. dest = dest.name;
  1496. }
  1497. if (typeof dest === "string") {
  1498. resultObj.dest = stringToPDFString(dest);
  1499. } else if (Array.isArray(dest)) {
  1500. resultObj.dest = dest;
  1501. }
  1502. }
  1503. }
  1504. }
  1505. export { Catalog };