diff --git a/src/core/PDFContext.ts b/src/core/PDFContext.ts index 66163b88e..bf7122640 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 569b9e503..11eb1fc5a 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 07c90dbc9..da6ee3642 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 f79448486..e4962c81f 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 67ba1ff47..b9157d3b4 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; diff --git a/tests/core/structures/PDFPageLeaf.spec.ts b/tests/core/structures/PDFPageLeaf.spec.ts index 0201ac8dc..7b3d5bc36 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>>', ); });