pdf-lib
Version:
Create and modify PDF files with JavaScript
198 lines (165 loc) • 5.74 kB
text/typescript
import PDFArray from 'src/core/objects/PDFArray';
import PDFDict, { DictMap } from 'src/core/objects/PDFDict';
import PDFName from 'src/core/objects/PDFName';
import PDFNumber from 'src/core/objects/PDFNumber';
import PDFObject from 'src/core/objects/PDFObject';
import PDFRef from 'src/core/objects/PDFRef';
import PDFStream from 'src/core/objects/PDFStream';
import PDFContext from 'src/core/PDFContext';
import PDFPageTree from 'src/core/structures/PDFPageTree';
class PDFPageLeaf extends PDFDict {
static readonly InheritableEntries = [
'Resources',
'MediaBox',
'CropBox',
'Rotate',
];
static withContextAndParent = (context: PDFContext, parent: PDFRef) => {
const dict = new Map();
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);
};
static fromMapWithContext = (
map: DictMap,
context: PDFContext,
autoNormalizeCTM = true,
) => new PDFPageLeaf(map, context, autoNormalizeCTM);
private normalized = false;
private readonly autoNormalizeCTM: boolean;
private constructor(
map: DictMap,
context: PDFContext,
autoNormalizeCTM = true,
) {
super(map, context);
this.autoNormalizeCTM = autoNormalizeCTM;
}
clone(context?: PDFContext): PDFPageLeaf {
const clone = PDFPageLeaf.fromMapWithContext(
new Map(),
context || this.context,
this.autoNormalizeCTM,
);
const entries = this.entries();
for (let idx = 0, len = entries.length; idx < len; idx++) {
const [key, value] = entries[idx];
clone.set(key, value);
}
return clone;
}
Parent(): PDFPageTree | undefined {
return this.lookupMaybe(PDFName.Parent, PDFDict) as PDFPageTree | undefined;
}
Contents(): PDFStream | PDFArray | undefined {
return this.lookup(PDFName.of('Contents')) as
| PDFStream
| PDFArray
| undefined;
}
Annots(): PDFArray | undefined {
return this.lookupMaybe(PDFName.Annots, PDFArray);
}
BleedBox(): PDFArray | undefined {
return this.lookupMaybe(PDFName.BleedBox, PDFArray);
}
TrimBox(): PDFArray | undefined {
return this.lookupMaybe(PDFName.TrimBox, PDFArray);
}
Resources(): PDFDict | undefined {
const dictOrRef = this.getInheritableAttribute(PDFName.Resources);
return this.context.lookupMaybe(dictOrRef, PDFDict);
}
MediaBox(): PDFArray {
const arrayOrRef = this.getInheritableAttribute(PDFName.MediaBox);
return this.context.lookup(arrayOrRef, PDFArray);
}
CropBox(): PDFArray | undefined {
const arrayOrRef = this.getInheritableAttribute(PDFName.CropBox);
return this.context.lookupMaybe(arrayOrRef, PDFArray);
}
Rotate(): PDFNumber | undefined {
const numberOrRef = this.getInheritableAttribute(PDFName.Rotate);
return this.context.lookupMaybe(numberOrRef, PDFNumber);
}
getInheritableAttribute(name: PDFName): PDFObject | undefined {
let attribute: PDFObject | undefined;
this.ascend((node) => {
if (!attribute) attribute = node.get(name);
});
return attribute;
}
setParent(parentRef: PDFRef): void {
this.set(PDFName.Parent, parentRef);
}
addContentStream(contentStreamRef: PDFRef): void {
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.Contents();
if (Contents instanceof PDFArray) {
Contents.insert(0, startStream);
Contents.push(endStream);
return true;
}
return false;
}
setFontDictionary(name: PDFName, fontDictRef: PDFRef): void {
const { Font } = this.normalizedEntries();
Font.set(name, fontDictRef);
}
setXObject(name: PDFName, xObjectRef: PDFRef): void {
const { XObject } = this.normalizedEntries();
XObject.set(name, xObjectRef);
}
ascend(visitor: (node: PDFPageTree | PDFPageLeaf) => any): void {
visitor(this);
const Parent = this.Parent();
if (Parent) Parent.ascend(visitor);
}
normalize() {
if (this.normalized) return;
const { context } = this;
const contentsRef = this.get(PDFName.Contents);
const contents = this.context.lookup(contentsRef);
if (contents instanceof PDFStream) {
this.set(PDFName.Contents, context.obj([contentsRef]));
}
if (this.autoNormalizeCTM) {
this.wrapContentStreams(
this.context.getPushGraphicsStateContentStream(),
this.context.getPopGraphicsStateContentStream(),
);
}
// 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);
// TODO: Clone `Font` if it is inherited
const Font =
Resources.lookupMaybe(PDFName.Font, PDFDict) || context.obj({});
Resources.set(PDFName.Font, Font);
// 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;