Repository: Hopding/pdf-lib
Pull request #222 (2019-10-19 01:17:06)

Include inherited resources when normalizing


Patch:
diff --git a/src/core/PDFContext.ts b/src/core/PDFContext.ts
index 66163b8..bf71226 100644
--- a/src/core/PDFContext.ts
+++ b/src/core/PDFContext.ts
@@ -90,6 +90,28 @@ class PDFContext {
     return this.indirectObjects.delete(ref);
   }
 
+  lookupMaybe(ref: LookupKey, type: typeof PDFArray): PDFArray | undefined;
+  lookupMaybe(ref: LookupKey, type: typeof PDFBool): PDFBool | undefined;
+  lookupMaybe(ref: LookupKey, type: typeof PDFDict): PDFDict | undefined;
+  lookupMaybe(
+    ref: LookupKey,
+    type: typeof PDFHexString,
+  ): PDFHexString | undefined;
+  lookupMaybe(ref: LookupKey, type: typeof PDFName): PDFName | undefined;
+  lookupMaybe(ref: LookupKey, type: typeof PDFNull): typeof PDFNull | undefined;
+  lookupMaybe(ref: LookupKey, type: typeof PDFNumber): PDFNumber | undefined;
+  lookupMaybe(ref: LookupKey, type: typeof PDFStream): PDFStream | undefined;
+  lookupMaybe(ref: LookupKey, type: typeof PDFRef): PDFRef | undefined;
+  lookupMaybe(ref: LookupKey, type: typeof PDFString): PDFString | undefined;
+
+  lookupMaybe(ref: LookupKey, type: any) {
+    const result = ref instanceof PDFRef ? this.indirectObjects.get(ref) : ref;
+    if (result && !(result instanceof type)) {
+      throw new UnexpectedObjectTypeError(type, result);
+    }
+    return result;
+  }
+
   lookup(ref: LookupKey): PDFObject | undefined;
   lookup(ref: LookupKey, type: typeof PDFArray): PDFArray;
   lookup(ref: LookupKey, type: typeof PDFBool): PDFBool;
diff --git a/src/core/objects/PDFArray.ts b/src/core/objects/PDFArray.ts
index 569b9e5..11eb1fc 100644
--- a/src/core/objects/PDFArray.ts
+++ b/src/core/objects/PDFArray.ts
@@ -47,6 +47,24 @@ class PDFArray extends PDFObject {
     return this.array[index];
   }
 
+  lookupMaybe(index: number, type: typeof PDFArray): PDFArray | undefined;
+  lookupMaybe(index: number, type: typeof PDFBool): PDFBool | undefined;
+  lookupMaybe(index: number, type: typeof PDFDict): PDFDict | undefined;
+  lookupMaybe(
+    index: number,
+    type: typeof PDFHexString,
+  ): PDFHexString | undefined;
+  lookupMaybe(index: number, type: typeof PDFName): PDFName | undefined;
+  lookupMaybe(index: number, type: typeof PDFNull): typeof PDFNull | undefined;
+  lookupMaybe(index: number, type: typeof PDFNumber): PDFNumber | undefined;
+  lookupMaybe(index: number, type: typeof PDFStream): PDFStream | undefined;
+  lookupMaybe(index: number, type: typeof PDFRef): PDFRef | undefined;
+  lookupMaybe(index: number, type: typeof PDFString): PDFString | undefined;
+
+  lookupMaybe(index: number, type: any) {
+    return this.context.lookupMaybe(this.get(index), type) as any;
+  }
+
   lookup(index: number): PDFObject | undefined;
   lookup(index: number, type: typeof PDFArray): PDFArray;
   lookup(index: number, type: typeof PDFBool): PDFBool;
diff --git a/src/core/objects/PDFDict.ts b/src/core/objects/PDFDict.ts
index 07c90db..da6ee36 100644
--- a/src/core/objects/PDFDict.ts
+++ b/src/core/objects/PDFDict.ts
@@ -41,6 +41,28 @@ class PDFDict extends PDFObject {
     return this.dict.get(key);
   }
 
+  has(key: PDFName): boolean {
+    return this.dict.has(key);
+  }
+
+  lookupMaybe(key: PDFName, type: typeof PDFArray): PDFArray | undefined;
+  lookupMaybe(key: PDFName, type: typeof PDFBool): PDFBool | undefined;
+  lookupMaybe(key: PDFName, type: typeof PDFDict): PDFDict | undefined;
+  lookupMaybe(
+    key: PDFName,
+    type: typeof PDFHexString,
+  ): PDFHexString | undefined;
+  lookupMaybe(key: PDFName, type: typeof PDFName): PDFName | undefined;
+  lookupMaybe(key: PDFName, type: typeof PDFNull): typeof PDFNull | undefined;
+  lookupMaybe(key: PDFName, type: typeof PDFNumber): PDFNumber | undefined;
+  lookupMaybe(key: PDFName, type: typeof PDFStream): PDFStream | undefined;
+  lookupMaybe(key: PDFName, type: typeof PDFRef): PDFRef | undefined;
+  lookupMaybe(key: PDFName, type: typeof PDFString): PDFString | undefined;
+
+  lookupMaybe(key: PDFName, type: any) {
+    return this.context.lookupMaybe(this.get(key), type) as any;
+  }
+
   lookup(key: PDFName): PDFObject | undefined;
   lookup(key: PDFName, type: typeof PDFArray): PDFArray;
   lookup(key: PDFName, type: typeof PDFBool): PDFBool;
diff --git a/src/core/objects/PDFName.ts b/src/core/objects/PDFName.ts
index f794484..e4962c8 100644
--- a/src/core/objects/PDFName.ts
+++ b/src/core/objects/PDFName.ts
@@ -36,6 +36,19 @@ class PDFName extends PDFObject {
   /* tslint:disable member-ordering */
   static readonly Length = PDFName.of('Length');
   static readonly FlateDecode = PDFName.of('FlateDecode');
+  static readonly Resources = PDFName.of('Resources');
+  static readonly Font = PDFName.of('Font');
+  static readonly XObject = PDFName.of('XObject');
+  static readonly Contents = PDFName.of('Contents');
+  static readonly Type = PDFName.of('Type');
+  static readonly Parent = PDFName.of('Parent');
+  static readonly MediaBox = PDFName.of('MediaBox');
+  static readonly Page = PDFName.of('Page');
+  static readonly Annots = PDFName.of('Annots');
+  static readonly TrimBox = PDFName.of('TrimBox');
+  static readonly BleedBox = PDFName.of('BleedBox');
+  static readonly CropBox = PDFName.of('CropBox');
+  static readonly Rotate = PDFName.of('Rotate');
   /* tslint:enable member-ordering */
 
   private readonly encodedName: string;
diff --git a/src/core/structures/PDFPageLeaf.ts b/src/core/structures/PDFPageLeaf.ts
index 67ba1ff..b9157d3 100644
--- a/src/core/structures/PDFPageLeaf.ts
+++ b/src/core/structures/PDFPageLeaf.ts
@@ -18,10 +18,10 @@ class PDFPageLeaf extends PDFDict {
 
   static withContextAndParent = (context: PDFContext, parent: PDFRef) => {
     const dict = new Map();
-    dict.set(PDFName.of('Type'), PDFName.of('Page'));
-    dict.set(PDFName.of('Parent'), parent);
-    dict.set(PDFName.of('Resources'), context.obj({}));
-    dict.set(PDFName.of('MediaBox'), context.obj([0, 0, 612, 792]));
+    dict.set(PDFName.Type, PDFName.Page);
+    dict.set(PDFName.Parent, parent);
+    dict.set(PDFName.Resources, context.obj({}));
+    dict.set(PDFName.MediaBox, context.obj([0, 0, 612, 792]));
     return new PDFPageLeaf(dict, context, false);
   };
 
@@ -57,8 +57,8 @@ class PDFPageLeaf extends PDFDict {
     return clone;
   }
 
-  Parent(): PDFPageTree {
-    return this.lookup(PDFName.of('Parent')) as PDFPageTree;
+  Parent(): PDFPageTree | undefined {
+    return this.lookupMaybe(PDFName.Parent, PDFDict) as PDFPageTree | undefined;
   }
 
   Contents(): PDFStream | PDFArray | undefined {
@@ -69,35 +69,35 @@ class PDFPageLeaf extends PDFDict {
   }
 
   Annots(): PDFArray | undefined {
-    return this.lookup(PDFName.of('Annots')) as PDFArray | undefined;
+    return this.lookupMaybe(PDFName.Annots, PDFArray);
   }
 
   BleedBox(): PDFArray | undefined {
-    return this.lookup(PDFName.of('BleedBox')) as PDFArray | undefined;
+    return this.lookupMaybe(PDFName.BleedBox, PDFArray);
   }
 
   TrimBox(): PDFArray | undefined {
-    return this.lookup(PDFName.of('TrimBox')) as PDFArray | undefined;
+    return this.lookupMaybe(PDFName.TrimBox, PDFArray);
   }
 
-  Resources(): PDFDict {
-    const dictOrRef = this.getInheritableAttribute(PDFName.of('Resources'));
-    return this.context.lookup(dictOrRef, PDFDict);
+  Resources(): PDFDict | undefined {
+    const dictOrRef = this.getInheritableAttribute(PDFName.Resources);
+    return this.context.lookupMaybe(dictOrRef, PDFDict);
   }
 
   MediaBox(): PDFArray {
-    const arrayOrRef = this.getInheritableAttribute(PDFName.of('MediaBox'));
+    const arrayOrRef = this.getInheritableAttribute(PDFName.MediaBox);
     return this.context.lookup(arrayOrRef, PDFArray);
   }
 
   CropBox(): PDFArray | undefined {
-    const maybeArrayOrRef = this.getInheritableAttribute(PDFName.of('CropBox'));
-    return this.context.lookup(maybeArrayOrRef) as PDFArray | undefined;
+    const arrayOrRef = this.getInheritableAttribute(PDFName.CropBox);
+    return this.context.lookupMaybe(arrayOrRef, PDFArray);
   }
 
   Rotate(): PDFNumber | undefined {
-    const numberOrRef = this.getInheritableAttribute(PDFName.of('Rotate'));
-    return this.context.lookup(numberOrRef) as PDFNumber | undefined;
+    const numberOrRef = this.getInheritableAttribute(PDFName.Rotate);
+    return this.context.lookupMaybe(numberOrRef, PDFNumber);
   }
 
   getInheritableAttribute(name: PDFName): PDFObject | undefined {
@@ -109,38 +109,32 @@ class PDFPageLeaf extends PDFDict {
   }
 
   setParent(parentRef: PDFRef): void {
-    this.set(PDFName.of('Parent'), parentRef);
+    this.set(PDFName.Parent, parentRef);
   }
 
   addContentStream(contentStreamRef: PDFRef): void {
-    this.normalize();
-    let Contents = this.Contents();
-    if (!Contents) {
-      Contents = this.context.obj([]);
-      this.set(PDFName.of('Contents'), Contents);
-    }
-    (Contents as PDFArray).push(contentStreamRef);
+    const Contents = this.normalizedEntries().Contents || this.context.obj([]);
+    this.set(PDFName.Contents, Contents);
+    Contents.push(contentStreamRef);
   }
 
   wrapContentStreams(startStream: PDFRef, endStream: PDFRef): boolean {
-    const contents = this.lookup(PDFName.of('Contents'));
-    if (contents instanceof PDFArray) {
-      contents.insert(0, startStream);
-      contents.push(endStream);
+    const Contents = this.Contents();
+    if (Contents instanceof PDFArray) {
+      Contents.insert(0, startStream);
+      Contents.push(endStream);
       return true;
     }
     return false;
   }
 
   setFontDictionary(name: PDFName, fontDictRef: PDFRef): void {
-    this.normalize();
-    const Font = this.Resources().lookup(PDFName.of('Font'), PDFDict);
+    const { Font } = this.normalizedEntries();
     Font.set(name, fontDictRef);
   }
 
   setXObject(name: PDFName, xObjectRef: PDFRef): void {
-    this.normalize();
-    const XObject = this.Resources().lookup(PDFName.of('XObject'), PDFDict);
+    const { XObject } = this.normalizedEntries();
     XObject.set(name, xObjectRef);
   }
 
@@ -150,15 +144,15 @@ class PDFPageLeaf extends PDFDict {
     if (Parent) Parent.ascend(visitor);
   }
 
-  normalize(): void {
+  normalize() {
     if (this.normalized) return;
 
     const { context } = this;
 
-    const contentsRef = this.get(PDFName.of('Contents'));
+    const contentsRef = this.get(PDFName.Contents);
     const contents = this.context.lookup(contentsRef);
     if (contents instanceof PDFStream) {
-      this.set(PDFName.of('Contents'), context.obj([contentsRef]));
+      this.set(PDFName.Contents, context.obj([contentsRef]));
     }
 
     if (this.autoNormalizeCTM) {
@@ -168,19 +162,36 @@ class PDFPageLeaf extends PDFDict {
       );
     }
 
-    const Resources = this.get(PDFName.of('Resources'))
-      ? this.Resources()
-      : context.obj({});
-    this.set(PDFName.of('Resources'), Resources);
+    // TODO: Clone `Resources` if it is inherited
+    const dictOrRef = this.getInheritableAttribute(PDFName.Resources);
+    const Resources =
+      context.lookupMaybe(dictOrRef, PDFDict) || context.obj({});
+    this.set(PDFName.Resources, Resources);
 
-    const Font = Resources.lookup(PDFName.of('Font')) || context.obj({});
-    Resources.set(PDFName.of('Font'), Font);
+    // TODO: Clone `Font` if it is inherited
+    const Font =
+      Resources.lookupMaybe(PDFName.Font, PDFDict) || context.obj({});
+    Resources.set(PDFName.Font, Font);
 
-    const XObject = Resources.lookup(PDFName.of('XObject')) || context.obj({});
-    Resources.set(PDFName.of('XObject'), XObject);
+    // TODO: Clone `XObject` if it is inherited
+    const XObject =
+      Resources.lookupMaybe(PDFName.XObject, PDFDict) || context.obj({});
+    Resources.set(PDFName.XObject, XObject);
 
     this.normalized = true;
   }
+
+  normalizedEntries() {
+    this.normalize();
+    const Resources = this.Resources()!;
+    const Contents = this.Contents() as PDFArray | undefined;
+    return {
+      Resources,
+      Contents,
+      Font: Resources.lookup(PDFName.Font, PDFDict),
+      XObject: Resources.lookup(PDFName.XObject, PDFDict),
+    };
+  }
 }
 
 export default PDFPageLeaf;


Test patch:
diff --git a/tests/core/structures/PDFPageLeaf.spec.ts b/tests/core/structures/PDFPageLeaf.spec.ts
index 0201ac8..7b3d5bc 100644
--- a/tests/core/structures/PDFPageLeaf.spec.ts
+++ b/tests/core/structures/PDFPageLeaf.spec.ts
@@ -173,6 +173,53 @@ describe(`PDFPageLeaf`, () => {
     expect(pageLeaf.Rotate()).toBe(rotate);
   });
 
+  it(`returns its Resources, MediaBox, CropBox, and Rotate entry values after being normalized, when they are inherited`, () => {
+    const context = PDFContext.create();
+
+    const resources = context.obj({
+      Font: { Foo: PDFRef.of(2100) },
+      XObject: { Foo: PDFRef.of(9000) },
+    });
+    const resourcesRef = context.register(resources);
+
+    const mediaBox = context.obj([]);
+    const mediaBoxRef = context.register(mediaBox);
+
+    const cropBox = context.obj([]);
+    const cropBoxRef = context.register(cropBox);
+
+    const rotate = context.obj(270);
+    const rotateRef = context.register(rotate);
+
+    const parent = PDFPageTree.withContext(context);
+    const parentRef = context.register(parent);
+
+    parent.set(PDFName.of('Resources'), resourcesRef);
+    parent.set(PDFName.of('MediaBox'), mediaBoxRef);
+    parent.set(PDFName.of('CropBox'), cropBoxRef);
+    parent.set(PDFName.of('Rotate'), rotateRef);
+
+    const pageLeaf = PDFPageLeaf.withContextAndParent(context, parentRef);
+    const pageLeafRef = context.register(pageLeaf);
+
+    parent.pushLeafNode(pageLeafRef);
+
+    pageLeaf.delete(PDFName.of('Resources'));
+    pageLeaf.delete(PDFName.of('MediaBox'));
+
+    const { Resources, Font, XObject } = pageLeaf.normalizedEntries();
+
+    expect(pageLeaf.Parent()).toBe(parent);
+    expect(pageLeaf.Resources()).toBe(resources);
+    expect(pageLeaf.MediaBox()).toBe(mediaBox);
+    expect(pageLeaf.CropBox()).toBe(cropBox);
+    expect(pageLeaf.Rotate()).toBe(rotate);
+
+    expect(Resources).toBe(resources);
+    expect(Font).toBe(resources.get(PDFName.Font));
+    expect(XObject).toBe(resources.get(PDFName.XObject));
+  });
+
   it(`can set its Parent`, () => {
     const context = PDFContext.create();
     const parentRef = PDFRef.of(1);
@@ -185,7 +232,6 @@ describe(`PDFPageLeaf`, () => {
     const context = PDFContext.create();
     const parentRef = PDFRef.of(1);
     const pageTree = PDFPageLeaf.withContextAndParent(context, parentRef);
-    pageTree.normalize();
 
     pageTree.addContentStream(PDFRef.of(21));
     expect(pageTree.Contents()!.toString()).toBe('[ 21 0 R ]');
@@ -197,20 +243,19 @@ describe(`PDFPageLeaf`, () => {
     const context = PDFContext.create();
     const parentRef = PDFRef.of(1);
     const pageTree = PDFPageLeaf.withContextAndParent(context, parentRef);
-    pageTree.normalize();
 
     const Font = PDFName.of('Font');
     pageTree.setFontDictionary(PDFName.of('Foo'), PDFRef.of(21));
     expect(
       pageTree
-        .Resources()
+        .Resources()!
         .get(Font)!
         .toString(),
     ).toBe('<<\n/Foo 21 0 R\n>>');
     pageTree.setFontDictionary(PDFName.of('Bar'), PDFRef.of(99));
     expect(
       pageTree
-        .Resources()
+        .Resources()!
         .get(Font)!
         .toString(),
     ).toBe('<<\n/Foo 21 0 R\n/Bar 99 0 R\n>>');
@@ -220,20 +265,19 @@ describe(`PDFPageLeaf`, () => {
     const context = PDFContext.create();
     const parentRef = PDFRef.of(1);
     const pageTree = PDFPageLeaf.withContextAndParent(context, parentRef);
-    pageTree.normalize();
 
     const XObject = PDFName.of('XObject');
     pageTree.setXObject(PDFName.of('Foo'), PDFRef.of(21));
     expect(
       pageTree
-        .Resources()
+        .Resources()!
         .get(XObject)!
         .toString(),
     ).toBe('<<\n/Foo 21 0 R\n>>');
     pageTree.setXObject(PDFName.of('Bar'), PDFRef.of(99));
     expect(
       pageTree
-        .Resources()
+        .Resources()!
         .get(XObject)!
         .toString(),
     ).toBe('<<\n/Foo 21 0 R\n/Bar 99 0 R\n>>');
@@ -271,12 +315,12 @@ describe(`PDFPageLeaf`, () => {
     pageTree.set(PDFName.of('Contents'), streamRef);
 
     expect(pageTree.Contents()).toBe(stream);
-    expect(pageTree.Resources().toString()).toBe('<<\n>>');
+    expect(pageTree.Resources()!.toString()).toBe('<<\n>>');
 
     pageTree.normalize();
 
     expect(pageTree.Contents()!.toString()).toBe('[ 21 0 R ]');
-    expect(pageTree.Resources().toString()).toBe(
+    expect(pageTree.Resources()!.toString()).toBe(
       '<<\n/Font <<\n>>\n/XObject <<\n>>\n>>',
     );
   });
@@ -300,7 +344,7 @@ describe(`PDFPageLeaf`, () => {
     expect(pageTree.Contents()!.toString()).toBe(
       `[ ${pushRef} 21 0 R ${popRef} ]`,
     );
-    expect(pageTree.Resources().toString()).toBe(
+    expect(pageTree.Resources()!.toString()).toBe(
       '<<\n/Font <<\n>>\n/XObject <<\n>>\n>>',
     );
   });