@cantoo/pdf-lib
Version:
Create and modify PDF files with JavaScript
264 lines (219 loc) • 7.56 kB
text/typescript
import PDFArray from '../objects/PDFArray';
import PDFDict, { DictMap } from '../objects/PDFDict';
import PDFName from '../objects/PDFName';
import PDFNumber from '../objects/PDFNumber';
import PDFObject from '../objects/PDFObject';
import PDFRef from '../objects/PDFRef';
import PDFStream from '../objects/PDFStream';
import PDFContext from '../PDFContext';
import PDFPageTree from './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);
}
ArtBox(): PDFArray | undefined {
return this.lookupMaybe(PDFName.ArtBox, 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;
}
addAnnot(annotRef: PDFRef): void {
const { Annots } = this.normalizedEntries();
Annots.push(annotRef);
}
removeAnnot(annotRef: PDFRef) {
const { Annots } = this.normalizedEntries();
const index = Annots.indexOf(annotRef);
if (index !== undefined) {
Annots.remove(index);
}
}
setFontDictionary(name: PDFName, fontDictRef: PDFRef): void {
const { Font } = this.normalizedEntries();
Font.set(name, fontDictRef);
}
newFontDictionaryKey(tag: string): PDFName {
const { Font } = this.normalizedEntries();
return Font.uniqueKey(tag);
}
newFontDictionary(tag: string, fontDictRef: PDFRef): PDFName {
const key = this.newFontDictionaryKey(tag);
this.setFontDictionary(key, fontDictRef);
return key;
}
setXObject(name: PDFName, xObjectRef: PDFRef): void {
const { XObject } = this.normalizedEntries();
XObject.set(name, xObjectRef);
}
newXObjectKey(tag: string): PDFName {
const { XObject } = this.normalizedEntries();
return XObject.uniqueKey(tag);
}
newXObject(tag: string, xObjectRef: PDFRef): PDFName {
const key = this.newXObjectKey(tag);
this.setXObject(key, xObjectRef);
return key;
}
setExtGState(name: PDFName, extGStateRef: PDFRef | PDFDict): void {
const { ExtGState } = this.normalizedEntries();
ExtGState.set(name, extGStateRef);
}
newExtGStateKey(tag: string): PDFName {
const { ExtGState } = this.normalizedEntries();
return ExtGState.uniqueKey(tag);
}
newExtGState(tag: string, extGStateRef: PDFRef | PDFDict): PDFName {
const key = this.newExtGStateKey(tag);
this.setExtGState(key, extGStateRef);
return key;
}
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);
// TODO: Clone `ExtGState` if it is inherited
const ExtGState =
Resources.lookupMaybe(PDFName.ExtGState, PDFDict) || context.obj({});
Resources.set(PDFName.ExtGState, ExtGState);
const Annots = this.Annots() || context.obj([]);
this.set(PDFName.Annots, Annots);
this.normalized = true;
}
normalizedEntries() {
this.normalize();
const Annots = this.Annots()!;
const Resources = this.Resources()!;
const Contents = this.Contents() as PDFArray | undefined;
return {
Annots,
Resources,
Contents,
Font: Resources.lookup(PDFName.Font, PDFDict),
XObject: Resources.lookup(PDFName.XObject, PDFDict),
ExtGState: Resources.lookup(PDFName.ExtGState, PDFDict),
};
}
}
export default PDFPageLeaf;